0

I'm trying to understand how prototype and constructor work in JavaScript and came across this case.

I always picture how objects got created in JavaScript is by

  • an empty object is created
  • the object is linked to the function's prototype
  • the object searches for a constructor function
  • this constructor function got run and properties got assigned to that object
  • the object is returned by an implicit return this

This way of understanding seems not to be able to explain obj2 and obj3.

For instance, I explicitly set Foo.prototype.constructor to something else for obj2, but obj2 still has the property bar. Let's say bar is stored in Foo's prototype, that explains why obj2 has the property bar and why obj3 is created by Object, but what if I create a couple more obj2s, that would mean they share the same variable bar on Foo's prototype, and I think this is not the case.

Can somebody give a more reasonable explanation on why obj2 and obj3 are like this?

function Foo(){
  this.bar = true;
}

console.log('obj1');
var obj1 = new Foo();
console.log(obj1);
console.log(obj1.constructor === Foo);

console.log('obj2');
Foo.prototype.constructor = 3; //a dummy value
var obj2 = new Foo();
console.log(obj2);
console.log(obj2.constructor === Foo);


function Bar(){
  this.foo = true;
}
console.log('obj3');
Bar.prototype = 3;
var obj3 = new Bar();
console.log(obj3);
console.log(obj3.constructor === Bar);
console.log(obj3.constructor === Object);
kevguy
  • 4,328
  • 1
  • 24
  • 45
  • http://stackoverflow.com/questions/572897/how-does-javascript-prototype-work – Abhitalks Jan 24 '17 at 07:22
  • JavaScript Objects are essentially variables (contextual or otherwise) that contain properties -- these properties can be methods, or just values. Prototypes (Object.prototype.prototype) modify these properties. – Joshua Jan 24 '17 at 07:26
  • @Abhitalks if you did read what I said, you'll know I already understand what your link is talking about. The second bullet point I'm already talking the linkage `__proto__`, I just didn't explicitly write `__proto__` or `[[Prototype]]`. – kevguy Jan 24 '17 at 07:34

3 Answers3

1

I saw your post yesterday, but I was a little busy at that time, now I'm free and I'd like to answer your question.

When the code new Foo() is executed, the following things happen:

  1. Function Foo() is called, as @slebetman mentioned, since the function is called with the new keyword then it is treated as a constructor, an empty object is created.

  2. The object is linked to the function's prototype,inheriting from Foo.prototype.

  3. this bound to the newly created object. this.bar = true executed. new Foo is equivalent to new Foo(), i.e. if no argument list is specified, Foo is called without arguments.

  4. The object returned by the constructor function becomes the result of the whole new expression. If the constructor function doesn't explicitly return an object, the object created in the first step is used instead. (Normally constructors don't return a value, but they can choose to do so if they want to override the normal object creation process.)

Let's say bar is stored in Foo's prototype, that explains why obj2 has the property bar and why obj3 is created by Object, but what if I create a couple more obj2s, that would mean they share the same variable bar on Foo's prototype, and I think this is not the case.

You are right.

bar is a property owned by obj1 and obj2, for example:

console.log(obj1.hasOwnProperty("bar")); // true

But if you add a property like this:

Foo.prototype.bar2 = true;
console.log(obj1.hasOwnProperty("bar2")); // false

The value of bar2 is shared by all the Foo instances, but bar property isn't shared by Foo instances, each Foo instance could have its own bar value.

Can somebody give a more reasonable explanation on why obj2 and obj3 are like this?

  • What happened to obj2 ?

When you declare constructor Foo, Foo.prototype.constructor will automatically point to Foo, console.log(Foo.prototype) will show that, actually this is called circular reference, which should be detected when you traverse an object recursively. But in your case, Foo.prototype.constructor = 3, fortunately, the process of new Foo() expression doesn't involve Foo.prototype.constructor property, so it will be done correctly, but the value of obj2.constructor or Foo.prototype.constructor is still 3.

  • What happened to obj3 ?

