📜 ⬆️ ⬇️

JavaScript scope and “raising” variables and function declarations

Do you know what value will render this code in javascript?
var foo = 1; function bar() { if (!foo) { var foo = 10; } alert(foo); } bar(); 


If you are surprised that “10” is displayed, then the following code will confuse you at all:
 var a = 1; function b() { a = 10; return; function a() {} } b(); alert(a); 

In this case, the browser will display "1". So what actually happens? Although this behavior seems strange, dangerous and confusing, in fact it is a very powerful and expressive JavaScript tool. I do not know if there is an official name for this behavior, but I like to use the term “lifting” (“hoisting”). In this article I will try to shed light on this mechanism of the language, but first let's talk about the scope in JavaScript.

JavaScript scope


One of the confusing reasons for newbies is the scope. Generally, not only beginners. I have met many experienced JavaScript developers who do not understand the scope mechanism in JavaScript. The reason is that JavaScript looks very similar to any other C-like language.
Let's look at the following C code:
 #include <stdio.h> int main() { int x = 1; printf("%d, ", x); // 1 if (1) { int x = 2; printf("%d, ", x); // 2 } printf("%d\n", x); // 1 } 


This program will output 1, 2, 1, because C and all other C-like languages ​​implement scopes at the level of code blocks . When a new block of code is executed, for example the if condition, the new variables declared in it will not affect the variables of the outer scope.
But not in the case of javascript. Try running this code in Firebug:
 var x = 1; console.log(x); // 1 if (true) { var x = 2; console.log(x); // 2 } console.log(x); // 2 

This time the numbers 1, 2, 2 will be displayed. This is due to the fact that JavaScript uses the scope at the function level . This is not at all what we used to see in programming languages ​​like C. Blocks of code, like the one that we have immediately after the if, do not create a new scope. Only functions create new scopes.
For many programmers who are used to C, C ++, C # or Java, this behavior is very unexpected and unpleasant. Fortunately, due to the flexibility of JavaScript functions, you can work around this problem. To create a temporary scope within a function, it’s enough to do the following:
 function foo() { var x = 1; if (x) { (function () { var x = 2; // -  }()); } // x   1. } 

This approach is quite flexible and can be used wherever you need a temporary scope, not only within blocks of code. But I insist that you still spend your time to understand the implementation of the scope in JavaScript. This is a pretty powerful language feature that I really like. If you understand the scope, it will be easier for you to understand the "raising" of variables and function declarations.
')

Declarations, naming, and “lifting” variables and functions


In JavaScript, there are four basic ways for an identifier to appear in scope:
  1. Internal mechanisms of the language : for example, this and arguments are available in all scopes.
  2. Formal parameters : functions can have named formal parameters, the scope of which is limited to the function body.
  3. Function declarations: declared in the form function foo () {}.
  4. Variable declarations : for example, var foo ;.

The JavaScript interpreter always invisibly moves (“raises”) the declarations of functions and variables to the beginning of the scope. The formal parameters of the functions and the built-in variables of the language are obviously initially at the beginning. This means that this code:
 function foo() { bar(); var x = 1; } 

actually interpreted as:
 function foo() { var x; bar(); x = 1; } 

It turns out that it does not matter whether the line in which the declaration takes place will be executed at all. The following two functions are equivalent:
 function foo() { if (false) { var x = 1; } return; var y = 1; } function foo() { var x, y; if (false) { x = 1; } return; y = 1; } 

Note that assigning values ​​to variables does not rise with their declaration. Only variable declarations are raised. In the case of functions, the entire function rises. There are two main ways to declare a function, let's consider them:
 function test() { foo(); // TypeError "foo is not a function" bar(); // "this will run!" var foo = function () { //  ,    'foo' alert("this won't run!"); } function bar() { //     'bar' alert("this will run!"); } } test(); 

in this case, only the bar function is raised. The identifier “foo” also rises, but not an anonymous function — it remains in place.

So we described the main points of "raising" variables and functions. Of course, JavaScript wouldn't be by itself if there were no special cases in which everything is a bit more complicated.

Name resolution


The most important special case to keep in mind is the order of name resolution. Remember, there are four ways that identifiers appear in scope. It is in the order in which I mentioned them that name resolution occurs. In general, if a name is already defined, it will never be redefined by another entity with the same name. That is, the function declaration takes precedence over declarations of a variable with the same name. But this does not mean that the assignment of a variable to a value does not replace a function, it will simply be ignored.
There are a few exceptions:

Named Functional Expressions


You can give names to functions defined using functional expressions using the syntax of function definitions. This does not lead to the declaration of the function, and therefore the name of the function is neither added to the scope nor rises along with the function body to the beginning of the scope. Here are a few lines to illustrate what I mean:
 foo(); // TypeError "foo is not a function" bar(); //  baz(); // TypeError "baz is not a function" spam(); // ReferenceError "spam is not defined" var foo = function () {}; //    ( 'foo') function bar() {}; //   ( 'bar'   ) var baz = function spam() {}; //    (  'baz') foo(); //  bar(); //  baz(); //  spam(); // ReferenceError "spam is not defined" 

How to write code with such knowledge


So, now you understand the scope and "raising" of variables and function declarations. What does this mean in relation to writing JavaScript code? The most important thing is to always declare your variables using var. I insist that you have exactly one var per scope and that it is located at its beginning. If you force yourself to do this, you will never have any problems with elevation. However, this can make it difficult to keep track of variables that are declared in the current scope. I recommend using JSLint with the onevar option enabled to force you to do so. If you do this, your code will look something like this:
 /*jslint onevar: true [...] */ function foo(a, b, c) { var x = 1, bar, baz = "something"; } 


What does the standard say


It is quite useful to contact the ECMAScript standard directly to understand how everything works. Here is what it says about variable declaration and scope (section 12.2.2 ):
If the variable instruction is found inside the Function Declaration, the variables are declared inside the local scope for this function as described in section 10.1.3. Otherwise, they are declared in the global scope (that is, they are created as fields of the global object as described in section 10.1.3) using the {DontDelete} property attributes. Variables are created when entering the execution area. The block does not define a new execution area. Only the Program and the Ad Functions create a new scope. Variables are initialized when they are set to undefined. The variable for which the Initializer is defined is assigned the value of its Assignment Expression at the moment of executing the Variable Instruction, and not at the moment of creating the variable.

I hope this article has shed some light on the JavaScript feature that so often confuses many. I tried to be as consistent as possible so as not to confuse you even more. If I am mistaken or have missed something, please let me know.

Source: https://habr.com/ru/post/127482/


All Articles