介绍 JavaScript 原型
JavaScript 引擎默认提供了 Object() 构造函数和一个可以被 Object.prototype 引用到的匿名对象。1
2console.log(Object);
console.log(Object.prototype);
Object.prototype 对象有许多内置属性,例如 toString() ,valueOf()等等。也有一个名为 constructor 的属性,它指向 Object() 构造函数。1
console.log(Object.prototype.constructor === Object); // true
假设原型代表一个函数,方块代表一个对象。下面的图形就描绘出了 Object() 函数和 Object.prototype 对象之间的关系:
首先,我们定义一个名为 Foo 的函数如下:1
2
3function Foo(name) {
this.name = name;
}
Foo() 函数接收一个参数,添加 name 属性到对象中,并设置 name 属性的值为传入的参数。
在这个情景的背后,JavaScript 引擎创建了一个函数 Foo() 和一个匿名对象。这个 Foo() 函数有一个名为 prototype 的属性指向这个匿名对象。这个匿名对象也有一个 constructor 属性指向这个 Foo() 函数。
另外,Foo.prototype 对象通过 [[Prototype]]
连接到 Object.prototype ,这就是我们知道的原型链接。原型链接由下图中的 [[Prototype]]
表示。
第二步,添加一个名为 whoAmI() 的方法到这个 Foo.prototype 对象。1
2
3Foo.prototype.whoAmI = function() {
return "I am " + this.name;
}
第三步,创建一个 Foo 对象的实例。1
var a = new Foo('a');
JavaScript 引擎在内部创建了一个新的对象,名为 a ,并通过原型链接将 a 对象连接到了 Foo.prototype 对象。
对象 a,Foo.prototype,和 Object.prototype 之间的连接就叫作原型链。
第四步,创建 Foo 对象的另一个实例 b。1
var b = new Foo('b');
第步,在 b 对象中添加一个 say() 方法。1
2
3b.say = function() {
console.log('Hi from ' + this.whoAmI());
}
JavaScript 引擎在 b 对象中添加 say() 方法,而不是 Foo.prototype 对象。
现在看看下面的代码。1
console.log(a.constructor); // Foo
对象 a 没有 constructor 属性,所以 JavaScript 引擎会沿原型链向上查找。因为 a 对象通过原型链接连接到了 Foo.prototype, 并且 Foo.prototype 有 constructor 属性,JavaScript 引擎会返回 Foo。所以下面的 表达式结果为 true。1
console.log(a.constructor === Foo); // true
获取原型链接
proto 是 Object.prototype 对象的一个访问器属性。它暴露了一个对象内部的原型链接([[Prototype]]
),同时也是通过原型链接被访问的。
proto 在 ES6 中被标准化,为了浏览器的兼容性。然而未来可能会被废除以支持 Object.getPrototypeOf() 。所以,最好不要在生产代码中使用 proto。
你可以在前面的图标中看到,a.proto 暴露了指向 Foo.prototype 的 [[Prototype]]
。相似的,b.proto 也指向了和 a.proto 相同的对象:1
2console.log(a.proto === Foo.prototype); // true
console.log(a.proto === b.proto); // true
前面提到过,你应该使用 Object.getPrototypeOf() 方法而不是 proto 。Object.getPrototypeOf() 方法返回一个指定对象的原型。1
console.log(a.proto === Object.getPrototypeOf(a)); // true
在 Object.getPrototypeOf() 方法还不能用的时候,程序员经常使用的另一种获得原型链接的方法是通过 constructor 属性,如下:1
a.constructor.prototype
a.constructor 返回 Foo,所以 a.constructor.prototype返回原型对象。
影子
看看下面的方法调用。1
console.log(a.whoAmI()); // I am a
a 对象没有 whoAmI() 方法,所以当 这个方法被从 a 调用时, JavaScript 引擎会沿着原型链向上查找,直到找到它。这样一来,就在 Foo.prototype 对象中发现并执行了这个调用。
我们在 a 对象中添加一个新的方法,和 Foo.prototype 对象中的方法名字相同。1
2
3a.whoAmI = function() {
console.log('This is ' + this.name);
}
然后调用这个 whoAmI 方法:1
console.log(a.whoAmI()); // This is a
因为我们在 a 对象中有 whoAmI() 方法了, JavaScript 引擎不会在原型链中查找,而是立即执行调用。
这是一个关于 影子 的示例。a 对象的 whoAmI() 方法隐藏了 a 连接到的原型对象上的 whoAmI() 方法。
现在你就理解了关于 JavaScript 原型的所有重要概念,包括 原型链,原型链接,proto,以及影子。