Search code examples
javascriptobjectprototype

Object is empty when a property to the prototype of function is added via Object.setPrototypeOf()


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.


Solution

  • 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 );