乐趣区

JavaScript继承背后的场景prototypeproto-prototype

prototype 是构造函数的一个属性,它决定了在构造出来的对象上__proto__属性将是什么样的。


如上图所示,理解 JavaScript 中的继承的关键是要理解母鸡如何产蛋的过程。

[[prototype]]、__proto__、prototype 三者之间的联系

每个对象都可以有另一个对象作为其原型。然后前一个对象继承了它的所有原型属性。对象通过内部属性 [[Prototype]] 指定其原型。由 [[Prototype]] 属性连接的对象链称为原型链。


为了理解基于原型的继承是怎么样的,我们先来看一个例子。

var proto = {describe: function () {return 'name:'+this.name;}
};
var obj = {[[Prototype]]: proto,
    name: 'obj'
};

> obj.describe
[Function]

> obj.describe()
'name: obj'

__proto__是 Object.prototype 对象的访问者属性。它暴露了访问它的对象的内部原型([[Prototype]])

function Foo(name) {
    this.name = name
    this.whoAmI = function () {return this.name}
}
var b = new Foo('b')
var a = new Foo('a')
b.say = function () {console.log(`Hi from ${this.whoAmI()}`)
}
console.log(a.__proto__ === Foo.prototype); // true
console.log(a.__proto__ === b.__proto__); // true


JavaScript 引擎在对象 b 上添加了一个 say 方法,而不是在 Foo 原型对象 (Foo.prototype) 上

正如上图中看到的那样,a.__ proto__暴露了指向 Foo.prototype 对象的[[Prototype]]。同样,b.__ proto__也指向与 a.__ proto__相同的对象。

通过构造函数来创建对象

除了通过指定模式创建对象之外,构造函数还可以执行另一个有用的操作 – 它会自动为新创建的对象设置原型对象。此原型对象存储在构造函数的原型对象属性中。

我们可以使用构造函数用 b 对象重写前面的例子。因此,对象 a(原型)Foo.prototype 的作用:

创建拥有原型对象属性 x 及方法 calculate() 的一个 Foo 对象。

function Foo(y) {this.y = y;}
Foo.prototype.x = 10;
Foo.prototype.calculate = function (z) {return this.x + this.y + z;};

使用对象 Foo 创建一个 b 对象实例。

var b = new Foo(20);
b.calculate(30); // 60
console.log(
  b.__proto__ === Foo.prototype, // true
  b.__proto__.calculate === Foo.prototype.calculate // true
  b.__proto__.calculate === b.calculate, // true
  Foo === b.constructor, // true
  Foo === Foo.prototype.constructor, // true
);

正如上面打印出来的信息,对象 b 从 Foo()继承了方法 calculate。“Foo.prototype”自动创建一个特殊属性“constructor”,它是对构造函数本身的引用。实例 b 可以通过代理来找到它,并用来检测其构造函数。

JavaScript 经典继承图

这也是通过构造函数来创建对象,但是在这一系列的对象和实例之间我们的焦点是放在原型链上。原型对象其实也是普通的对象,也有属于它们自己的属性。如果原型具有对其原型的非空引用,依此类推,则称为原型链。

以下是 JavaScript 经典继承的图表。构造函数 Foo 只是虚构类的类名。foo 对象是 Foo 的一个实例。

现在我们可以从图中看到为什么当我们从 Dog 类继承 Animal 时,我们会这样做:

function Dog() {} // the usual constructor function
Dog.prototype = new Animal();
Dog.prototype.constructor = Dog;

当通过 new 操作符创建一个实例的时候,都发生了些什么:

注意 Foo.prototype 的原型 (prototype) 并不是来自原型链。

退出移动版