Search code examples
javascriptinheritanceprototype

JavaScript Prototype - Please Clarify


Can someone please help me understand the prototype property? I don't understand whether the prototype property is a property of the function or of what's inside the function.

Let's say we create the following constructor, Food. At this point, the function Food() has the property Food.prototype. Since Food is an instance of Object, then this means that Obect.prototype is the prototype attribute of all objects created with Food().

function Food() {}

Then create another constructor Pizza. Pizza has the property Pizza.prototype.

function Pizza(toppings) {
    this.toppings = toppings;
}

Then we make Pizza inherit from Food by setting Pizza's prototype property to be an instance of Food. The prototype attribute of Pizza is now Food.prototype since Food is the parent of pizza.

Pizza.prototype = new Food();

Then we create an instance of Pizza

var myPizza = new Pizza("pepperoni");

Does myPizza also have a prototype property that it inherits from Pizza? If so, is myPizza.prototype == Object.prototype? What is Obejct.prototype? Is it a property of Object()? Do only functions have the prototype property? Is Object.prototype an object? Does Pizza.prototype refer to the entire function that creates the Pizza constructor? Is this function an object itself?

function Pizza(toppings) {
    this.toppings = toppings;
}

Or does the Pizza.prototype just refer to what is inside the scope of Pizza()?

this.toppings = toppings;

Is Pizza.toppings a property of Pizza.prototype? But isn't Pizza.prototype a property of Pizza()? Is toppings only a property of an object created with the Pizza constructor? And Pizza.prototype is a property of the Pizza constructor?

Current prototype chain is as follows:

myPizza --> Pizza.prototype --> Food.prototype --> Object.prototype


