JavaScript(03)面向对象

创建对象

一、js中的面向对象

  • 其他语言中(如java)是依靠类和实例这两个人概念来实现面向对象的编程的
  • JavaScript不区分类和实例的概念,而是通过原型prototype来实现面向对象编程
  • java 本质是通过类模版来创建不同的实例对象,而js中一切皆对象,是通过对象来创建对象
简单面向对象的实现
// 原型对象:
var Student = {
    name: 'Robot',
    height: 1.2,
    run: function () {
        console.log(this.name + ' is running...');
    }
};

function createStudent(name) {
    // 基于Student原型创建一个新对象:
    var s = Object.create(Student);
    // 初始化新对象:
    s.name = name;
    return s;
}

var xiaoming = createStudent('小明');
xiaoming.run(); // 小明 is running...
xiaoming.__proto__ === Student; // true

二、使用构造函数创建对象

1、构造函数

  • 构造函数就是一个普通的函数,不过使用的方法比较特殊
  • 不写new就是一个普通函数,它返回undefined
  • 写了new就变成了一个构造函数,它绑定的this指向新创建的对象,并默认返回this
function Student(name) {
    this.name = name;
    this.hello = function () {
        alert('Hello, ' + this.name + '!');
    }
}

var xiaoming = new Student('小明');
xiaoming.name; // '小明'
xiaoming.hello(); // Hello, 小明!

2、prototype & constructor

  • 创建一个函数A,浏览器就会在内存中对应创建一个对象B,函数A的 prototype 属性指向对象B
  • 这个对象B就是函数A的原型对象,简称函数的原型
  • 这个原型对象B 默认会有一个属性 constructor 指向了这个函数A
  • 可以为函数的 prototype 属性指定新的对象来作为原型,这样做的问题就是新对象的constructor属性则不再指向该构造函数了

3、__proto__

  • 通过构造函数创建的对象没有prototype这个属性,不过可以用__proto__这个非标准用法来查看构造函数的原型对象
    在这里插入图片描述
hasOwnProperty方法和in操作符
  • hasOwnProperty判断属性是不是对象特有的,属性不存在或者存在于原型链中都会返回false
  • in 操作符只要属性存在,无论是对象特有还是原型链中的都返回true
  • 存在于原型中的属性判断:in 返回true,hasOwnProperty返回false

4、JS对象创建的最佳实践

  • 构造函数加属性(特有),原型(prototype)加方法(共有);
function CreatePerson(name, qq)        //构造函数
{
    this.name=name;
    this.qq=qq;
}

CreatePerson.prototype.showName=function ()    //原型
{
    alert('我的名字叫:'+this.name);
};

CreatePerson.prototype.showQQ=function ()
{
    alert('我的QQ号:'+this.qq);
};

var obj=new CreatePerson('blue', '258248832');
var obj2=new CreatePerson('张三', '45648979879');
  • 上诉代码的缺陷在于方法和属性还是分离的,封装性还不够好,可以使用如下代码改进
  • 改进代码的特点就是第一次通过构造函数创建对象的时候会自动通过原型添加方法
function CreatePerson(name, qq)        //构造函数
{
    this.name=name;
    this.qq=qq;
    if(typeof this.showName !== 'function'){
		CreatePerson.prototype.showName=function ()    //原型
		{
		    alert('我的名字叫:'+this.name);
		};
	}
	if(typeof this.showQQ !== 'function'){
		CreatePerson.prototype.showQQ=function ()
		{
		    alert('我的QQ号:'+this.qq);
		};
	}
}

原型继承

一、原型链

  • 原型链示例代码,其中包含继承的实现(非最佳方案)
function Father () {
	// 添加name属性.  默认直接赋值了。当然也可以通过构造函数传递过来
	this.name = "马云";
}
	//给Father的原型添加giveMoney方法
Father.prototype.giveMoney = function () {
	alert("我是Father原型中定义的方法");
}
//再定义一个构造函数。
function Son () {
	//添加age属性
	this.age = 18;
}
//关键地方:把Son构造方法的原型替换成Father的对象。
Son.prototype = new Father();
	//给Son的原型添加getMoney方法
Son.prototype.getMoney = function () {
	alert("我是Son的原型中定义的方法");
}
//创建Son类型的对象
var son1 = new Son();
  • 原型链的示意图,属性和方法的寻找就是按照这样的路径一步一步往上找的
    在这里插入图片描述
  • Father没有指定父类型,默认继承自Object,这也是原型链的顶端
    在这里插入图片描述

二、JS继承的最佳实现

1、原型继承(实质)

  • 上文原型链中的示例代码就是原型继承的实现,这样的代码存在一个问题就是父类对象的属性会被所有的子类共享,这一点不符合属性私有的要求,例如下面的示例代码
function Father () {
	this.girls = ["志玲", "凤姐"];
}
function Son () {
	
}
// 子类的原型对象中就有一个属性 girls ,是个数组
Son.prototype = new Father();	
var son1 = new Son();
var son2 = new Son();
//给son1的girls属性的数组添加一个元素
son1.girls.push("亦非");
//这时,发现son2中的girls属性的数组内容也发生了改变
alert(son2.girls);  // "志玲", "凤姐", "亦非"

