返回课程

为什么两只仓鼠都吃饱了?

重要性: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,将食物添加到原型的胃中

所以所有仓鼠共用一个胃!

对于 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 对象。

我们也可以通过确保每只仓鼠都有自己的胃来完全避免这个问题。

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,都应该写入该对象。这可以防止此类问题。