嵌套对象的原型继承

Prototypal inheritance with nested objects

我正试图了解 Javascript 中的原型继承。我想我已经掌握了基本概念,但是当我在玩这个的时候,我 运行 进入了下面,这仍然让我感到困惑。

有一个非常相似的问题和答案here,但它没有完全回答为什么会发生这种情况,至少对我来说不是。

我这样创建一个新对象:

var User = {
  username: "",
  name: {
    first: "",
    last: ""
  }
}

接下来我创建两个 "instances" 该对象:

var user1 = Object.create(User);
var user2 = Object.create(User);

现在我这样设置名称 属性:

user1.name = { first: "John", last: "Jackson"}
user2.name = { first: "James", last: "Jameson"}

现在我

alert(user1.name.first) \ -> John alert(user2.name.first) \ -> James

一切如预期。到目前为止一切顺利。

但是,如果我这样设置 name.first 属性:

user1.name.first = "John";
user2.name.first = "James";

然后我得到

alert(user1.name.first) \ -> James
alert(user2.name.first) \ -> James

很明显,现在 属性 是在原型对象 User 上设置的(或者更确切地说是包含的 name 对象),而不是在当前对象 user1 中覆盖它.为什么会这样?

进一步如果我这样做

user1.name.middle = "Mortimer"

我现在可以了

alert(User.name.middle) // -> Mortimer

这不是我所期望的。通常,每当在派生对象上设置 属性 时,该对象要么已经将 属性 作为 ownProperty,在这种情况下,只需分配值,要么 属性在派生对象上新创建为 ownProperty,覆盖原型 属性。就像我分配给 user1.name.

时发生的一样

那么,为什么对包含在原型对象中的对象进行赋值会导致这种(至少对我而言)意外和违反直觉的行为?

按照我的理解,当进行赋值时,第一个检查是查看 user1 是否有一个名为 nameownProperty,但它没有。如果这是一个读取操作,现在将查找 prototype 属性 并 User 检查它是否有 ownProperty name。但是既然这是一个集合操作,为什么在通常只是简单地创建一个丢失的 ownProperty 时走原型链?

But since this is a set operation why walk the prototype chain when usually a missing ownProperty is simply created?

当您说 user1.name.first = "John" 时,user1.name 部分必须先解析,然后才能检索或设置 .first 属性。在你的例子中 user1.name 部分只存在于原型对象上,所以它是 that object whose .first 属性 you are setting.

类似地,当您说 user1.name.middle = "Mortimer" 时,user1.name 部分再次解析为原型中的嵌套对象,因此您在 .middle 属性 =34=]那个对象,这就是为什么User.name.middle也returns"Mortimer".

如果您说 user1.name.firstuser1.name 无法解析(在当前对象或其原​​型链中),那么您将有一个 TypeError: Cannot set property 'first' of undefined。 (您可以通过说 user1.address.street = "something" 在现有代码中尝试该概念 - 您会得到 TypeError,因为 user1.address 不存在于 user1 或其原型链上。 )

由于您已经阅读了类似的问题和答案,但似乎仍然难以理解该行为,因此我会尽量使我的解释尽可能清楚。这是我认为你出错的地方(强调我的):

Generally, whenever a property is set on a derived object, that object either already has that property as an ownProperty in which case the value is simply assigned, or the property is newly created as an ownProperty on the derived object, overriding the prototype property. Just like happens when I assign to user1.name.

这里的问题是您假设 user.name.first 算作 "a property . . . set on a derived object"(User 的一个实例)。然而,事实并非如此。在 JavaScript 中,属性的继承很浅(单层深)。 user.name只是一个通过原型引用共享的对象值,所以从一个地方对它的修改到处都会反映出来。

user1.nameuser2.name 想像为以下示例代码段中的 firstReferencesecondReference,希望您能更清楚地了解该行为。

var User = {
  username: "",
  name: {
    first: "",
    last: ""
  }
}

var firstReference = User.name
var secondReference = User.name

firstReference.name.first = 'First!'

console.log(secondReference.name) //=> 'First!' (logical and expected result)

Object.create方法使用第一个参数作为原型创建一个对象,第二个可选参数是一个具有自身属性的附加对象。

我认为这里的问题是 name 根据定义在原型中,因此不是自己的 属性。

如果你想要单独的属性,那么你应该使用第二个参数。原型是您存储方法和共享属性的地方。

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create

有更多详细信息。

关键是你想要原型和自己的属性。