簡體   English   中英

Javascript原型繼承和對象屬性陰影

[英]Javascript Prototypal Inheritance & object properties shadowing

var person = { name :"dummy", personal_details: { age : 22, country : "USA" } };

var bob = Object.create(person);

bob.name = "bob";
bob.personal_details.age = 23;


console.log(bob.personal_details === person.personal_details);
// true : since it does not shadow object of prototype object

console.log(bob.name  === person.name);
// false : since it shadows name

////now
bob.personal_details  = {a:1};
console.log(bob.personal_details === person.personal_details); 

//假

當對象Bob試圖重寫的 “name”屬性的話,它本身鮑勃被遮蔽。

但是在personal_details的情況下,將違反相同規則。

我很想知道為什么會這樣?

這是jsbin的鏈接: http ://jsbin.com/asuzev/1/edit

下面的例子可以很容易地說明發生了什么:

通過原型鏈訪問personal_details

var person = { name :"dummy", personal_details: { age : 22, country : "USA" } }
var bob = Object.create(person)

bob.name = "bob"
bob.personal_details.age = 23

輸出如下:

console.log( bob );
/// { name :"bob", personal_details: { age : 23, country : "USA" } }

console.log( person )
/// { name :"dummy", personal_details: { age : 23, country : "USA" } }

現在在person對象上設置了23歲, because bob.personal_details是通過bob的原型鏈直接引用person.personal_details的。 導航到對象結構后,就可以直接使用person.personal_details對象。


用本地屬性覆蓋原型屬性

但是,如果您用另一個對象覆蓋bob的personal_details屬性,則該原型鏈接將被更本地的屬性覆蓋。

bob.personal_details = { a: 123 }

現在的輸出是:

console.log( bob );
/// { name :"bob", personal_details: { a : 123 } }

console.log( person )
/// { name :"dummy", personal_details: { age : 23, country : "USA" } }

因此,從現在開始訪問bob.personal_details ,您將引用{ a: 123 }對象,而不是原始的{ age : 23, country : "USA" }對象。 所做的所有更改都將在該對象上發生,並且基本上與bobperson對象無關。


原型鏈

為了使事情變得有趣,完成以上所有操作后,您認為會發生什么:

delete bob.personal_details

您最終恢復了到person.personal_details的原始原型鏈接(因為您已刪除添加的本地屬性),因此控制台日志將顯示:

console.log( bob );
/// { name :"bob", personal_details: { age : 23, country : "USA" } }

基本上,JavaScript引擎將沿着原型鏈進行工作,直到找到每個原型對象上要請求的屬性或方法為止。 設置項的鏈越靠上,這意味着它將在以后向下覆蓋其他項。

現在再問一個問題,如果再次觸發以下命令會發生什么?

delete bob.personal_details

什么也沒有,不再給bob分配一個名為personal_details的實際屬性, delete將僅在當前對象上起作用,而不會遵循原型鏈。


一種不同的看待方式

觀察原型鏈如何工作的另一種方法基本上是想象一堆對象。 當JavaScript掃描特定的屬性或方法時,它將通過以下結構向下讀取:

bob :          {  }
  person :     { name: 'dummy', personal_details: { age: 22 } }
    Object :   { toString: function(){ return '[Object object]'; } }

例如,假設我要訪問bob.toString toString是存在於JavaScript基礎對象Object上的方法,該Object是幾乎所有內容的基礎原型。 當解釋器獲得對對象上特定方法或屬性的讀取請求時,它將遵循以下事件鏈:

  1. bob是否有一個名為toString的屬性? 沒有。
  2. bob.__proto__person是否具有稱為toString的屬性? 沒有。
  3. bob.__proto__.__proto__Object是否具有稱為toString的屬性?
  4. 返回對function(){ return '[Object object]'; }的引用function(){ return '[Object object]'; } function(){ return '[Object object]'; }

