[[Prototype]]
JavaScript 中的对象有一个内部属性,在语言规范中称为 [[Prototype]],它只是一个其他对象的引用。几乎所有的对象在被创建时,它的这个属性都被赋予了一个非 null 值
var anotherObject = {a: 2};// 创建一个链接到 `anotherObject` 的对象var myObject = Object.create( anotherObject );myObject.a; // 2
现在让 myObject [[Prototype]] 链到了 anotherObject。虽然很明显 myObject.a 实际上不存在,但是无论如何属性访问成功了(在 anotherObject 中找到了),而且确实找到了值 2。
Object.prototype
每个 普通 的 [[Prototype]] 链的最顶端,是内建的 Object.prototype。这个对象包含各种在整个 JS 中被使用的共通工具,因为 JavaScript 中所有普通(内建,而非被宿主环境扩展的)的对象都“衍生自”Object.prototype 对象
设置与遮蔽属性
myObject.foo = "bar";
以下来 考察 myObject.foo = "bar" 赋值的三种场景,当 foo 不直接存在 于 myObject,但 存在 于 myObject 的 [[Prototype]] 链的更高层时:
- 如果一个普通的名为
foo的数据访问属性在[[Prototype]]链的高层某处被找到,而且没有被标记为只读(writable:false),那么一个名为foo的新属性(也就是代表新添加)就直接添加到myObject上,形成一个 遮蔽属性。 - 如果一个
foo在[[Prototype]]链的高层某处被找到,但是它被标记为 只读(writable:false) ,那么设置既存属性和在myObject上创建遮蔽属性都是 不允许 的。如果代码运行在strict mode下,一个错误会被抛出。否则,这个设置属性值的操作会被无声地忽略。不论怎样,没有发生遮蔽。 - 如果一个
foo在[[Prototype]]链的高层某处被找到,而且它是一个 setter(也就是set 属性名()的特殊函数),那么这个 setter 总是被调用。没有foo会被添加到(也就是遮蔽在)myObject上,这个foosetter 也不会被重定义。
案例如下:
var anotherObject = {a: 2};var myObject = Object.create( anotherObject );anotherObject.a; // 2myObject.a; // 2anotherObject.hasOwnProperty( "a" ); // truemyObject.hasOwnProperty( "a" ); // falsemyObject.a++; // 这里就产生了隐式的遮蔽,为myObject产生了一个新的属性 并且赋值++anotherObject.a; // 2myObject.a; // 3myObject.hasOwnProperty( "a" ); // true
“类”
在所有语言中,JavaScript 几乎是独一无二的,也许是唯一的可以被称为“面向对象”的语言,因为可以根本没有类而直接创建对象的语言很少,而 JavaScript 就是其中之一。JavaScript 只有 对象
function Foo() {// ...}var a = new Foo();Object.getPrototypeOf( a ) === Foo.prototype; // true
当通过调用 new Foo() 创建 a 时,会发生的事情之一是,a 得到一个内部 [[Prototype]] 链接,此链接链到 Foo.prototype 所指向的对象。
在 JavaScript 中,我们不从一个对象(“类”)向另一个对象(“实例”) 拷贝。我们在对象之间制造 链接。对于 [[Prototype]] 机制,视觉上,箭头的移动方向是从右至左,由下至上。

这种机制常被称为“原型继承(prototypal inheritance)”
“继承”意味着 拷贝 操作,而 JavaScript 不拷贝对象属性(原生上,默认地)。相反,JS 在两个对象间建立链接,一个对象实质上可以将对属性/函数的访问 委托 到另一个对象上。对于描述 JavaScript 对象链接机制来说,“委托”是一个准确得多的术语
“构造器”(Constructors)
function Foo() {// ...}Foo.prototype.constructor === Foo; // truevar a = new Foo();a.constructor === Foo; // true
Foo.prototype 对象默认地得到一个公有的,称为 .constructor的不可枚举属性,而且这个属性回头指向这个对象关联的函数(这里是 Foo)。另外,我们看到被“构造器”调用 new Foo() 创建的对象 a 看起来 也拥有一个称为 .constructor 的属性,也相似地指向“创建它的函数”
Foo 不会比你的程序中的其他任何函数“更像构造器”。函数自身 不是 构造器。但是,当你在普通函数调用前面放一个 new 关键字时,这就将函数调用变成了“构造器调用”。事实上,new 在某种意义上劫持了普通函数并将它以另一种方式调用:构建一个对象,外加这个函数要做的其他任何事(也就是执行函数体)
!!!“构造器”是在前面 用 new 关键字调用的任何函数~!
函数不是构造器,但是当且仅当 new 被使用时,函数调用是一个“构造器调用”。
原型继承
有这样一段原型继承示例代码:
function Foo(name) {this.name = name; //注意这里的this代表是什么?? -->a : new对象会使得this绑定到新的对象上}Foo.prototype.myName = function() {return this.name;};function Bar(name,label) {Foo.call( this, name );this.label = label;}// 这里,我们创建一个新的 `Bar.prototype` 链接链到 `Foo.prototype`// 而Bar.prototype = Foo.prototype 不会创建新对象让 Bar.prototype 链接// 它只是让 Bar.prototype 成为 Foo.prototype 的另一个引用,Bar.prototype = Object.create( Foo.prototype );// 如果你有依赖这个属性的习惯的话,它可以被手动“修复”。Bar.prototype.myLabel = function() {return this.label;};var a = new Bar( "a", "obj a" );a.myName(); // "a"a.myLabel(); // "obj a"
比较一下 ES6 之前和 ES6 标准的技术如何处理将 Bar.prototype 链接至 Foo.prototype:
// ES6 以前// 扔掉默认既存的 `Bar.prototype`Bar.prototype = Object.create( Foo.prototype );// ES6+// 修改既存的 `Bar.prototype`Object.setPrototypeOf( Bar.prototype, Foo.prototype );
考察“类”关系
如果你有一个对象 a 并且希望找到它委托至哪个对象呢(如果有的话)?考察一个实例(一个 JS 对象)的继承血统(在 JS 中是委托链接)
function Foo() {
// ...
}
Foo.prototype.blah = ...;
var a = new Foo();
我们如何自省 a 来找到它的“祖先”(委托链)呢?
a instanceof Foo; // true
instanceof 操作符的左侧操作数接收一个普通对象,右侧操作数接收一个 函数。instanceof 表达的意思是:在 a 的整个 [[Prototype]] 链中,有没有出现那个被 Foo.prototype 所随便指向的对象
我们 只需要 两个 对象** 来考察它们之间的关系。比如:
// 简单地:`b` 在 `c` 的 `[[Prototype]]` 链中出现过吗?
b.isPrototypeOf( c );
注意,这种方法根本不要求有一个函数(“类”)。它仅仅使用对象的直接引用 b 和 c,来查询他们的关系。换句话说,我们上面的 isRelatedTo(..) 工具是内建在语言中的,它的名字叫 isPrototypeOf(..)
.__proto__ 虽然看起来像一个属性,但实际上将它看做是一个 getter/setter(见第三章)更合适。
大致地,我们可以这样描述 .__proto__ 的实现(见第三章,对象属性的定义):
Object.defineProperty( Object.prototype, "__proto__", {
get: function() {
return Object.getPrototypeOf( this );
},
set: function(o) {
// ES6 的 setPrototypeOf(..)
Object.setPrototypeOf( this, o );
return o;
}
} );
**__proto__** 读作“dunder proto”
创建链接
var foo = {
something: function() {
console.log( "Tell me something good..." );
}
};
var bar = Object.create( foo );
bar.something(); // Tell me something good...
Object.create(..) 创建了一个链接到我们指定的对象(foo)上的新对象(bar),这给了我们 [[Prototype]] 机制的所有力量(委托),而且没有 new 函数作为类和构造器调用产生的所有没必要的复杂性,搞乱 .prototype 和 .constructor 引用,或任何其他的多余的东西
注意: Object.create(null) 创建一个拥有空(也就是 null)[[Prototype]] 链接的对象,如此这个对象不能委托到任何地方。因为这样的对象没有原形链,instancof 操作符(前 面解释过)没有东西可检查,所以它总返回 false。由于他们典型的用途是在属性中存储数据,这种特殊的空 [[Prototype]] 对象经常被称为“字典(dictionaries)”,这主要是因为它们不可能受到在 [[Prototype]] 链上任何委托属性/函数的影响,所以它们是纯粹的扁平数据存储
在 myObject 上没有 cool() 方法时调用 myObject.cool()也能工作的一个比较好的方案就是采用原型链接的方式:
var anotherObject = {
cool: function() {
console.log( "cool!" );
}
};
//创建对象myObject链接到对象anotherObject
var myObject = Object.create( anotherObject );
myObject.doCool = function() {
this.cool(); // internal delegation!
};
myObject.doCool(); // "cool!"
这里,我们调用 myObject.doCool(),它是一个 实际存在于 myObject 上的方法,这使我们的 API 设计更清晰(没那么“魔性”)。在它内部,我们的实现依照 委托设计模式,利用 [[Prototype]] 委托到 anotherObject.cool()。
虽然这些 JavaScript 机制看起来和传统面向类语言的“初始化类”和“类继承”类似,而在 JavaScript 中的关键区别是,没有拷贝发生。取而代之的是对象最终通过 [[Prototype]] 链链接在一起。
