原型和原型链

构造函数创建对象

所有函数默认都会有一个名为 prototype 的公有且不可枚举的属性,它会指向另一个对象,这个对象通常称为某函数原型,因为我们通过该函数属性 prototype 引用来访问。

举个例子:

function Foo (name) {
    this.name = name
}

Foo.prototype // { } 对象

通过调用 new Foo() 创建的每个对象将最终被 [[Prototype]] 链接到 Foo.prototype 原型对象上:(见下面 [[Prototype]] 内容介绍)

function Foo (name) {
    this.name = name
}

var bar = new Foo('Jack')

Object.getPrototypeOf(bar) === Foo.prototype // true

[[Prototype]]

JavaScript 中每个对象都有个 [[Prototype]] 内置属性,这个属性会指向该对象的原型,且它也是关联其他对象的引用。如果对象本身没有找到需要的属性,那么就会继续访问对象的 [[Prototype]] 链。

JavaScript 的非标准但许多浏览器实现的属性 __proto__ 来表示 [[Prototype]]

关于 __proto__ 的简单例子:

var foo = {
    name: 'foo',
    age: 20
}

console.log(foo) // 打印结果见下图

Chrome(v89) 浏览器打印结果图如下:

JS原型

分析:从图中可以看到 __proto__ 这个属性指向了该对象的原型 Object.prototype。因为原型对象上的 constructor 属性指向关联的函数 Object()。(见下面 constructor 内容介绍)

关于原型链的简单例子:

var foo = {
    v: 1
}

var bar = Object.create(foo) // 创建 bar 和 foo 关联

console.log(bar.v) // 1

分析:因为 bar 对象的 [[Prototype]] 关联到了 foo,所以 bar 对象本身没有 v 这个属性,也可以通过 [[Prototype]] 链查找到。但是,如果 foo 对象上也找不到 v 属性,那么会沿着 [[Prototype]] 链继续查找匹配的属性,直到查找完整条原型链。

constructor

每个原型默认有一个公有且不可枚举的 constructor 属性,这个属性引用的是对象关联的函数。

function Foo (name) {
    this.name = name
}

Foo.prototype.constructor === Foo // true

注意

如果你创建了一个新对象并替换了函数默认的 prototype 对象引用,那么新对象并不会自动获得 constructor 属性。

举个例子:

function Foo (name) {
    this.name = name
}

Foo.prototype = {
    // ...
}

var bar = new Foo()

bar.constructor === Foo // false
bar.constructor === Object // true

分析:对象 bar 本身并没有 constructor 属性,所以它会委托 [[prototype]] 链上的 Foo.prototype 对象,但是这个对象由于被改写了,导致没有默认的 constructor 属性,最后会委托给原型链顶端的 Object.prototype。这个对象有 constructor 属性,指向内置的 Object() 函数。

因此它们的关系图如下:

JS原型

其中 [[prototype]] 关联的对象就是原型链。

结语

以上内容是参考《你不知道的JavaScript》中,关于原型解析所整理出来的总结。