Search code examples
javascriptprototypeprototypal-inheritance

Javascript prototype chain behaves unexpectedly


Here is my code:

var Person = new Function() // same as function Person(){}
var john = new Person()

now I have prototype chain like that: Object -> Function -> Person -> john

Now I am doing something like that:

Function.prototype.bar = "boo"

So I expect to have Person.bar and john.bar to be "boo"

Person.bar  // boo
john.bar    // undefined

so what happened? I dived into and found out that john.__proto__ is prototype of Person, but john.__proto__.__proto__ is NOT prototype of Function, it's prototype of Object, so I lost one piece of chain(Finction). that's why john.bar was undefined. So why is this happening? shouldn't I be able to access Function prototype properties from john?


Solution

  • Consider the following image taken from this answer.

    As you can see, when you create a function in JavaScript a new prototype object is automatically created as well.

    function Person() {}
    

    Hence, the above code is actually:

    function Person() {}
    Person.prototype = { constructor: Person };
    

    Now, you should also understand that the __proto__ property of a function is different from its prototype property.

    function Person() {}
    
    console.log(Person.__proto__ === Function.prototype); // true
    console.log(Person.prototype !== Function.prototype); // true

    When you create an instance of Person using the new keyword, that instance object inherits from Person.prototype and not from Person or Person.__proto__:

    function Person() {}
    
    const john = new Person;
    
    console.log(john.__proto__ !== Person);           // true
    console.log(john.__proto__ !== Person.__proto__); // true
    console.log(john.__proto__ === Person.prototype); // true

    The same goes for functions created by calling new Function.

    const Person = new Function;
    
    console.log(Person.__proto__ === Function.prototype); // true
    console.log(Person.prototype !== Function.prototype); // true

    That's the reason why john does not inherit from Function.prototype. The prototype chain for john is as follows.

          __proto__                     __proto__                     __proto__
    john -----------> Person.prototype -----------> Object.prototype -----------> null
    

    Whereas the prototype chain of Person is as follows:

            __proto__                       __proto__                     __proto__
    Person -----------> Function.prototype -----------> Object.prototype -----------> null
    

    Here's the proof:

    const Person = new Function;
    const john   = new Person;
    
    console.log(john.__proto__                     === Person.prototype); // true
    console.log(john.__proto__.__proto__           === Object.prototype); // true
    console.log(john.__proto__.__proto__.__proto__ === null);             // true
    
    console.log(Person.__proto__                     === Function.prototype); // true
    console.log(Person.__proto__.__proto__           === Object.prototype);   // true
    console.log(Person.__proto__.__proto__.__proto__ === null);               // true

    This is because Person.prototype !== Person.__proto__.


    Now, you may ask why do we have both the prototype and the __proto__ properties. The reason for this is because an object in JavaScript (let's call it derived) inherits from another object (let's call it base) if and only if derived.__proto__ = base. Consider the following code:

    const base = { foo: 10 };
    
    const derived = {};
    
    derived.__proto__ = base; // derived now inherits from base
    
    console.log(derived.foo); // 10
    
    const newBase = { bar: 20 };
    
    derived.__proto__ = newBase; // derived no longer inherits from base, it inherits from newBase
    
    console.log(derived.foo); // undefined
    console.log(derived.bar); // 20

    Now, when you create an instance of a function the instance actually inherits from the prototype of the function. It doesn't inherit from the function.

    function Person() {}
    
    const john = new Person;
    

    The above code is equivalent to:

    function Person() {}
    Person.prototype = { constructor: Person };
    
    const john = { __proto__: Person.prototype };
    Person.call(john);
    

    Hope that clears things.