2、构造函数借调继承(形式)

  • 通过call方法进行构造函数的借调,但是这种继承仅仅是形式上的,本质没有完成继承
  • 这里的好处就是通过借调使得 girls 这个数组私有化了,后面即使设置父类对象为子类的原型,私有化的 girls 数组也会覆盖原型(父类对象)的 girls 属性,这样不就解决了原型继承的属性共享问题吗
    在这里插入图片描述

3、组合继承(最佳实现)

  • 通过上面的讨论,所以js继承的最佳实现应该是这样的
  • 记得子类 prototype 的 constructor 属性要进行修复,不然可能会发生奇怪的问题
function Father (name,age) {
	// 属性放在构造函数内部
	this.name = name;
	this.age = age;
	// 方法定义在原型中
	if((typeof Father.prototype.eat) != "function"){
		Father.prototype.eat = function () {
			alert(this.name + " 在吃东西");
		}
	}  

}
// 定义子类类型的构造函数
function Son(name, age, sex){
  	//借调父类型的构造函数,相当于把父类型中的属性添加到了未来的子类型的对象中
	Father.call(this, name, age);
	this.sex = sex;
}
//修改子类型的原型。这样就可以继承父类型中的方法了。
Son.prototype = new Father(	);
Son.prototype.constructor = Son;
var son1 = new Son("志玲", 30, "女");
var son2 = new Son("mike", 11, "man");
alert(son1.name + son1.age + son1.sex);
son1.eat();
alert(son2.name + son2.age + son2.sex);
son2.eat();

class继承

  • 这个玩意是 ES6 中引入的,本质还是用原型实现的继承,不过官方为我们进行了封装,不用我们自己实现,这样我们就可以使用类似 java 中的 class 继承的方式来实现 js 的继承了

一、类定义

  • 使用的话还是和以前的没有区别的
class Student {
    constructor(name) {
        this.name = name;
    }

    hello() {
        alert('Hello, ' + this.name + '!');
    }
}

二、类继承

class PrimaryStudent extends Student {
    constructor(name, grade) {
        super(name); // 记得用super调用父类的构造方法!
        this.grade = grade;
    }

    myGrade() {
        alert('I am at grade ' + this.grade);
    }
}

其他补充

一、面向对象的选项卡(练习)

  • 面向对象原则:不能有函数套函数、但可以有全局变量
  • 过程:onload → 构造函数;全局变量 → 属性;函数 → 方法
window.onload=function(){
    new Tar('div1');
};

function Tar(id) {
    var _this = this;
    this.oDiv = document.getElementById(id);
    this.aInput = this.oDiv.getElementsByTagName('input');
    this.aDiv = this.oDiv.getElementsByTagName('div');

    for(var i = 0;i < this.aInput.length;i ++){
        this.aInput[i].index = i;
        this.aInput[i].onclick=function () {
            _this.fnClick(this);
        };
    }
}

Tar.prototype.fnClick = function (aInput) {
    for(var j = 0;j < this.aInput.length;j ++){
        this.aDiv[j].style.display = "none";
        this.aInput[j].className = '';
    }
    this.aDiv[aInput.index].style.display = 'block';
    aInput.className = 'active';
};

二、JSON方式的面向对象

  • js对象中支持使用getter和setter
//first method
var person = {
    _name: '',
    get name() { return this._name },
    set name(n) { this._name = n }
}
 
// 测试
person.name // 输出 --> ''
person.name = 'Zhangsan'
person.name // 输出 --> Zhangsan

//other method
var person = function() {
    var _name = ' ';
    var obj = {};
    Object.defineProperty(obj, 'name', {
        configurable: true,
        enumerable: true,
        get: function() {
            return _name;
        },
        set: function(n) {
            _name = n;
        }
    })
    return obj;
}();
  • ES5中Object有很多方法可以对对象及其属性进行操作
//这里将person的language属性设置为只读
Object.defineProperty(person, "language", {writable:false});

三、继承:以屏幕拖拽为例

/*普通拖拽*/
function Drag(id) {
    var _this = this;
    this.oDiv = document.getElementById(id);
    this.oDiv.onmousedown = function (ev) {
        _this.down(ev);
    }
}

Drag.prototype.down = function (ev) {
    var _this = this;
    var oEvent = ev || event;
    this.disX = oEvent.clientX - this.oDiv.offsetLeft;
    this.disY = oEvent.clientY - this.oDiv.offsetTop;
    document.onmousemove = function (ev) {
        _this.move(ev);
    };
    document.onmouseup = function () {
        _this.up();
    };
};

Drag.prototype.move = function (ev){
    var oEvent = ev || event;
    this.oDiv.style.left = oEvent.clientX - this.disX + "px";
    this.oDiv.style.top = oEvent.clientY - this.disY + "px";
};

Drag.prototype.up = function () {
    document.onmousemove = null;
    document.onmouseup = null;
};


/*限制拖拽*/
function LimitDrag(id) {
    Drag.call(this,id);
}

for(var i in Drag.prototype){
    LimitDrag.prototype[i] = Drag.prototype[i];
}

LimitDrag.prototype.move = function (ev) {
    var oEvent = ev || event;
    var l = oEvent.clientX - this.disX;
    var t = oEvent.clientY - this.disY;
    if(l < 0){
        l = 0;
    }
    if(t < 0){
        t = 0;
    }
    this.oDiv.style.left = l + "px";
    this.oDiv.style.top = t + "px";
}

参考文章:
https://blog.csdn.net/u012468376/article/details/53127929

发布了153 篇原创文章 · 获赞 51 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/stanwuc/article/details/103859378