3

Let's consider the following code,

let f = function () {
   this.a = 1;
   this.b = 2;
}

let o = new f(); 
f.prototype.c = 3;
console.log(Object.getPrototypeOf(o)); 

Prints

[object Object] { c: 3 }

But if I use setPrototypeOf instead of f.prototype.c, the object is empty.

let f = function () {
   this.a = 1;
   this.b = 2;
}

let o = new f();   
Object.setPrototypeOf(f, {c: 3}); 
console.log(Object.getPrototypeOf(o));

Prints

[object Object] { ... }

But if I use

let f = function () {
   this.a = 1;
   this.b = 2;
}

let o = new f(); 
Object.setPrototypeOf(f, {c: 3}); 
console.log(Object.getPrototypeOf(f)); 

Prints

[object Object] { c: 3 }

In short the question is, when using Object.setPrototypeOf(o), the Object prints empty, and when using Object.setPrototypeOf(f), the objects prints the property which is added. where as when setting prototype using f.prototype.c = 3, it is accessible by both Objects prototype and the functions prototype.

VLAZ
  • 26,331
  • 9
  • 49
  • 67
Tinu Jos K
  • 888
  • 1
  • 8
  • 21
  • Not much different if you have `obj = {}` and then `obj.foo = 1` or `obj = {bar: 2}`. You're either modifying the existing object or replacing it. `o` maintains the reference to the original. – VLAZ Jan 23 '20 at 13:06
  • so in terms of these 2 ways (setPrototypeOf and .prototype) for adding prototype, how can it differ? – Tinu Jos K Jan 23 '20 at 13:09
  • I'm expanding this in an answer but imagine the code above had `obj2 = obj`. If you do `obj.foo = 1` you're "changing" `obj2`, since it's really just a new reference to the same object. But `obj = {bar: 2}` will leave `obj2` intact, since now `obj` and `obj2` are pointing at different objects. – VLAZ Jan 23 '20 at 13:10
  • wooh, i didn't know that obj = {} syntax would not modify the other object, i was aware of the Object.assign() and the spread operator for copying, but never read about this way which will keep the object immutable, thanks, now let me have a look again on the code. I am still trying to relate it with this 2 ways of setting prototype. – Tinu Jos K Jan 23 '20 at 13:16
  • another thing i came across is when doing setPrototypeOf(), it replaces the complete prototype properties, where as .prototype can add on to it, here i can relate your explanation about reference and copying – Tinu Jos K Jan 23 '20 at 13:20

1 Answers1

2

When you Object.setPrototypeOf(f, ... ), the chain is actually 3 long:

The prototype of o is f. The prototype of f is {c:3}.

So your examples are not equivalent to each other:

1) In the first example, you add the property c directly to the prototype that instances of f will use, so the proto of o contains c since f is the constructor for o.

In the last two examples you add c to the proto of f, so the prototype f was created with. Remember that functions are also just objects and that you're setting the proto of a function you use as a constructor. So the proto of o contains the proto of f which contains c.

2) In the 2nd example, you getPrototypeOf() o. In the 3rd, you getPrototypeOf() f. Hence only the 3rd example shows c again.

3) If you inspect the element in chrome, you see that the constructor of the 2nd example is f, since you ask the proto of o.

In the 3rd example you'll see that the constructor is Object, since you ask the proto of f, which has been set to the object {c} and skip the proto from o to f.

PS: I'm aware this is might be a confusing explanation.

PPS: If you want inheritance, sticking to child.prototype = Object.create( parent.prototype ); child.constructor = child; or ES6 class class child extends parent when it can be used led to the least confusion for me personally.

