返回課程

為什麼兩隻倉鼠都吃飽了?

重要性:5

我們有兩隻倉鼠:speedylazy,繼承自一般的 hamster 物件。

當我們餵其中一隻時,另一隻也吃飽了。為什麼?我們要如何解決這個問題?

let hamster = {
  stomach: [],

  eat(food) {
    this.stomach.push(food);
  }
};

let speedy = {
  __proto__: hamster
};

let lazy = {
  __proto__: hamster
};

// This one found the food
speedy.eat("apple");
alert( speedy.stomach ); // apple

// This one also has it, why? fix please.
alert( lazy.stomach ); // apple

讓我們仔細看看 speedy.eat("apple") 呼叫中發生了什麼事。

  1. 方法 speedy.eat 在原型(=hamster)中找到,然後以 this=speedy(點之前的物件)執行。

  2. 然後 this.stomach.push() 需要找到 stomach 屬性並在其上呼叫 push。它在 this=speedy)中尋找 stomach,但什麼也沒找到。

  3. 接著,它會沿著原型鏈找到 hamster 中的 stomach

  4. 然後,它會呼叫 push,將食物加入 原型中的 stomach

因此,所有倉鼠共用一個 stomach!

對於 lazy.stomach.push(...)speedy.stomach.push(),屬性 stomach 都會在原型中找到(因為它不在物件本身),然後將新資料推入其中。

請注意,在簡單的指定 this.stomach= 時不會發生這種情況。

let hamster = {
  stomach: [],

  eat(food) {
    // assign to this.stomach instead of this.stomach.push
    this.stomach = [food];
  }
};

let speedy = {
   __proto__: hamster
};

let lazy = {
  __proto__: hamster
};

// Speedy one found the food
speedy.eat("apple");
alert( speedy.stomach ); // apple

// Lazy one's stomach is empty
alert( lazy.stomach ); // <nothing>

現在一切都運作良好,因為 this.stomach= 沒有執行 stomach 的查詢。值會直接寫入 this 物件。

我們也可以透過確保每個倉鼠都有自己的 stomach 來完全避免問題。

let hamster = {
  stomach: [],

  eat(food) {
    this.stomach.push(food);
  }
};

let speedy = {
  __proto__: hamster,
  stomach: []
};

let lazy = {
  __proto__: hamster,
  stomach: []
};

// Speedy one found the food
speedy.eat("apple");
alert( speedy.stomach ); // apple

// Lazy one's stomach is empty
alert( lazy.stomach ); // <nothing>

作為一個常見的解決方案,所有描述特定物件狀態的屬性,例如上述的 stomach,都應該寫入該物件。這樣可以防止此類問題。