7

Consider the following javascript code

var a = Object.create(null);
a.foo = 1;
var b = Object.create(a);
console.log(b.foo);  //prints 1
console.log(b.__proto__);  //prints undefined
b.__proto__ = null;
console.log(b.__proto__);  //prints null
console.log(b.foo);  //prints 1

Can anyone explain how object b is accessing the "foo" property of a even after setting b.__proto__ to null? What is the internal link which is used to access the property of a?

I tried searching through SO for possible explanations but couldn't find any explanation for this particular behaviour of Javascript.

Priyanjit
  • 305
  • 3
  • 9

2 Answers2

7

Your problem is that you are using the deprecated __proto__ property, which is a getter/setter on Object.prototype - but your objects don't inherit from that, so it's undefined at first and the assignment creates a standard property with the name __proto__.

Use the proper Object.getPrototypeOf/Object.setPrototypeOf instead and the code will do what you expect:

var a = Object.create(null);
a.foo = 1;
var b = Object.create(a);
console.log(b.foo); // 1
console.log(Object.getPrototypeOf(b)); // {foo:1} - a
Object.setPrototypeOf(b, null);
console.log(Object.getPrototypeOf(b)); // null
console.log(b.foo); // undefined
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • Thanks. You answered the first question. Can you explain how the inheritance is working in my code? It's not working through the `__proto__` for sure. – Priyanjit Jul 13 '17 at 06:36
  • @Priyanjit In your version, `a` keeps being the [[prototype]] of `b`. – Bergi Jul 13 '17 at 06:39
  • How's that happening? Is there any hidden property other than `__proto__`? Because `a` and `b` are two different objects. – Priyanjit Jul 13 '17 at 06:48
  • 1
    It happened right when you used `Object.create`. Notice that `__proto__` is not a "hidden property" (ok, it's nonenumerable, but that's not what we mean). The prototype chain link - denoted [[prototype]] - is an [internal slot](https://stackoverflow.com/q/33075262/1048572), which is truly "hidden" and *not a property*. – Bergi Jul 13 '17 at 06:55
  • Thanks a lot. @Bergi – Priyanjit Jul 13 '17 at 07:15
  • @Bergi But if you remove the null in the first line then it is working fine. Any reason for that. And that is also not a browser/engine specific. – Ayan Pal Jul 14 '17 at 05:18
  • @AyanPal Sure, `Object.create` can also create objects that do inherit from `Object.prototype`, including the `__proto__` behaviour – Bergi Jul 14 '17 at 05:58
  • @Bergi what happens when I set to `__proto__ = null`? Why I can't to reset for `__proto__` value? – MaximPro Feb 27 '18 at 16:21
  • @MaximPro What do you mean by "*can't reset*"? Using the `__proto__` setter to set the [[prototype]] to `null` just does that. Of course afterwards, the object doesn't inherit from `Object.prototype` any more, loosing the `__proto__` property… – Bergi Feb 27 '18 at 16:25
  • @Bergi I mean it how reinstall (reset) – MaximPro Feb 27 '18 at 16:45
  • @MaximPro `Object.setPrototypeOf(x, Object.prototype)` (or whatever it was before). Doesn't my answer say so? – Bergi Feb 27 '18 at 16:48
  • @Bergi no, I talking about this: `var a = new Object; a.__proto__; ///true proto, created by engine` `a.__proto__ = null; /// delete __proto__???` `a.__proto__ = Object.prototype; /// false proto, creates simple property by user` – MaximPro Feb 27 '18 at 16:53
  • @MaximPro yes, that's exactly what my answer explains. Don't use the deprecated `__proto__` property, especially not on objects that don't inherit from `Object.prototype`. – Bergi Feb 27 '18 at 17:05
  • @Bergi I fully understand your answer. But when we write so `a.__proto__ = null` what happens? Is it somewhere described? Just after `null`, it's unclear how the engine behaves. I mean that `a.__proto__ = Object.prototype` is not a prototype. – MaximPro Feb 27 '18 at 17:13
  • Yes, it is described, go read the ES6 spec. When you do `a.__proto__ = null` then `a` becomes an object without a prototype, i.e. the [[prototype]] slot is null. It looks like it was created by `Object.create(null)`. – Bergi Feb 27 '18 at 17:18
  • @Bergi Well, then why can not we put the prototype back, for example, so `a.__proto__ = Object.prototype` (it's supposed to set `[[Prototype]]` to `Object.prototype`). Why does not it work and create a user property? – MaximPro Feb 27 '18 at 17:29
  • @MaximPro "*`__proto__` is a getter/setter on Object.prototype - but your objects don't inherit from that, so it's undefined*". There is nothing magic about this property. When it doesn't exist, it doesn't exist. A setter that does not exist is not supposed to set [[prototype]]. – Bergi Feb 27 '18 at 17:36
  • @Bergi Hmm it turns out that when we put `null` for `__proto__` we cut off the inheritance and accordingly cut off getter and setter and therefore we can not put any prototype back in place. Right? – MaximPro Feb 27 '18 at 17:46
  • @MaximPro That's what I've been saying all the time. Putting `null` in [[prototype]] of course cuts of the inheritance chain, isn't that the purpose you intended in the first place? – Bergi Feb 27 '18 at 17:47
  • @Bergi So, I understood correctly this? I do not know for me this moment was difficult to understand. – MaximPro Feb 27 '18 at 20:02
  • @MaximPro Right. – Bergi Feb 27 '18 at 20:03
2

Answer by @Bergi is correct. Here is in-depth answer what is happening in case of __proto__

var a = Object.create({});
var b = Object.create(a);
b.__proto__===a; //true
var c = Object.create(null);
var d = Object.create(c);
d.__proto__===c; //false..confusion

Object.hasOwnProperty.call(d,"__proto__"); //false as expected
Object.hasOwnProperty.call(b,"__proto__"); //false ?

Object.hasOwnProperty.call(Object,"__proto__"); //false
Object.hasOwnProperty.call(Object.prototype,"__proto__"); //true

Which means __proto__ is only present in Object.prototype.

Object.getOwnPropertyDescriptor(Object.prototype,"__proto__")
//{enumerable: false, configurable: true, get: ƒ, set: ƒ}

__proto__ is a getter setter which should return internal link to object parent something like

get __proto__(){return this.hidden_internal_link_to_parent;}

Case b.__proto__:- b doesn't have __proto__ property so it goes through [[prototype]] chain to a, then to a's parent and at last to Object.prototype. Object.prototype have __proto__ and it returns link of b's parent which is a.

Case d.__proto__:- d's link to Object.prototype is broken (d --parent-->c and c--parent-->null). So d.__proto__ is undefined. But d have internal link to c which can be accessed by Object.getPrototypeOf(d).

amitdigga
  • 6,550
  • 4
  • 26
  • 31