嵌套对象

时间:2017-03-30 08:57:21

标签: javascript prototypal-inheritance

我试图在Javascript中了解原型继承。我想我已经掌握了基本的概念,但当我玩这个时,我遇到了以下问题,但仍然让我感到困惑。

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

我创建了一个像这样的新对象:

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

接下来我创建两个"实例"那个对象:

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是否有一个名为ownProperty的{​​{1}},但它没有。如果这是一个阅读操作,现在将查找name属性并检查prototype以查看它是否User ownProperty。但是,由于这是一个集合操作,为什么在通常只是创建一个缺少的ownProperty的情况下走原型链?

3 个答案:

答案 0 :(得分:1)

  

但是,由于这是一个设置操作,为什么在通常只创建一个缺少的ownProperty的情况下遍历原型链?

当您说user1.name.first = "John"时,必须先解析user1.name部分,然后才能检索或设置.first属性。在您的示例中,user1.name部分仅存在于原型对象上,因此 对象的设置为.first属性。

同样,当您说user1.name.middle = "Mortimer"时,user1.name部分再次从原型解析为嵌套对象,因此您在上创建.middle属性 object,这就是User.name.middle也返回"Mortimer"的原因。

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

答案 1 :(得分:1)

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

  

通常,每当在派生对象上设置属性时,该对象或者已经将该属性作为ownProperty,在这种情况下简单地赋值,或者将属性新创建为ownProperty在派生对象上,覆盖prototype属性。就像我分配给user1.name时发生的那样。

这里的问题是你假设user.name.first算作"属性。 。 。在派生对象上设置" (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)




答案 2 :(得分:0)

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

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

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

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

有更多细节。

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