深入挖掘原型链

16次阅读

共计 2320 个字符,预计需要花费 6 分钟才能阅读完成。

1. 原型对象,实例对象与构造函数

如果想要深入理解原型链,首先需要明确知道什么时原型对象,什么是实例对象,以及原型对象,实例对象与构造函数三者之间的关系。
首先,实例对象是指通过关键字 new,由构造函数构造出的对象实例对象可以有多个,并且实例对象会继承它的原型对象上的所有属性。

    function Foo() {}
    var f1=new Foo();
    var f2=new Foo();

f1f2 就是由构造函数 Foo 通过关键字 new 创建的两个实例对象。
同一个构造函数的实例对象的原型对象是同一个,是构造函数的 prototype 属性,在实例对象上可以通过__proto__属性获取。即:

    console.log(f1.__proto__ === Foo.prototype);//true
    console.log(f2.__proto__ === Foo.prototype);//true
    console.log(f1.__proto__ === f2.__proto__);//true

因此当我们在原型对象上添加属性或方法时,所有的实例对象都会继承该属性或方法,我们可以将一些公用的方法定义在 Foo.prototype 上。

2. 原型对象,实例对象和构造函数三者的关系

每个实例对象的原型对象上都有一个 constructor 属性,这个属性指向实例对象的构造函数,实例对象可以从它的原型对象上继承该属性。
三者的关系听起来十分绕口,但是用代码体现就很清晰了:
实例对象f1,构造函数Foo,原型对象Foo.prototype===f1.__proto__

    console.log(Foo.prototype.constructor === Foo);//true
    console.log(f1.__proto__.constructor === Foo);//true
    console.log(f1.constructor === Foo);//true
 console.log(f1.constructor === f1.__proto__.constructor);//true

3.JavaScript 的原型链规则

1.js 中规定,所有的对象都有自己的原型对象,原型对象和和函数也是对象,所以他们也有自己的原型对象 (__proto__),因此会形成原型链。
2.js 中读取属性和方法的规则:js 引擎会首先在对象本身查找属性和方法,如果找不到就会沿着原型链逐层向上查找,最终找不到就会返回 undefined。如果找到了就不会再继续查找,所以当一个实例对象和它的原型对象上具有同名属性或方法时,只会查找到对象本身的属性或方法。
使用图示可以更好地理解原型链:

    function Foo() {}
    var f1 = new Foo();

以上这段代码:
首先有实例对象,构造函数和原型对象
原型对象的 constructor 指向构造函数,实例对象继承的 constructor 也是指向构造函数,实例对象和构造函数的 __proto__prototype指向同一个原型对象。

然后,因为 Foo.prototype 也是一个对象,他也具有自身的原型对象 __proto__, 由三者关系我们可以知道,Foo.prototype.__proto__.constructor 应该指向 Foo.prototype 的构造函数,输出结果是 Object, 即Foo.prototype.__proto__.constructor===Object,并且Object.prototype 也应该有自己的原型对象,输出为 null
所以有:

再然后,构造函数 FooObject也是实例对象,他们也应该由自己的原型对象 __proto__,而所有的函数都是由构造函数Function 构造的,所以 Object.__proto__===Function.prototypeFoo.__proto__===Function.prototype,Object.__proto__===Foo.__proto__, 并且Function.prototype 也有自己的原型对象 __popto__,指向了Object.prototype, 可以画出:

原型链图示
这就是一条完整的原型链了,从图中我们可以发现:
Object.prototype是原型链的尽头,再向上就是 null 了,null是无意义的,一次所有的对象都继承了 Object.prototype 上的属性和方法。

4. 提一个小问题,我想让 Foo 的所有实例对象都具有数组的属性和方法,应该怎么做呢?

通过上面的分析,我们知道,实例对象共用的属性和方法都应该定义在了他们的原型对象上,所以数组的属性和方法都在 Array.prototype 上,
实例对象 f1,f2 共用的属性和方法来自 Foo.prototype, 所以我们可以:
Foo.prototype=Array.prototype
这样 f1,f2 就继承了数组的属性和方法,但是我们需要注意一点,此时输出 f1.constructor,来观察f1,f2 的构造函数会发现它们已经成了 Array
这是因为 constructor 属性同样是从原型对象 Foo.prototype 上继承而来的,所以当 Foo.prototype 上的 constructor 属性发生变化时,实例对象 f1constructor也会变化。所以我们如果不想让构造函数发生变化的话,需要重写一次 Foo.prototypeconstructor属性,
Foo.prototype=Array.prototype
Foo.prototype.constructor=Foo

所以我们需要注意,在修改实例对象的原型对象之后,也要记得修改 constructor 属性

正文完
 0