简单解释什么是原型链

672 阅读2分钟

当一个函数被创建的时候,会产生一个叫做 prototype 的属性。如果我们以当前函数作为构造函数,创建出一个实例对象,那这个实例对象就会有一个隐式的 [[prototype]] 指针指向构造函数的 prototype 属性。也就是这个实例对象的原型。

如果当前实例对象的原型也是由某个类型的实例,那么这个原型也有一个隐式的[[prototype]] 指向另一个原型,如此递推,就形成了一个原型链。举个例子来说,如果我们普通的新建一个函数:

function A() {
    this.name = 'mysteryven'
}

A.prototype.sayName = function() {
    console.log(this.name);
}

我们使用 A 函数创建一个实例,那么,就有:

const a = new A();

a.__proto__ === A.prototype;
A.prototype.__proto__ === Object.prototype;
Object.prototype.__proto__ === null;

在原型对象中,会有一个 constructor 属性默认指向构造函数,比如在我们的内置 Function 函数里:

Function.prototype.constructor === Function

但是,有时候我们在设置原型的时候会像下面这样直接把原型赋值给另外一个对象:

A.prototype = {
    sayName: () => {}
}

这样子就导致 constructor 的指向不准了,针对这种情况,我们要专门的额外设置一下:

A.prototype = {
    constructor: A,
    sayName: () => {}
}

做到这里,指向是没问题了,但是我们会发现,我们可以通过 for...in... 遍历到 constructor 属性:

image.png

原因是因为 JS 内置的 constructor 是不可枚举的。而我们这里给原型对象增加了一个 constructor 属性,没有做其他额外的处理,她就变成可枚举的了。这可不是我们想要的,为此,我们要这么设置:

Object.defineProperty(
    A.prototype, 
    'constructor', 
    {
        enumerable: false, 
        value: A
    }
)

在谷歌浏览器中,我们可以通过在实例对象的 __proto__ 属性访问到它的原型,但是这个并不是标准的实现,并且可能会在未来被废弃,更好的是通过 Object.getPrototypeOf()