Javascript Prototypal Inheritance&对象属性阴影

时间:2013-01-17 12:32:49

标签: javascript prototypal-inheritance

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

3 个答案:

答案 0 :(得分:13)

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

通过原型链访问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" } }

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


使用本地属性

覆盖prototyped属性

但是,如果您使用其他对象覆盖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" }对象。所做的所有更改都将在该对象上进行,基本上与bobperson对象无关。


原型链

为了让事情变得有趣,在完成上述所有操作之后,您认为会发生什么:

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.toStringtoString是一种存在于JavaScript基础对象Object上的方法,它是几乎所有内容的基本原型。当解释器获取对象上特定方法或属性的读取请求时,它将遵循此事件链:

  1. bob是否有名为toString的属性?
  2. bob.__proto__person是否有名为toString的属性?
  3. bob.__proto__.__proto__Object是否有名为toString的属性?的
  4. 将引用返回function(){ return '[Object object]'; }
  5. 一旦到达第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方法,那么这次我们得到:

    1. bob是否有名为toString的属性?
    2. 该过程在第一个障碍处停止,并从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.
      

      最后一项是引起混淆的项目。这个过程将遵循这条路线:

      1. bob是否有名为personal_details的属性?
      2. person是否有名为personal_details的属性?
      3. 返回对{ age: 22 }的引用,该引用存储在内存中的某处。
      4. 现在启动了一个新进程,因为对象导航或赋值的每个部分都是对属性或方法的新请求。所以,现在我们将personal_details对象切换到写请求,因为左边的属性或变量有=等于的一边总是赋值。

        1. 将值123写入对象age上的属性{ age: 22 }
        2. 所以原始请求可以看作是这样的:

          (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对象的原型创建personperson的所有属性都可由bob通过对其原型的引用来访问。

在下一张图片中,您更改了bob的名称。它现在是bob所以,您只需创建名为slot的新propertybob name(现在,解释器在查看时不会检查原型链对于属性name,它会直接发现bob具有这样的属性。)

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

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

enter image description here