2013-09-27 72 views
0

我一直在試驗原型繼承,如下面的代碼片段所示。在父級原型上設置屬性

function extend(c, p) { 
    function f() { this.constructor = c; } 
    f.prototype = p.prototype; 
    c.prototype = new f(); 
} 

function Parent() {} 
function Child() {} 

extend(Child, Parent); 

var p = new Parent(); 
var c = new Child(); 

// Child.prototype.say = function() { alert("child"); }; 
Parent.prototype.say = function() { alert("parent"); }; 

p.say(); 
c.say(); 

運行此腳本時,會顯示兩條警報顯示parent

但是,如果我取消對註釋行的註釋,則第一個警報顯示parent,而第二個顯示child
乍一看,這是非常意外的。看起來Parent.say覆蓋Child.say,因爲它稍後設置。

從我的理解,因爲Child.prototype是一個對象,而f一個實例,在這個原型設置的所有屬性上直接設置的f該特定實例的實例。

當調用c.say,將發生以下情況:

  1. 如果say直接設置c,調用它。它不會直接在實例上設置,因此,請跳至2.

  2. Children.prototype中查找sayf的實例。再次查找直接在f實例上設置的屬性。如果該行是未註釋,則在此處找到say,並停止搜索。

  3. 查找sayf.prototype,這是Parent.prototype。這是發現say的地方,當時線路仍然爲評論了

問:我才明白正確的JavaScript如何查找財產?如果是這種情況,那麼可以解釋爲什麼子行不是,如果該行未被註釋,則由父屬性重寫

回答

1

我是否正確理解JavaScript如何查找屬性?

基本上,是的。但重要的是要注意,對象的基礎原型由new操作設置爲指向由構造函數的prototype屬性引用的對象,此時如果您將構造函數的prototype屬性指向完全不同的對象,則它不會「對已有的孩子沒有任何影響。孩子們是指物體,而不是物體。

所以一般來說,財產查找工作原理是這樣:

  • 是否屬性的對象本身存在嗎?如果是這樣,請使用它的值。
  • 不,該屬性是否存在於該對象的基礎原型上?如果是這樣,請使用它的值。
  • 它的原型的原型上是否存在?如果是這樣,請使用該值。
  • 依此類推,直到我們找到屬性,或用完原型。

讓我們把一些ASCII藝術在你構建有什麼,只是爲了好玩:

+-----------+ 
| Parent |<---------------------------+ 
+-----------+       | 
| prototype |---------->+-------------+ | 
+-----------+  +--->| (object) | | 
        | +->+-------------+ | 
        | | | constructor |--+  +------------------+ 
        | | | say   |--------->| (function) | 
        | | +-------------+   +------------------+ 
        | |       | alert("parent"); | 
        | |       +------------------+ 
        | | 
        | +--------------------------------------------------+ 
        |             | 
        +-----------------------+       | 
+-----------+        |       | 
| Child  |<---------------------------+ |       | 
+-----------+   +-------------+ | |       | 
| prototype |------+--->| (object) | | |       | 
+-----------+  | +-------------+ | |       | 
        | | constructor |--+ |       | 
        | | __proto__ |----+  +------------------+ | 
        | | say   |--------->| (function) | | 
        | +-------------+   +------------------+ | 
        |        | alert("child"); | | 
+-----------+  |        +------------------+ | 
|  c  |  |             | 
+-----------+  |             | 
| __proto__ |------+             | 
+-----------+               | 
                     | 
+-----------+               | 
|  p  |               | 
+-----------+               | 
| __proto__ |-----------------------------------------------------------+ 
+-----------+

...其中__proto__是表示對象到它的原型鏈接的隱藏屬性。對於Parent.prototype(有些發動機實際上暴露這一點,並有將它添加到標準的建議。)

正如你所看到的,Child.prototypec實例的__proto__都指向同一個對象(類似與p__proto__ )。

我之所以讓我在上面第一款的區別是這樣的:

function Foo() { 
} 
Foo.prototype.a = 1; 
var f1 = new Foo(); 
Foo.prototype = {b: 2}; // Putting an entirely new object on `prototype`, not just modifying it 
var f2 = new Foo(); 
console.log(f1.a); // "1" 
console.log(f1.b); // "undefined" 
console.log(f2.a); // "undefined" 
console.log(f2.b); // "2" 

f1f2最終不得不完全不同的原型,並通過上述的結束,f1__proto__不再指到同一個對象Foo.prototype是指。

出於這個原因,除非是非常特殊的情況下(如您的extend功能),我強烈建議分配新對象的構造函數的prototype屬性,因爲它可以變得很混亂。 :-)你的extend函數也是一個例外。

+0

有道理。現有實例受到影響的原因是實例的原型和構造函數的原型仍然保持相同的對象。 – afsantos

+0

@afsantos:對,它們都指向同一個對象,因此通過這兩個引用向該對象添加屬性。 –

+0

非常棒的答案!對於ASCII藝術的獎勵點,應該有一個工具來自動說明這些事情。 – afsantos