var first = function first() {
  this.value = 'first function';
};
// before .prototype.extra, first inherist from Function.
console.log( 'constructor of first before:', first.constructor );
first.prototype.extra = 'implemented with .prototype.extra';
// after .prototype.extra, first still inherits from Function, we did not change anything to first itself.
console.log( 'constructor of first after:', first.constructor );
// When first is used as a prototype, the instances will get "extra".
// Aka, everything that inherist from first.
var first_instance = new first();
console.log( 'extra on instance of first:', first_instance.extra );
// f itself does NOT have the extra property, only instances of first do.
console.log( 'extra on first itself:', first.extra );
console.log( '------' );
var second = function second() {
  this.value = 'second function';
};
// before setPrototypeOf, second inherist from Function, like above.
console.log( 'constructor of second before:', second.constructor );
Object.setPrototypeOf( second, { extra: 'implemented with .setPrototypeOf()' });
// after setPrototypeOf, second inherist from Object, we broke the default inheritance chain.
console.log( 'constructor of second after:', second.constructor );
// BY doing this, we effectively turned second into an object.
// It no longer is a function, so we cannot use function methods like .call() or .apply()
console.log( 'second is object, not function: function.apply', second.apply );
console.log( 'second is object, not function: object.hasOwnProperty', second.hasOwnProperty );
// When second is used as a prototype, the instances will not get "extra".
var second_instance = new second();
console.log( 'extra on instance of second:', second_instance.extra );
// second itself Does have the extra property, sine we assigned it on the prototype used by second. not when second is used as the prototype.
console.log( 'extra on second itself:', second.extra );
Shilly
  • 8,511
  • 1
  • 18
  • 24
  • thanks for the explanation, i will go through it slowly and get back to you. :) – Tinu Jos K Jan 23 '20 at 13:23
  • I am a bit confused in your sentence, "In the first example, you add the property c directly to the prototype that instances of f will use" My assumption was setPrototypeOf(f) and f.prototype will both set the prototype of that function with some properties. – Tinu Jos K Jan 23 '20 at 16:42
  • Yeah, I editted that line like 5 times. :/ That's the difficult part to explain. `f` is the prototype for `o`. But `f` also has a prototype, since it's an object. And setPrototypeOf() will set the prototype of `f`. So the prototype that `f` was created with. `o` however, was not created from the prototype of `f`. `o` uses `f` as it's prototype. Hence `c` is nested two deep. – Shilly Jan 24 '20 at 08:12
  • i guess you are trying to say, f.prototype.c = 3 will just add to f's own property (some prototype which o refers to but which is sitting at the same level of f) and not to it's prototype which is above, but setPrototypeOf(f) will add to f's prototype ( which is one level up to f). Is it true? – Tinu Jos K Jan 24 '20 at 08:24
  • I ahve added more examples. It;s even more complicated. We're actually turning `f` into an object instead of a function by using `Object.setPrototypeOf()` so the differences between the two are even bigger. – Shilly Jan 24 '20 at 08:51
  • great! thanks, once last question, here when we use setProtoypeOf(), we are seeing the extra property is not accessible by the object created which is one level down, but according to the concept of prototype chaining, whatever the level be, a property should be searched until the end of the chain, right? this makes me confusing. – Tinu Jos K Jan 24 '20 at 09:01
  • 1
    Yes, but as mentioned in the docs for setPrototypeOf(), we should use Object.create() or classes to create inheritance. The way we used these things are not equivalent to each other or to inheritance. Look at this answer: https://stackoverflow.com/questions/45042777/how-to-properly-use-object-setprototypeof and how it uses `setPrototypeOf()` on a prototype ( Dog.prototype ) or instance ( this ), not on a function that will be a constructor ( Dog ). And doing so, removes the link to Dog, the opposite of what we usually want in inheritance. – Shilly Jan 24 '20 at 09:14
  • 1
    The difference between `f` and `f.prototype`. So in your code, we have to `setPrototypeOf()` `o.prototype` instead of `f` to get the proper inheritance. Hence I prefer `o.prototype = Object.create( f.prototype );` over `Object.setPrototypeOf( o.prototype, f);`. The first one makes it more clear to me that we're copying one prototype to the other and less likely we'd forget to add `.prototype` in the correct spot, since `Object.setPrototypeOf( o, f);` `Object.setPrototypeOf( o.prototype, f);` and `Object.setPrototypeOf( o, f.prototype);` all mean something different. – Shilly Jan 24 '20 at 09:20