一、 原型
在JavaScript中,原型也是一个对象,通过原型可以实现对象的属性继承,JavaScript的对象中都包含了一个Prototype内部属性,这个属性所对应的就是该对象的原型。
Prototype作为对象的内部属性,是不能被直接访问的。所以为了方便查看一个对象的原型,Firefox和Chrome中提供了__proto__这个非标准(不是所有浏览器都支持)的访问器(ECMA引入了标准对象原型访问器”Object.getPrototype(object)”)。
在JavaScript的原型对象中,还包含一个”constructor”属性,这个属性对应创建所有指向该原型的实例的构造函数
- 每个对象都有一个
__proto__属性,并且指向它的prototype原型对象 - 每个构造函数都有一个
prototype原型对象 prototype原型对象里的constructor指向构造函数本身
二、 原型链
因为每个对象和原型都有原型,对象的原型指向原型对象
而父的原型又指向父的父,这种原型层层连接起来的就构成了原型链。
所谓原型”链“,不管是现实中的锁链,还是计算机算法中的链表,他们都是把一个一个节点串联起来,从而形成一条链。
原型对象是节点,而私有属性 __proto__ 就是串联连接两个原型对象节点的”线“
当我们访问对象的一个属性或方法时,它会先在对象自身中寻找,如果有则直接使用,如果没有则会去原型对象中寻找,如果找到则直接使用。如果没有则去原型的原型中寻找,直到找到Object对象的原型,Object对象的原型没有原型,如果在Object原型中依然没有找到,则返回undefined。
我们可以使用对象的hasOwnProperty()来检查对象自身中是否含有该属性;使用in检查对象中是否含有某个属性时,如果对象中没有但是原型中有,也会返回true
function Person() {}
Person.prototype.a = 123;
Person.prototype.sayHello = function () {
alert("hello");
};
var person = new Person()
console.log(person.a)//123
console.log(person.hasOwnProperty('a'));//false
console.log('a'in person)//true
person实例中没有a这个属性,从 person 对象中找不到 a 属性就会从 person 的原型也就是 person.__proto__ ,也就是 Person.prototype中查找,很幸运地得到a的值为123。那假如 person.__proto__中也没有该属性,又该如何查找?
当读取实例的属性时,如果找不到,就会查找与对象关联的原型中的属性,如果还查不到,就去找原型的原型,一直找到最顶层Object为止。Object是JS中所有对象数据类型的基类(最顶层的类)在Object.prototype上没有__proto__这个属性。
console.log(Object.prototype.__proto__ === null) // true
三、 prototype 和 __proto__
有的同学可能会问prototype 和 __proto__有什么用呢?
实例对象的__proto__指向构造函数的prototype,从而实现继承。
prototype对象相当于特定类型所有实例对象都可以访问的公共容器
看代码
function Person(nick, age){
this.nick = nick;
this.age = age;
}
Person.prototype.sayName = function(){
console.log(this.nick);
}
var p1 = new Person('Byron', 20);
var p2 = new Person('Casper', 25);
p1.sayName() // Byron
p2.sayName() // Casper
p1.__proto__ === Person.prototype //true
p2.__proto__ === Person.prototype //true
p1.__proto__ === p2.__proto__ //true
Person.prototype.constructor === Person //true
注意
- 当
Object.prototype.__proto__已被大多数浏览器厂商所支持的今天,其存在和确切行为仅在ECMAScript 2015规范中被标准化为传统功能,以确保Web浏览器的兼容性。为了更好的支持,建议只使用Object.getPrototypeOf()。 - Object.create(null) 新建的对象是没有__proto__属性的。
四、 JavaScript 中的继承
继承是指一个对象直接使用另外一个对象的属性和方法 由此可见只要实现属性和方法的继承,就达到继承的效果
- 得到一个对象的属性
- 得到一个对象的方法
4.1 原型链继承
利用原型让一个引用类型继承另一个引用类型的属性和方法
function SuperType() {
this.name = 'tt';
}
SuperType.prototype.sayName = function() {
return this.name
}
function SubType() {
this.name = 'oo';
}
SubType.prototype = new SuperType()
var instance = new SubType()
instance.sayName() // oo
instance instanceof SubType // true
instance instanceof SuperType // ture
以上的试验中,我们创建了两个构造函数 SuperType 和 SubType ,并且让 SubType 的原型指向 SuperType,SubType 也就继承了 SuperType 原型对象中的方法。所以在创建 instance 实例的时候,实例本身也就具有了 SuperType 中的方法,并且都处在它们的原型链中
SubType.prototype.constructor == SubType // false
SubType.prototype.constructor == SuperType // true
需要注意的是:这个时候 SubType.prototype.constructor 是指向 SuperType 的,相当于重写了 SubType 的原型对象。
SubType.prototype 相当于 SuperType 的实例存在的,所以 SubType.prototype.constructor 就指向 SuperType
4.2 原型继承的特点
优点:
- 简单、易于实现
- 父类新增原型方法/原型属性,子类都能访问到
- 非常纯粹的继承关系,实例是子类的实例,也是父类的实例
缺点:
- 无法实现多继承
- 想要为子类
SubType添加原型方法,就必须在new SuperType之后添加(会覆盖) - 来自原型对象的所有属性被所有实例共享(引用类型的值修改会反映在所有实例上面)
- 创建子类实例时,无法向父类构造函数传参
五、总结
prototype 和 __proto__
-
每个对象都有一个
__proto__属性,并且指向它的prototype原型对象 -
每个构造函数都有一个
prototype原型对象 -
prototype原型对象里的constructor指向构造函数本身
原型链
每个对象都有一个__proto__,它指向它的prototype原型对象,而prototype原型对象又具有一个自己的prototype原型对象,就这样层层往上直到一个对象的原型prototype为null,原型链的终点为 null 对象,即无中生有。
这个查询的路径就是原型链