Bar.prototype = 3, my theory is that, when new Bar() executed, as in Step 2, the created object is supposed to linked to Bar.prototype, but since the value of Bar.prototype doesn't refer to an object, implicitly a default value was assigned, which is the Object.prototype.

console.log(Object.prototype === Object.getPrototypeOf(obj3)); // true

Due to object.prototype.constructor's reference to the Object, obj3.constructor also refer to the Object, but obj3's de facto constructor is still Bar, because of Step 1, which also can be proved by console.log(obj3.foo); // true.

More information: How objects are created when the prototype of their constructor isn't an object?

Community
  • 1
  • 1
Yichong
  • 707
  • 4
  • 10
  • About `obj3`, I thought it was `Number` as if `3` were boxed to an object, but it's not. And obviously the assignment was not ignored otherwise constructor would be still `Bar`. Then I was wondering how the prototype becomes `Object`. I've tested in Firefox Chrome Safari, they all behave the same. So I think it should be something clearly specified in ECMA-262. However, I haven't got to the right place. Could you provide more information about the internal mechanism? – Leo Jan 25 '17 at 06:32
  • @Leo This is my theory, but I can't find any documents to support it, so I've asked this question-- http://stackoverflow.com/questions/41846565/how-objects-are-created-when-the-prototype-of-their-constructor-is-a-primitive-n – Yichong Jan 25 '17 at 08:29
  • @Leo I thought it was something like 'automatic creation of global variable', but you know, under the strict mode, it doesn't throw an error. – Yichong Jan 25 '17 at 08:32
  • The question has been answered, that's great. – Leo Jan 25 '17 at 09:17
  • Thanks for the explanation man, that clears things out a lot. @Y.C. – kevguy Jan 27 '17 at 18:09
0

Your understanding is almost correct except for this:

  • the object searches for a constructor function

No, the constructor function is this:

function Foo(){
  this.bar = true;
}

And you're calling the constructor function directly without the object existing:

new Foo();
new Bar();

That's just a function call. It's a bit special because of the new key word. So, to modify your understanding a bit I'm going to take your description and change it a bit:

  • a function is called
  • since the function is called with the new keyword then it is treated as a constructor
  • an empty object is created
  • the object is linked to the function's prototype
  • the object is returned by an implicit return this

Note, there is no need whatsoever for the object to search for the constructor when it is created within a call to the constructor itself. The call to the constructor is the first step, not the third.

Note that this is a very high-level description of how objects are constructed. There are several details like how this is treated that is glossed over.

slebetman
  • 109,858
  • 19
  • 140
  • 171
  • I know when you do `new Foo()`, `this` will be set to the object created and there's an implicit `return this` at the end of the function if that function doesn't have a `return` statement. So you're saying there's more to that? @slebetman – kevguy Jan 24 '17 at 07:37
0

For instance, I explicitly set Foo.prototype.constructor to something else for obj2, but obj2 still has the property bar. [..]

In your case constructor is just a prototype property on Foo. It is not the constructor for obj2 as you would call it. The constructor for obj2 is Foo. By applying a property named as constructor to the prototype, you are not changing the constructor!

[..] but Let's say bar is stored in Foo's prototype, that explains why obj2 has the property bar [..]

Contrary to your belief, bar is not stored in Foo's prototype. It is a property which exists on obj2 instance. It is an instance property not a prototype property. The prototype property in your case is constructor.

Look at these examples for more clarity:

function Foo() {
  this.bar = true;
}

var obj = new Foo();
console.log('The instance property "bar" is ' + obj.bar);

Foo.prototype.baz = 4;
console.log('The prototype property "baz" is ' + obj.baz);

Foo.prototype.bar = 5;
console.log('The prototype property "bar"=' + Foo.prototype.bar + ' is overridden by instance property "bar"=' + obj.bar);
Abhitalks
  • 27,721
  • 5
  • 58
  • 81