4

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.");
iamdanchiv
  • 4,052
  • 4
  • 37
  • 42
  • 3
    Your code works just fine (make a snippet and you'll see). The error is somewhere else. Also, 'hoisting' only applies to variables, object properties are not hoisted in any way. – georg Mar 26 '17 at 13:46
  • Thanks for the reply @georg! Indeed I forgot to mention that I was calling the **uppercut** method before the prototype implementation. I edited the post. Also, when you are saying that _object properties are not hoisted in any way_, what are you referring to exactly? I added the **mana** snippet (forgot previously) to prove that the object property added via prototype is indeed hoisted, whereas the function expression is not. – iamdanchiv Mar 26 '17 at 14:28
  • Could you convert your examples to a runnable snippet? (Use the snippet button in the editor). Your question as it stands it pretty hard to follow. – georg Mar 26 '17 at 14:53
  • 1
    Doesn’t the `console.log` say “You have undefined remaining mana?” – Thai Mar 26 '17 at 14:54
  • @Thai of course it says _undefined_. But that proves that the object property has indeed been **hoisted**. You can afterwards go and change the value for each User instance. – iamdanchiv Mar 26 '17 at 15:03
  • @georg added the snippet at the bottom. So basically my question is: Are prototype functions subject to the same **hoisting** rules as regular function expressions and why? – iamdanchiv Mar 26 '17 at 15:21
  • @iamdanchiv Hoisting happens for variable declarations and nothing else. – Bergi Mar 26 '17 at 15:54
  • @iamdanchiv The same thing applies for prototype functions. `Liz.uppercut` gives you `undefined`, which you then call, yielding the “undefined is not a function” error. – Thai Mar 26 '17 at 16:48

3 Answers3

2

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).

Claies
  • 22,124
  • 4
  • 53
  • 77
0

No, assignments (to prototype properties or other) are not hoisted.

The code

function User() { this.name = ""; … }
var Dan = new User("Danny");
console.log("Hey there " + Dan.name + "! You have " + Dan.mana + " remaining mana.");
User.prototype.mana = 100;

does not work, it will log Hey there ! You have undefined remaining mana..

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
0

Accessing a property that is not defined on the object will give you undefined. There is no hoisting rule in play here.

  • Liz.mana will give you undefined.
  • Liz.uppercut will also give you undefined.
  • Liz.agility will also give you undefined.

Calling Liz.uppercut(Dan) throws the error because you are trying to call Liz.uppercut, which is undefined, as a function. That leads to the “undefined is not a function” error.

Thai
  • 10,746
  • 2
  • 45
  • 57