Solution

  • Instead of starting with answers to the questions, I’m going to start with the way prototypes and constructors work to avoid confusion in trying to interpret those answers with partial understanding. So, prototype recap:

    • Every value in JavaScript, except null and undefined, has an associated value: its prototype.¹

    • The prototype is used to look up properties. When you evaluate x.foo, you check to see if the value x has an own property – a property on itself – named “foo”. If it does, x.foo is the value of that own property. If not, the lookup continues on x’s prototype.

    • A value’s prototype can be null, meaning any property lookup that didn’t find an own property results in undefined.

    • You can get a value’s prototype with the function Object.getPrototypeOf.

    • You can create a new object with a specific prototype with the function Object.create.

    Constructors in JavaScript have a property named “prototype”. The value of this property isn’t the prototype of the constructor; it’s the prototype of values created with the constructor. Taking your example constructor:

    function Food() {}
    

    If you run new Food(), a new object will be created with its prototype set to Food.prototype, and Food will be executed with this set to that new object. In other words, this:

    // create a new instance of Food
    let f = new Food();
    

    means the same thing as this:

    // create a new object with Food.prototype as its prototype
    let f = Object.create(Food.prototype);
    
    // initialize it using the constructor
    Food.call(f);
    

    Now, the way property lookups work, summarized above, gives rise to a prototype chain. If x has a prototype y and y has no prototype, x.foo gets looked up on this chain:

    x -> y -> null
    
    1. If x has an own property “foo”, x.foo evaluates to its value
    2. If y has an own property “foo”, x.foo evaluates to its value
    3. We’ve reached null, the end of the chain, so x.foo is undefined

    The default value of the prototype property of constructors is a new Object instance, so the prototype chain of a new Food() looks like this:

    f -> Food.prototype -> Object.prototype -> null
    

    and you can say that a value x is an instance of a constructor C if x’s prototype is C.prototype or x’s prototype is not null and an instance of C. (If x is C.prototype, x is not an instance of C.) This is how the instanceof operator works²:

    console.log({} instanceof Object);  // true
    console.log(Object.prototype instanceof Object);  // false

    You can also say that C inherits from D if C.prototype is an instance of D.

    Everything built into JavaScript has Object.prototype on its prototype chain. Functions are Object instances:

    function f() {}
    f instanceof Object  // true
    

    and so constructors are too:

    function Food() {}
    Food instanceof Object  // true
    

    It’s important to note that this doesn’t say anything about the relationship between instances of Food and Object. You can set Food.prototype = null to get new Food() instanceof Object === false, but it will still be the case that Food instanceof Object.

    I hope that this framework is enough to address your questions. That was the idea, anyway. Still going to respond to them explicitly using it:

    The questions at hand

    Let's say we create the following constructor, Food. At this point, the function Food() has the property Food.prototype. Since Food is an instance of Object, then this means that Obect.prototype is the prototype attribute of all objects created with Food().

    All objects created with new Food() have a prototype of Food.prototype. The prototype of Food.prototype is Object.prototype. Food is a function, which means it’s true that it’s an instance of Object, but the relevant instance of Object here is Food.prototype.

    Then we make Pizza inherit from Food by setting Pizza's prototype property to be an instance of Food. The prototype attribute of Pizza is now Food.prototype since Food is the parent of pizza.

    The prototype property of the function Pizza is now an object with Food.prototype as its prototype.

    Does myPizza also have a prototype property that it inherits from Pizza?

    myPizza doesn’t inherit anything from Pizza. It inherits everything from the object Pizza.prototype. Since Pizza.prototype does not have a property named “prototype”, myPizza does not inherit a property named “prototype”.

    What is Object.prototype? Is it a property of Object()?

    Object.prototype literally means “the ‘prototype’ property of Object”, so yes. This value is on the prototype chain of all instances of Object, which is only significant because most things in JavaScript are instances of Object. Apart from that, it’s like any other prototype property of a constructor.

    Do only functions have the prototype property?

    Functions defined using the function or class keywords start with a property named prototype. (Arrow functions don’t, and can’t be used as constructors.) You can put a property named prototype on anything. It only has meaning when it’s on a constructor – a function used with new or instanceof.

    Is Object.prototype an object?

    It’s an object with a lowercase “o”, when using “object” to refer to any non-primitive² value. It is not an instance of Object – it has a null prototype.

    Is this function an object itself?

    Yes, functions are objects³.

    Does Pizza.prototype refer to the entire function that creates the Pizza constructor?

    No. Pizza.prototype is not a function. It’s used by the Pizza constructor but the Pizza constructor is not an instance of it and was not created by it.

    Or does the Pizza.prototype just refer to what is inside the scope of Pizza()?

    Nothing to do with scope, either. When you evaluate new Pizza(), Pizza is called with a new instance of Pizza as its this value. this is not the scope of a function. A “scope” is an area where some set of variables is visible.

    function Foo() {
        let x = 5;  // a variable in scope. unrelated to `this`.
    }
    
    function Foo() {
        this.x = 5;  // assigning to a property of the value `this`.
                     // unrelated to variables.
    }
    

    Is Pizza.toppings a property of Pizza.prototype?

    It’s not Pizza.toppings. There’s a new object – this – and you’re assigning the value of a parameter to the Pizza function, named toppings, to a property of that new object, also named toppings. The new object’s prototype is Pizza.prototype, but the new object is not Pizza.prototype itself and so the answer is that “no”, toppings is not a property of Pizza.prototype.

    But isn't Pizza.prototype a property of Pizza()?

    It’s a property of Pizza. (Just making sure Pizza() refers to the function and not the value you get by calling the function. Gotta be precise!)

    Is toppings only a property of an object created with the Pizza constructor?

    Right!

    And Pizza.prototype is a property of the Pizza constructor?

    Yes.

    Current prototype chain is as follows:

    myPizza --> Pizza.prototype --> Food.prototype --> Object.prototype

    Also correct. You can confirm it with the aforementioned getPrototypeOf.

    Object.prototype.toString = function () { return 'Object.prototype'; };
    
    function Food() {}
    Food.prototype.toString = function () { return 'Food.prototype'; };
    
    function Pizza(toppings) {
        this.toppings = toppings;
    }
    
    Pizza.prototype = Object.create(Food.prototype);
    Pizza.prototype.toString = function () { return 'Pizza.prototype'; };
    
    let myPizza = new Pizza();
    myPizza.toString = function () { return 'myPizza'; };
    
    let chainLink = myPizza;
    
    while (true) {
        console.log(String(chainLink));
        
        if (chainLink === null) {
            break;
        }
        
        chainLink = Object.getPrototypeOf(chainLink);
    }

    Note that I’ve written Object.create(Food.prototype) instead of new Food() here. You don’t want to run the parent constructor outside the child constructor, though it was common in ES3. ES5 added Object.create. ES6 added class and extends, which is what you’ll want to use in practice.

    ¹ Primitives don’t have a [[Prototype]] in the spec but that doesn’t really matter because their property lookup and post-ES5 Object.getPrototypeOf work like they do.
    ² Primitives are strings, booleans, numbers, symbols, null, and undefined. Primitives are immutable – they don’t have any own properties. The distinction between objects and primitives isn’t that important in JavaScript, but because they don’t have any own properties it doesn’t make sense to use them as prototypes. They also don’t count as instanceof anything for what I’m going to claim are historical reasons.
    ³ By default, that is. You can ask one or all of them not to be with Object.setPrototypeOf. There’s no reason to do this.