一旦到達點4,解釋器將返回對Object上toString方法的引用。 如果未在Object上找到該屬性,則很可能引發了未定義屬性的錯誤(因為它是鏈中的最后一個)。

現在,如果我們以前面的示例為例,這次在bob上定義一個toString方法-因此:

bob :          { toString: function(){ return '[Bob]'; } }
  person :     { name: 'dummy', personal_details: { age: 22 } }
    Object :   { toString: function(){ return '[Object object]'; } }

如果我們嘗試再次讀取bob的toString方法,這一次我們得到:

  1. bob是否有一個名為toString的屬性? 是。

該過程在第一個障礙處停止,並從bob返回toString方法。 這意味着bob.toString()將返回[Bob]而不是[Object object]

正如Phant0m簡潔指出的那樣,對對象的寫入請求將遵循不同的路徑,並且永遠不會沿原型鏈傳播。 理解這一點是要弄清什么是讀請求和什么是寫請求之間的區別。

bob.toString                   --- is a read request
bob.toString = function(){}    --- is a write request
bob.personal_details           --- is a read request
bob.personal_details = {}      --- is a write request
bob.personal_details.age = 123 --- is a read request, then a write request.

最后一項是引起混亂的一項。 該過程將遵循以下路線:

  1. bob是否有一個名為personal_details的屬性? 沒有。
  2. person是否擁有一個名為personal_details的財產? 是。
  3. 返回對{ age: 22 }的引用,該引用以浮動形式存儲在內存中。

現在開始新的過程,因為對象導航或分配的每個部分都是對屬性或方法的新請求。 因此,現在我們有一個personal_details對象,我們可以切換到寫請求,因為左側的=等於的屬性或變量始終是賦值。

  1. 將值123寫入對象{ age: 22 }的屬性age

因此原始請求可以看作是這樣的:

(bob.personal_details)            --- read
    (personal_details.age = 123)  --- write

如果bob擁有自己的personal_details財產,則過程將相同,但寫入的目標對象將不同。


最后...

提出您的問題:

很難理解原型對象上的屬性被視為READ_ONLY,但是如果屬性是一個對象,那么人們就可以擁有它並可以自由地修改其屬性! 我的理解正確嗎?

原型屬性看似只讀,但僅當作為繼承它們的對象的屬性直接訪問時才使用-因為這些屬性實際上根本不在繼承對象上存在。 如果您向下瀏覽到原型對象本身,則可以將其視為任何普通對象(具有讀寫功能),因為它就是所謂的普通對象。 最初可能會造成混淆,但這是性質或原型繼承,而這完全取決於您如何訪問要使用的屬性。

在第二種情況下,您沒有分配給bob屬性,那么如何覆蓋它呢?

如果bob.name = "bob" ,則綁定bob 自己的 name屬性。

在第二種情況下,您沒有 您可以通過原型訪問bob的personal_details屬性。 然后,您將其分配給該對象的屬性,到那時bob所有連接都將丟失。

這樣想:

bob.personal_details.age = 23;
// equivalent
expr = bob.personal_details;
expr.age = 23; // As you can see, there's no assignment to bob

它不違反任何規則,因為情況完全不同。

我希望下面的圖表足夠清楚,但是我將嘗試簡要解釋發生了什么。

使用Object.create創建新對象時,將使用原型create方法的第一個參數創建對象。 因此,您將創建帶有原型的bob ,該原型指向person對象。 通過引用bob的原型,可以訪問person所有屬性。

在下一張圖片中,您更改bob的名稱。 現在是bob因此只需創建名稱為namebobslotproperty (現在解釋器在查找屬性name時不會檢查原型鏈,它將直接發現bob具有此類屬性)。

在第三個中,您更改了影響到person.personal_details.age bob.personal_details.age ,因為這是同一對象。

最后,您設置了屬性personal_details ,現在bob具有slot personal_details ,它不是原型屬性,而是對另一個對象(匿名對象)的引用。

在此處輸入圖片說明

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM