Javascript学习--原型链

参考:
《JavaScript权威指南》
《JavaScript高级程序设计》

在JavaScript中,每个类的实现是基于其原型继承机制的。如果两个实例都从同一个原型对象上继承了属性,我们说它们是同一个类的实例。如果两个对象继承同一个原型,往往意味着(但不是绝对)它们是同一个构造函数创建并初始化的。

许多面向对象的语言都支持两种继承方式:接口继承和实现继承。
接口是一些方法特征的集合,一个接口只有方法的特征没有方法的实现,因此这些方法可以在不同的地方被不同的类实现,而这些实现可以具有不同的行为(功能)。实现继承即继承一个拥有具体方法的类,可以重写方法。

概念

原型链是JavaScript作为实现继承的主要方法。其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。

构造函数、原型、实例之间的关系

每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。此时,如果让原型对象等于另一个类型的实例,此时原型对象将包含一个指向另一个原型的指针,相应的,另一个原型中也包含着一个指向另一个构造函数的指针。如果另一个原型又是另外一个类型的实例,那么上述关系仍然成立,如此层层递进,就构成了实例与原型的关系。这就是原型链的基本概念。

Javascript学习--原型链
实现原型链的一种基本模式

function SuperType() {
    this.property = true;
}
SuperType.prototype.getSuperValue = function() {
    return this.property;
}
function SubType() {
    this.subproperty = false;
}
// 继承了SuperType
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function() {
    return this.subproperty;
}
var instance = new SubType();
console.log(instance.getSuperValue());       // true

继承是通过创建SuperType实例,并将该实例赋给SubType.prototype实现的。实现的本质是重写原型对象即代之以一个新类型的实例。换句话说就是原来存在于SuperType中的属性和方法现在也存在于SubType中了。
Javascript学习--原型链
从图中SuperType的原型指向SuperType的构造函数,SuperType的原型指针指向SuperType的原型,SuperType的原型中除了有构造函数的指针外,还有属性和方法。SubType继承了SuperType,那么SubType的原型就是SuperType的实例,不仅拥有该实例的全部方法和属性,其内部还有一个指针,指向SuperType的原型。

console.log(SuperType.prototype);
//{getSuperValue: ƒ, constructor: ƒ}
console.log(SubType.prototype);
// SuperType {property: true, getSubValue: ƒ}

结果就是instance指向SubType的原型,SubType指向SuperType的一个实例。
可能有人还要问,为什么instance的返回值是true而不是false呢?因为此时instance调用的是SuperType实例中的getSuperValue,当然调用的也就是SuperType实例的property了。不信你试试看instance有没有property属性。

console.log(SubType.property);    // undefined

要注意,此时instance的construct指向的是SuperType,这是因为原来SubType.prototype中的construct(construct是属性)已经被重写了。

原型链搜索机制

原型搜索也就是说,如果存在多次继承,如何找到某个实例对应的原型和构造函数?
当读取方式访问一个实例的属性的时候,首先会在该实例中搜索该实例,如果没哟u 找到该实例,就会继续搜索该实例的原型。在原型链实现继承的情况下,搜索过程可以沿着原型链搜索向上,即instance.getSuperValue()依次经历:
(1)搜索实例
(2)搜索SubType.prototype
(3)搜索SuperType.prototype
需要注意的是所有引用类型默认都继承了Object,而这个继承也是通过原型链进行的。也就是说所有函数的默认原型都是Object的实例,即默认原型都会包含一个指针,指向Object.prototype。
完整原型链:
Javascript学习--原型链

_proto_

和prototype非常相似的是_proto_。
关于_proto_,MDN有具体介绍。

Object.prototype 的 _proto_ 属性是一个访问器属性(一个getter函数和一个setter函数), 暴露了通过它访问的对象的内部[[Prototype]] (一个对象或 null)。也就是说实例的 _proto_ 属性指向它类型的原型。

let shape = function () {};
let p = {
    a: function () {
        console.log('aaa');
    }
};
shape.prototype.__proto__ = p; 
 // 修改shape.prototype指向p,也就是说shape的实例存在指向p的指针。可以认为此时shape的实例拷贝了p。
let circle = new shape();
circle.a();//aaa
console.log(shape.prototype === circle.__proto__);//true

prototype和__proto__的关系

prototype和__proto__都指向原型对象,任意一个函数(包括构造函数)都有一个prototype属性,指向该函数的原型对象,同样任意一个构造函数实例化的对象,都有一个__proto__属性(__proto__并非标准属性,ECMA-262第5版将该属性或指针称为[[Prototype]],可通过Object.getPrototypeOf()标准方法访问该属性),指向构造函数的原型对象。