var person = { name :"dummy", personal_details: { age : 22, country : "USA" } };
var bob = Object.create(person);
bob.name = "bob";
bob.personal_details.age = 23;
console.log(bob.personal_details === person.personal_details);
// true : since it does not shadow object of prototype object
console.log(bob.name === person.name);
// false : since it shadows name
////now
bob.personal_details = {a:1};
console.log(bob.personal_details === person.personal_details);
//假
当对象 bob 尝试覆盖" 名称 " person 的属性然后,它会被bob本身遮蔽。
但是在 personal_details 的情况下,违反了相同的规则。
我很好奇,为什么会这样???
以下是jsbin的链接:http://jsbin.com/asuzev/1/edit
答案 0 :(得分:13)
通过几个例子可以很容易地说明正在发生的事情:
var person = { name :"dummy", personal_details: { age : 22, country : "USA" } }
var bob = Object.create(person)
bob.name = "bob"
bob.personal_details.age = 23
哪些输出如:
console.log( bob );
/// { name :"bob", personal_details: { age : 23, country : "USA" } }
console.log( person )
/// { name :"dummy", personal_details: { age : 23, country : "USA" } }
现在在人物对象上设置了23岁because bob.personal_details
是通过bob原型链直接引用person.personal_details
。在向下导航对象结构后,您将直接使用person.personal_details
对象。
但是,如果您使用其他对象覆盖bob的personal_details
属性,则该原型链接将被更多本地属性覆盖。
bob.personal_details = { a: 123 }
现在输出是:
console.log( bob );
/// { name :"bob", personal_details: { a : 123 } }
console.log( person )
/// { name :"dummy", personal_details: { age : 23, country : "USA" } }
因此,从现在开始访问bob.personal_details
- 您引用的是{ a: 123 }
对象,而不是人的原始{ age : 23, country : "USA" }
对象。所做的所有更改都将在该对象上进行,基本上与bob
或person
对象无关。
为了让事情变得有趣,在完成上述所有操作之后,您认为会发生什么:
delete bob.personal_details
您最终将原始链接恢复为person.personal_details
(因为您已删除了您添加的本地属性),因此控制台日志会显示:
console.log( bob );
/// { name :"bob", personal_details: { age : 23, country : "USA" } }
基本上,JavaScript引擎将沿着原型链返回,直到它找到您在每个原型对象上请求的属性或方法。设置项目的链越往上意味着它将在稍后覆盖其他链。
现在另一个问题,如果你再次解雇以下会发生什么?
delete bob.personal_details
没有,不再为bob分配一个名为personal_details
的实际属性,delete
只会对当前对象起作用,而不会跟随原型链。
查看原型链如何工作的另一种方法基本上是想象一堆对象。当JavaScript扫描特定属性或方法时,它将通过以下结构向下读取:
bob : { }
person : { name: 'dummy', personal_details: { age: 22 } }
Object : { toString: function(){ return '[Object object]'; } }
举个例子,假设我想访问bob.toString
。 toString
是一种存在于JavaScript基础对象Object
上的方法,它是几乎所有内容的基本原型。当解释器获取对象上特定方法或属性的读取请求时,它将遵循此事件链:
bob
是否有名为toString
的属性? 否强> bob.__proto__
即person
是否有名为toString
的属性? 否强> bob.__proto__.__proto__
即Object
是否有名为toString
的属性?的是 function(){ return '[Object object]'; }
一旦到达第4点,解释器将返回对Object上的toString
方法的引用。如果在Object
找不到该属性,则未定义属性的错误最有可能被触发(因为它是链中的最后一个)。
现在,如果我们之前采用这个例子,这次在toString
上定义bob
方法 - 那么:
bob : { toString: function(){ return '[Bob]'; } }
person : { name: 'dummy', personal_details: { age: 22 } }
Object : { toString: function(){ return '[Object object]'; } }
如果我们再次尝试在bob上读取toString
方法,那么这次我们得到:
bob
是否有名为toString
的属性? 是强> 该过程在第一个障碍处停止,并从bob返回toString
方法。这意味着bob.toString()
将返回[Bob]
而不是[Object object]
。
正如Phant0m简洁地说明,对象的写入请求遵循不同的路径,并且永远不会沿着原型链向下移动。理解这一点是为了解决什么是读取和什么是写入请求之间的区别。
bob.toString --- is a read request
bob.toString = function(){} --- is a write request
bob.personal_details --- is a read request
bob.personal_details = {} --- is a write request
bob.personal_details.age = 123 --- is a read request, then a write request.
最后一项是引起混淆的项目。这个过程将遵循这条路线:
bob
是否有名为personal_details
的属性? 否强> person
是否有名为personal_details
的属性? 是强> { age: 22 }
的引用,该引用存储在内存中的某处。现在启动了一个新进程,因为对象导航或赋值的每个部分都是对属性或方法的新请求。所以,现在我们将personal_details
对象切换到写请求,因为左边的属性或变量有=
等于的一边总是赋值。
123
写入对象age
上的属性{ age: 22 }
所以原始请求可以看作是这样的:
(bob.personal_details) --- read
(personal_details.age = 123) --- write
如果bob
拥有自己的personal_details
属性,那么该过程将是相同的,但写入的目标对象会有所不同。
提出你的问题:
难以消化原型对象上的属性被视为READ_ONLY,但如果属性是一个对象,那么可以抓住它并可以自由地修改它的属性!我的理解是对的吗?
原型属性看似只读,但只能直接作为继承它们的对象的属性访问 - 因为这些属性实际上并不存在于继承对象上。如果您向下导航到原型对象本身,那么它可以被视为任何普通对象(带有读取和写入),因为它正是它 - 正常对象。最初可能会让人感到困惑,但这是继承的性质或原型,它是关于您如何访问您正在使用的属性的全部内容。
答案 1 :(得分:3)
在第二种情况下,您没有指定bob
的属性,那么您怎么能覆盖它呢?
如果是bob.name = "bob"
,则绑定bob的拥有名称属性。
在第二种情况下,您不。您可以通过原型访问bob的personal_details
属性。然后,您分配给该对象的属性,到那时所有与bob
的连接都将丢失。
这样想:
bob.personal_details.age = 23;
// equivalent
expr = bob.personal_details;
expr.age = 23; // As you can see, there's no assignment to bob
它没有违反任何规则,因为情况完全不同。
答案 2 :(得分:3)
我希望下面的图表足够清楚,但我会尽量解释发生了什么。
使用Object.create
创建新对象时,使用原型创建一个对象create
方法的第一个参数。因此,您使用指向bob
对象的原型创建person
。 person
的所有属性都可由bob
通过对其原型的引用来访问。
在下一张图片中,您更改了bob
的名称。它现在是bob
所以,您只需创建名为slot
的新property
或bob
name
(现在,解释器在查看时不会检查原型链对于属性name
,它会直接发现bob具有这样的属性。)
在第三个中,您更改了bob.personal_details.age
,这会影响到person.personal_details.age
,因为这只是同一个对象。
最后你设置属性personal_details
,现在bob
有插槽personal_details
,它不是原型属性,它是对另一个对象的引用 - 匿名对象。