Search code examples
javascriptvariablesprototypehoisting

Property added to a JS object as a prototype is hoisted, whereas a prototype function is not


I am (or at least I thought I was) pretty much familiar with the concept of Hoisting in JavaScript.

Consider the following statements:

A function declaration will be hoisted along with its body, whereas a function expression will not; only the var statement will be hoisted.

Function declarations and function variables are always moved (‘hoisted’) to the top of their JavaScript scope by the JavaScript interpreter” - Berry Cherry

Now consider the following function:

function User() {
    this.name = "";
    this.life = 100;
    this.heal = function heal(player) {
        player.life+=5;
        console.log("\nHey" + player.name + "! Player " + this.name + " healed you for 5.");
    } 
}

... and the following users:

var Dan = new User("Danny");
var Liz = new User("Lizzy");

Say I want to add a new skill in the form of a prototype function to already defined users, like so (appended to the code):

User.prototype.uppercut = function uppercut(player) {
    player.life-=10;
    console.log("\nBaaam! " + player.name + ", player " + this.name + " uppercuted you for 10 damage.");
};

... and now, use said skill (add this before prototype):

Liz.uppercut(Dan);
Liz.uppercut(Dan);

... I will receive the following error:

Liz.uppercut(Dan);
    ^
TypeError: Liz.uppercut is not a function

Should I add a property to the User objects using a prototype, but accessing it in the code before the prototype declaration, it will work (it is hoisted):

console.log("Hey there " + Dan.name + "! You have " + Dan.mana + " remaining mana.");
User.prototype.mana = 100;

Basically this confirmed to me that the same hoisting principles which apply to functions & variables, also apply to prototypes.

Question no.1: Is this logic valid and if so, can you explain why?

Question no.2: If there a way to avoid this w/o moving the prototype expression above the prototype function call?

Thanks and have a good one!

Code snippet:

function User(name) {
    this.name = name;
    this.life = 100;
    this.heal = function heal(player) {
        player.life+=5;
        console.log("Hey" + player.name + "! Player " + this.name + " healed you for 5.");
    }
}

var Dan = new User("Danny");
var Liz = new User("Lizzy");

Liz.uppercut(Dan);
Liz.uppercut(Dan);

console.log(Liz.name + " you know have " + Liz.life + " life.");

User.prototype.mana = 100;
User.prototype.uppercut = function (player) {
    player.life-=10;
    console.log("Baaam " + player.name + "! Player " + this.name + " uppercuted you for 10 damage.");
};

console.log("Hey there " + Dan.name + "! You have " + Dan.mana + " remaining mana.");
Dan.mana = 200;
console.log("Hey there " + Dan.name + "! You have " + Dan.mana + " remaining mana.");


Solution

  • You are confusing variables and properties here. Variables are hoisted, properties are not.

    In your example, Dan is a variable, mana is a property of Dan.

    JavaScript handles undefined variables differently from undefined properties.

    Variables are hoisted when they are declared, by splitting off the left hand side of the declaration. i.e. var Dan = new User("Danny"); is split into two statements, var Dan; Dan = new User("Danny");. the var Dan is then hoisted to the top of the function. but the assignment stays in place.

    If your code contatined only Dan = new User("Danny");, you would receive a ReferenceError, because you would be trying to make an assignment to a variable that isn't declared. The declaration is missing, thus the variable was never hoisted.

    Properties, on the other hand, operate differently. Property accessors return the result of a hash lookup on the parent object. In the case of Dan.mana, the parent object is defined, so no ReferenceError, but mana is not a property of Dan, so the hash lookup returns undefined. No hoisting has occurred, because there is no variable declaration.

    Therefore, prototypes cannot be tied to hoisting, because they are strictly assignment operations. Even if the prototype is modified at the beginning of the function, it wouldn't be affected by hoisting, since hoisting only affects the declaration of the variable, not the assignment (which happens on the right side of the call).