8
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的“ name ”属性时,它会在 bob 本身中被遮蔽。

但是在personal_details的情况下,同样的规则被违反了。

我很想知道为什么会这样??

这是 jsbin 的链接:http: //jsbin.com/asuzev/1/edit

4

3 回答 3

14

用几个例子可以很容易地说明正在发生的事情:

通过原型链访问personal_details

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" } }

现在在 person 对象上设置的 Age 23 是通过 bob 的原型链because bob.personal_details直接引用的。person.personal_details向下导航对象结构后,您将直接使用该person.personal_details对象。


用本地属性覆盖原型属性

但是,如果您personal_details用另一个对象覆盖 bob 的属性,则该原型链接将被更本地的属性覆盖。

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 }对象而不是 person 的原始{ 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_detailsdelete只会在当前对象上工作,而不是沿着原型链。


一种不同的看待它的方式

查看原型链如何工作的另一种方法基本上是想象一堆对象。当 JavaScript 扫描特定的属性或方法时,它会向下读取以下结构:

bob :          {  }
  person :     { name: 'dummy', personal_details: { age: 22 } }
    Object :   { toString: function(){ return '[Object object]'; } }

例如,假设我想访问bob.toString. toString是一种存在于 JavaScript 基础对象上的方法,Object它是几乎所有东西的基础原型。当解释器收到对对象上特定方法或属性的读取请求时,它将遵循以下事件链:

  1. bob一个属性叫? 不。toString
  2. bob.__proto__ie是否person有一个名为 的属性toString不。
  3. bob.__proto__.__proto__ie是否Object有一个名为 的属性toString是的
  4. 返回参考function(){ return '[Object object]'; }

一旦到达第 4 点,解释器将返回对toString在 Object 上找到的方法的引用。如果未在未定义属性Object的错误上找到该属性,则很可能已被触发(因为它是链中的最后一个)。

现在如果我们以之前的例子为例,这次toString在 - 上定义一个方法bob

bob :          { toString: function(){ return '[Bob]'; } }
  person :     { name: 'dummy', personal_details: { age: 22 } }
    Object :   { toString: function(){ return '[Object object]'; } }

如果我们再次尝试读取toStringbob 上的方法,这次我们得到:

  1. bob一个属性叫? 是的。toString

该过程在第一个障碍处停止并toString从 bob 返回方法。这意味着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.

最后一项是引起混乱的一项。该过程将遵循以下路线:

  1. bob一个属性叫? 不。personal_details
  2. person一个属性叫? 是的。personal_details
  3. 返回{ age: 22 }存储在内存中某处的引用。

现在开始一个新的过程,因为对象导航或赋值的每个部分都是对属性或方法的新请求。所以,现在我们有了我们的personal_details对象,我们切换到一个写请求,因为等号左边的属性或变量=总是一个赋值。

  1. 将值写入对象123的属性age{ age: 22 }

所以原始请求可以被看作是这样的:

(bob.personal_details)            --- read
    (personal_details.age = 123)  --- write

如果bob拥有它自己personal_details的进程属性将是相同的,但被写入的目标对象将是不同的。


最后...

按照你的问题的思路:

很难理解原型对象上的属性被视为 READ_ONLY,但如果属性是一个对象,那么人们可以掌握它并可以自由修改其属性!我的理解对吗?

原型属性似乎是只读的,但只有在作为继承它们的对象的属性直接访问时——因为这些属性实际上根本不存在于继承对象上。如果您向下导航到原型对象本身,则可以将其视为任何普通对象(具有读取和写入功能),因为这正是它的本质——普通对象。一开始可能会令人困惑,但这是本质或原型继承,它与您如何访问正在使用的属性有关。

于 2013-01-17T13:20:56.450 回答
3

在第二种情况下,您没有分配给 的属性bob,那么您如何覆盖它呢?

在 的情况下bob.name = "bob",您绑定了 bob自己的name 属性。

在第二种情况下,你没有personal_details您可以通过原型访问 bob 的属性。然后,您分配给该对象的属性,到那时所有连接都将bob丢失。

可以这样想:

bob.personal_details.age = 23;
// equivalent
expr = bob.personal_details;
expr.age = 23; // As you can see, there's no assignment to bob

它不违反任何规则,因为情况完全不同。

于 2013-01-17T12:39:09.607 回答
3

我希望下面的图表足够清楚,但我会尽量简单地解释发生了什么。

当您创建新对象时,Object.create您会使用原型方法的第一个参数创建一个对象create。因此,您bob使用指向person对象的原型创建。的所有属性person都可以bob通过对其原型的引用来访问。

在下一张图片中,您更改了bob的名字。现在bob很简单,您可以创建新名称slot或名称(现在解释器在查找属性时不会检查原型链,它会直接发现 bob 具有这样的属性)。propertybobnamename

在第三个中,您将其更改bob.personal_details.age为影响到,person.personal_details.age因为这只是同一个对象。

最后你设置了属性personal_details,现在bob有了 slot personal_details,它不是原型属性,它是对另一个对象的引用——匿名对象。

在此处输入图像描述

于 2013-01-17T13:16:18.310 回答