我试图在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的情况下走原型链?
答案 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.first
和user1.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.name
和user2.name
,例如firstReference
和secondReference
,希望您的行为看起来更清晰。
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
有更多细节。
关键是你需要原型和自己的属性。