立诚勿怠,格物致知
It's all about connecting the dots

JavaScript hosting

Reference: http://www.kenneth-truyers.net/2013/04/20/javascript-hoisting-explained/

What is hoisting?

In Javascript, you can have multiple var-statements in a function. All of these statements act as if they were declared at the top of the function. Hoisting is the act of moving the declarations to the top of the function.

Hoisting versus block-scope

In most programming languages there’s something called block-scope. Basically what it means is that every variable declared within a block is only visible within that block. The following code snippet in c# shows this behavior:


// C#
void mymthod()
{
 
    // code
    for (int i = 0; i < 10; i++)
    {
        int myvar = 5;
    }
 
    // more code
    Console.WriteLine(myvar);     // this will raise a compiler(编译器) error
    // myvar is not available here
     
}

In the snippet above, the variable myvar is only available inside the for-loop. That’s because c# has block-scope (just as VB.NET, Java and most other languages).

If we look at Javascript however, there are only two scopes (in ES5): global and function scope. So if we write an equivalent method in Javascript the result is quite different:


function mymethod() {
 
    for (var i = 0; i < 10; i++) {
        var myvar = 5;
    }
 
    alert(myvar); // Will alert 5
}
 
mymethod();

Because Javascript “hoists” the declaration of myvar to the top of the function this will actually alert ‘5’. The above code is functionally equivalent to this, which explains the behavior:


function mymethod() {
    var myvar;
    for (var i = 0; i < 10; i++) {
        myvar = 5;
    }
 
    alert(myvar); // Will alert 5
}
 
mymethod();

Unexpected behavior due to hoisting

As said, if you expect your variables to be block-scoped, you will get unexpected behavior and unexplainable errors. Let’s look at a few examples:

Variable hoisting

var myvar = "global variable";
 
function mymethod(){
    alert(myvar);   // expected "global variable", result: undefined
    var myvar = "local variable";
    alert(myvar); // local variable
}
 
mymethod();

Normally you would expect the first alert to display the value of the global variable since you haven’t overwritten it yet. However, since the declaration is actually hoisted to the top it overwrites it at the start of the function.

The following code is functionally equivalent:


var myvar = "global variable";
 
function mymethod(){
    var myvar;  // this is the same as var myvar = undefined;
    alert(myvar);   // undefined
    myvar = "local variable";
    alert(myvar); // local variable
}
 
mymethod();
Function hoisting

When we are declaring functions inside a function there are two patterns available: function expressions and function declarations:


function something(){  
    // function declaration
    function mymethod(){}
         
    // function expression
    var mysecondmethod = function() {};
     
    mymethod();
    mysecondmethod();
}

As you can see, both implementations are valid and you can call them using the same syntax. This might lead you to believe that they are in fact equivalent. However, there is a small difference when it comes to hoisting: function declarations get hoisted completely (name AND implementation) whereas function expressions get only the variable declaration hoisted and not the implementation. Let’s look at an example:


function mymethod(){
   alert("global mymethod");
}
 
function mysecondmethod(){
   alert("global mysecondmethod");
}
 
function hoisting(){
    alert(typeof mymethod);
    alert(typeof mysecondmethod);
     
    mymethod();         // local mymethod
    mysecondmethod(); // TypeError: undefined is not a function
     
    // mymethod AND the implementation get hoisted
    function mymethod(){
        alert("local mymethod");  
    }
     
    // Only the variable mysecondmethod get's hoisted
    var mysecondmethod = function() {
        alert("local mysecondmethod");  
    };
}
hoisting();

In this example you can see that, as with normal variables, just the presence of mymethod and mysecondmethod inside the function moves them to the top. However, mymethod (a function declaration) gets hoisted completely and therefore is available everywhere inside the function. On the other hand, mysecondmethod (a function expression) has only the var declaration hoisted, which prevents the global method from being seen but is not usable as a function until it’s assigned.

Best practices

From the examples, it’s obvious that the hoisting behavior Javascript exhibits can lead to confusing results and make it difficult to read code. Therefore you should follow some best practices.

