qu上章提到过[[prototype]] chain, 本章详细分析
⚠️所有试图模仿类复制的行为,如上章提到的mixins的变种,完全规避了[[Prototype]] chain机制,本章会谈到这方面
[[Prototype]]
一个特别的内建属性。用于引用到其他对象。当对象创建后,会得到一个这个属性的非空值。
例子
var anotherObject = { a: 2 }; // create an object linked to `anotherObject` var myObject = Object.create( anotherObject ); myObject.a; // 2
如果请求的属性没有在myObject上直接找到,默认的[[Get]]操作会沿原型链的连接去找。
点击查看:([[Get]]内容)
Object.create(..)此时可以简单理解为它创建一个对象,并用[[Prototype]] 关联的我们指定的对象上。
- 如果没有在anotherOject查找到对应属性名字,会继续沿着prototype查找,直到找到。
- 如果prototype chain结束后仍没有找到,则返回 undefined
类似[[Prototype]] chain的查询方式,
如果你使用for.. in循环一个对象,任何通过它的原型链条被查到的属性都会进行相关运算(enumerable:true才会被循环侦测到)。
for (var k in myObject) { console.log("found: " + k); } // found: a
如果你使用in操作符号来测试一个属性是否存在于一个对象, in会检查整个原型链条(不考虑enumerable)
"a" in myObject // true
Object.prototype
这是一个原型链的最后端点! 内建的Object.prototype!
这个对象包括了大量普通的程序utilities用于全部JS。
所有正常的对象的原型链的终端都是它Object!
一些utilities,如.toString(), .valueOf(), hasOwnPreperty(..), 之后还会用到.isPrototypeOf()
anotherObject.isPrototypeOf(myObject) // true , 用于查看一个对象是否是另一个个对象的原型(这个对象在另一个对象的正上面)
Setting & Shadowing Properties(避免避免避免!!1)
第3章提到了属性的设置更复杂,默认调用[[Put]]操作,有一系列的步骤。(点击)
myObject.foo = "bar";
首先判断myObject自身是否有foo属性,
- 有一个普通的data accessor 属性,就和改变已经存在的属性的值一样简单。
- 如果myObject自身没有这个foo属性,则先查看原型链条,
- 如果没有找到,则为myObject新增这个属性/值
- 如果在prototype chain找到foo,myObject.foo = "bar"这个声明行为有一些区别,见?。
如果这个属性名字,在自身和原型链条上同时有,叫做shadowing。(比较复杂)
- 如果一个普通的data accessor属性名字叫foo,在原型链条上存在,并且它没有被标记为只读(writable:false), 那么一个新的属性叫做foo会被直接地增加到myObject对象上,作为一个影子属性。
- 如果原型链条上的foo是只读的。在严格模式下,扔出❌,非严格模式,这个setting会被忽略,没有shadowing发生!
- 如果一个foo在原型链条上存在,并且它是一个setter,这个setter会被调用!foo不会添加到myObject上,foo setter也不会被重新定义。
只有第一种才能使用 = 分配符号,定义一个影子属性。
第2,3种情况, 不会发生任何改变,有一个后门,使用Object.defineProperty(..)。
第2类比较特殊,在原型链上设置为只读的属性,在它的低层链条上就不能再有shadow 属性。(这种方式主要是模仿类的继承)
影子方法导致丑陋的explicit pseudo-polymorphism。避免使用它!
这个例子:展示了,如果不小心的使用计算符号,也会导致影子属性的出现,应当避免!!!
var anotherObject = { a: 2 }; var myObject = Object.create( anotherObject ); anotherObject.a; // 2 myObject.a; // 2 anotherObject.hasOwnProperty( "a" ); // true myObject.hasOwnProperty( "a" ); // false myObject.a++; // oops, implicit shadowing! anotherObject.a; // 2 myObject.a; // 3 myObject.hasOwnProperty( "a" ); // true
解释:
这相当于myObject.a = myObject.a + 1
即先发生[[Get]]得到值2,然后发生[[Put]]分配3给一个影子属性!
"Class"
为什么一个对象需要连接到另外一个对象?有什么好处?
a good question!
但在此之前你必须首先理解[[Prototype]] 不是什么 (不是复制,而是delegation授权)
然后你就能完全的理解并欣赏原型链是什么,并且用途是多么大!
Chapter4已经解释,JS没有类的抽象模式/蓝图给对象。 JS只有对象。
事实上, object-oriented这个词组完全适用JavaScript语言,其他语言都不能这么讲!
因为,JS是在所有语言中及少的,一个对象可以被指向创建,而无需一个类的语言!
JS中,类不能描述一个对象能够做什么(因为类不存在)。 对象直接地定义自己的行为!。
这里只有对象!
"Class" Functions
JS中,有一些奇怪的行为,看起来像是类。这里谈这些细节:
function Foo() { // ... } var a = new Foo(); Object.getPrototypeOf( a ) === Foo.prototype; // true
?变量a的原型竟然和函数Foo的原型是一个东西!
使用了new Foo(),(看第2章this)。
在oo语言。一个类可以发生多重复的复制。实例化一个类的意思就是:拷贝行为计划从类到物理的对象。
但是JS, 并没有拷贝行为的执行。你没有创建多个类的实例。你可以创建多个对象,它们是用[[Prototype]]连接到一个普通的对象。默认,没有复制发生,因此这些对象并不是完全地独立地互补相关地。它们是互相连接的。
因此new Foo()创建了一个新对象,这个新对象是一个内部的原型,连接到这个Foo.prototype对象。
我们没有复制,仅仅是把2个对象连接起来。
事实上,对大多数开发者来说的秘密,new Foo()函数调用并没有直接的创建连接的process!.这是一个side-effect。new Foo()通过非直接的方式,让我们得到一个新对象连接到另一个对象上。
我们能有更直接的方式吗?可以! 使用Object.create().
区别和new Foo()非常明显!!!
What's in a name?
原型链条继承机制?
在JS, 我们不进行从一个对象(类)到另一个对象(instance)的复制行为。
我们连接双方。
作者认为 prototypal inheritance 这个词对理解JavaScript的机制,坏处多于好处!
inheritance暗示了复制操作, 但JS没有复制对象功能。
代替地,JS在2个对象之间创建了一个连接!
一个对象可以delegate 属性/函数存取权利给另一个对象! (这样描述更准确!)
Deleagtion: 授权/委托:deleagte sth to sb
If you delegate duties, responsibilities, or power to someone, you give them those duties, those responsibilities, or that power so that they can act on your behalf.
"Constructors" 构造器 :
是什么导致我们认为Foo是类?
function Foo() { // ... } var a = new Foo(); //new Foo()一个构造器函数!!! (点击链接)
1. 看到了new关键字, 对象继承语言里的实例化如:Ruby中,a = String.new
2. 似乎在执行一个类中的constructor方法,因为Foo是方法被调用,就像当你实例化类时,一个真的类构建器被调用。
3.还有Foo首字母大写,也类似类的名字。
⚠️,JS engine,首字母大写,不意味任何事。
Constructor 还是 Call?
这些想法诱惑你认为Foo是一个Constructor,因为我们用new关键字调用它,我们观察它构建了是一个对象。
而事实上,Foo不是构建器。函数本身不是constructor!
当你把new放到一个普通的函数前面调用,这个行为让函数调用一个"constructor call".
事实是,new劫持了任何普通函数,并用某种方式调用这个函数构建一个对象。
Foo只是普通的函数,但是当我们call new关键字,new关键字constructs一个对象并分配给变量a。
这个调用Call, 就是一个构建器调用(a constructor call)。 Foo函数本身(in and of itself)不是构建器!
可以这么说: 如果函数前面有一个new关键字,则发生了一个构建器调用!
一个构建器是任何函数的调用前面有一个new关键字。
Mechanics (详细解释了JS开发者的天马行空的方法,来模拟类)
前面的说明就是 错误的关于JS的类的讨论吗?不完全是。 JS开发者一直努力模仿类。
"Constructor" Redux
Misconception, busted.
.constructor
是不可靠的, and an unsafe reference to rely upon in your code.
Generally, such references should be avoided where possible.
"(Prototypal) Inheritance"
上一节,看到了近似类的mechanics hack进JS程序。但还缺少一个近似继承的概念。
除了Foo.prototype中的函数可以授权给a1, a2使用。类似 类->实例。
还有类到类的继承, 这里使用:Bar.prototype被Foo.prototype授权!
function Foo(name) { this.name = name; } //在prototype对象中声明一个函数,用于授权给Bar Foo.prototype.myName = function() { return this.name; }; //声明Bar函数, 在执行这个函数时,name, label是新增对象的属性。
//其中,name属性使用Foo.call(this, name)函数被声明并赋值。 function Bar(name,label) { Foo.call( this, name ); this.label = label; } // 这里, 定义一个新的 `Bar.prototype`,链接到Foo.prototype Bar.prototype = Object.create( Foo.prototype );
// 在Bar的原型对象上声明一个函数,链接Bar函数的对象,可以使用它。 Bar.prototype.myLabel = function() { return this.label; };
//使用构造器创建一个实例! var a = new Bar( "a", "obj a" ); a.myName(); // "a" a.myLabel(); // "obj a"
对象a,声明了2个属性name,label,同时自动声明了一个_proto_属性,这个_proto_是一个对象,它是一个方向标,指向了Foo.protype,
我个人理解:它本身也存储了Bar.prototype中定义的方法。或者是指向了Bar.prototype中定义的方法。
当function Bar()被声明后,有一个.prototype连接到了默认的object对象。
我们想要的是Bar.prototype连接到Foo.prototype上面。
所以,使用Object.create()创建一个新的对象,分配给Bar.prototype。
这样就把Bar.prototype和Foo.prototype链接在一起了。
Bar.prototype = Object.create( Foo.prototype );
在ES6出现后,提供了标准的帮助方法,建立prototype的链接!
// pre-ES6 // throws away default existing `Bar.prototype` ,
// 改变了prototype识别符号的指向。指向一个新创建的对象。后期需要垃圾回收掉默认产生的对象。 Bar.prototype = Object.create( Foo.prototype ); // ES6+ // modifies existing `Bar.prototype` Object.setPrototypeOf( Bar.prototype, Foo.prototype );
假如在Bar.prototype = Object.create( Foo.prototype )前,已经声明了一个Bar的实例a,
那么在Bar.prototype被指向了一个新的对象后, 原来的实例a,并没有改变a._proto_没有发生同步改变。
好在ES6有了Object.setPrototypeof方法。
Inspecting "Class" Relationships
使用a instanceof Bar; // true
Object.getPrototypeOf( a );得到的就是 a._proto_的结果。
Object.getPrototypeOf(a) === a.__proto__
// true
理解_proto_
他看起来像一个属性,但是他实际更适合的想象它是一个类似 getter/setter:
这是想象的:
Object.defineProperty( Object.prototype, "__proto__", { get: function() { return Object.getPrototypeOf( this ); }, set: function(o) { // setPrototypeOf(..) as of ES6 Object.setPrototypeOf( this, o ); return o; } } );
Object Links
"prototype chain".
Create()
ing Links
var foo = { something: function() { console.log( "Tell me something good..." ); } }; var bar = Object.create( foo ); bar.something(); // Tell me something good...
Object.create(..)创建了一个新的对象bar链接到foo,并给了我们所有原型机制上的授权delegation,没有任何复制的new函数作为类和构建器调用。
我们无需类来创建两个对象直接的关系。 我们只关系对象连接在一起是为了授权!
Object.create(..)提供了这个连接并且没有class的干扰
Object.create(对象名,{新建的对象的属性的特性设置})
这是ES5中添加的功能。完整的案例:
var anotherObject = { a: 2 }; var myObject = Object.create( anotherObject, { b: { enumerable: false, writable: true, configurable: false, value: 3 }, c: { enumerable: true, writable: false, configurable: false, value: 4 } } ); myObject.hasOwnProperty( "a" ); // false myObject.hasOwnProperty( "b" ); // true myObject.hasOwnProperty( "c" ); // true myObject.a; // 2 myObject.b; // 3 myObject.c; // 4
Links As Fallbacks?
认为对象之间的连接,当没有找到属性/方法时,应当提供一个回调。
这是一个可以观察到的输出,但作者认为这不是正确理解[[Prototype]]的方式。
作者建议使用ES6中的Proxy方法提供的类似"method not found"
没有仔细看,我的理解是一个对象不直接调用原型链条上的方法,而是定义自身的方法,自身的方法内使用原型链条上的方法。
Review (TL;DR)