What you really want when you read code, is that it clearly states what it does. Since you know it’s moving the variable declarations to the top, you should do this explicitly, so it’s obvious that they’re there. Consider the following examples (the first one is the “wrong” one, the second example show a better version):


function badhoistingpractices(){
    var myvar = getFromWhere();
       
    if (myvar == 5){
        var mysecondvar = 20;
        somefunction(mysecondvar);
    }    
}
 
function betterhoistingpractices(){
    var myvar, mysecondvar;
     
    myvar = getFromWhere();
       
    if (myvar == 5){
        mysecondvar = 20;
        somefunction(mysecondvar);
    }    
}

Sidenote

For completeness sake, I need to mention that behind the scenes things are actually implemented a little bit different. The ECMAScript standard does not define hoisting as “the declaration gets moved to the top of the function”. Handling code happens in two steps: the first one is parsing and entering the context and the second one is runtime code execution. In the first step variables, function declarations and formal parameters are created. In the second stage function expressions and unqualified identifiers (undeclared variables) are created. However for practical purposes we can adopt the concept of hoisting.

Conclusion

With this post I tried to demystify some of the strange behavior you sometimes may experience. I hope by reading this and applying the concept you can avoid some frustration in the future. Following the best practices will get you a long way, and remember: it’s Javascript, it IS different.

Some examples

Question: Can you please tell me why following throws error:


function hello(){
  console.log(x); //error here
  x = 3.14; //global variable
  console.log(x);
}
hello();

Answer: Because x is not hoisted. If you don't declare x with a var-keyword, it will not be hoisted. When it reaches the second line (assuming it does) it just assigns it to a global variable. But because in the first line it tries to print something that doesn't yet exist, it throws an error. To clarify this, you can rewrite your example like this:


function hello(){
    console.log(window.x); //error here
    window.x = 3.14; //global variable
    console.log(window.x);
}
hello();

Or similar, but instead of a global variable with a variable on an object. (global variables are just that, a variable on the window object, which is global):


function hello(){
    var myObject = {};
    console.log(myObject.x); //error here
    myObject.x = 3.14;
    console.log(myObject.x);
}
hello();

Question: Can you explain this:


var is_android = true;
if(is_android) {
    function foo() {
        console.log('I am Android');
    }
} else {
    function foo() {
        console.log('I am NOT Android');
    }
}
foo();

Answer: Remember "function declarations get hoisted completely (name AND implementation)" so after hoisting the function actually looks something like this:


var is_android = true;

/* hoisted upto here */
function foo() {
    console.log('I am Android');
}

/* hoisted upto here */
function foo() {
    console.log('I am NOT Android');
}

if (is_android) {
/* hoisted out of here */
} else {
/* hoisted out of here */
}

foo();

The functions have been hoisted to the top and by effectively declaring the function twice the last one wins and overwrites the implemenation of the first.

Question: Does this mean things like variable declarations in for loops get hoisted, too? So declaring i in a for loop once will result in it getting hoisted to the top and any subsequent call to i in a for loop under the first one (but not inside) will have the same final value as i? Does that make sense? Guess I could try it out in dev tools, but this is more fun.

Answer: Yes, variables only have function scope, so in a loop they are shared as well:


function something(){
    for (var i=0;i<10;i++){
    }
    alert(i); // will alert 10    
}
赞(0) 打赏
文章名称:《JavaScript hosting》
文章链接:https://www.orzzone.com/javascript-hosting.html
商业联系:yakima.public@gmail.com

本站大部分文章为原创或编译而来,对于本站版权文章,未经许可不得用于商业目的,非商业性转载请以链接形式标注原文出处。
本站内容仅供个人学习交流,不做为任何投资、建议的参考依据,因此产生的问题需自行承担。

评论 抢沙发

觉得文章有用就打赏一下文章作者

非常感谢你的打赏,我们将继续给力提供更多优质内容!

支付宝扫一扫打赏

微信扫一扫打赏

登录

找回密码

注册