In depth understanding of Javascript scope and variable promotion

  • 2020-03-30 00:47:12
  • OfStack

What is the result of the following procedure?


var foo = 1;
function bar() {
 if (!foo) {
  var foo = 10;
 }
 alert(foo);
}
bar();

That's 10;

What about the next one?


var a = 1;
function b() {
 a = 10;
 return;
 function a() {}
}
b();
alert(a);

The result is 1.

Did you freak out? What happened? This can be strange, dangerous, confusing, and in fact a very useful and impressive feature of the javascript language. For this performance, I do not know to have a standard name, but I like the term: "Hoisting (variable lift)". This article will give you an introductory look at this mechanism, but first let's have some necessary understanding of the scope of javascript.

Javascript scope

One of the most confusing areas for Javascript beginners is scope; In fact, it's not just beginners. I've met experienced javascript programmers who don't understand scope very well. Javascript scope is confusing because its program syntax itself looks like the C family of languages, like the following C program:


#include <stdio.h>
int main() {
 int x = 1;
 printf("%d, ", x); // 1
 if (1) {
  int x = 2;
  printf("%d, ", x); // 2
 }
 printf("%dn", x); // 1
}

The output is 1, 2, 1, because the C family of languages has block scope, when the program control enters a block, such as an if block, the variables that only apply to that block can be declared without affecting the outside scope of the block. But in Javascript, that doesn't work. Take a look at the following code:

var x = 1;
console.log(x); // 1
if (true) {
 var x = 2;
 console.log(x); // 2
}
console.log(x); // 2

It's going to be 1, 2, 2. Because javascript is a function scope. This is the biggest difference from the c family language. The if in this program does not create a new scope.

For many C, C ++, Java programmers, this is not what they expect or welcome. Fortunately, based on the flexibility of javascript functions, there is room for flexibility. If you have to create temporary scopes, you can do this:


function foo() {
 var x = 1;
 if (x) {
  (function () {
   var x = 2;
   // some other code
  }());
 }
 // x is still 1.
}

This method is flexible and can be used wherever you want to create temporary scopes. Not just inside the block. However, I highly recommend that you take the time to understand the scope of javascript. It's useful and one of my favorite javascript features. If you understand the scope, then variable promotion makes more sense to you.

Variable declaration, naming, and promotion

In javascript, variables are scoped in four basic ways:

The & # 8226; Language built-in: this and arguments in all scopes; Arguments are not visible in the global scope.

The & # 8226; 2. Formal parameters: the formal parameters of the function will be taken as part of the scope of the function body;

The & # 8226; Function foo(){};

The & # 8226; Var foo;

Function declarations and variable declarations are always quietly "promoted" by the interpreter to the very top of the method body. This means, like the following code:


function foo() {
 bar();
 var x = 1;
}

It's actually interpreted as:

function foo() {
 var x;
 bar();
 x = 1;
}

Whether or not the block that defines the variable can be executed. The following two functions are actually the same:

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 the variable assignment is not promoted, but the declaration is. However, the declaration of the function is a little different, and the body of the function will be promoted with it. Note, however, that functions can be declared in two ways:

function test() {
 foo(); // TypeError "foo is not a function"
 bar(); // "this will run!"
 var foo = function () { //The variable points to the function expression
  alert("this won't run!");
 }
 function bar() { //The function declaration function is called bar
  alert("this will run!");
 }
}
test();

In this example, only functional declarations are promoted along with the function body. Foo's declaration will be promoted, but the body it points to will only be assigned at execution time.

The above covers some of the basics of ascension, and they don't seem so confusing. However, in some special situations, there is a certain complexity.

Variable resolution order

The most important thing to keep in mind is the variable resolution order. Remember the 4 ways I gave you earlier to name it into scope? The order in which the variables are resolved is the order in which I listed them.


<script>
function a(){ 
}
var a;
alert(a);//Print out the body of a
</script>
<script>
var a;
function a(){ 
}
alert(a);//Print out the body of a
</script>
//But notice the difference between the following two ways:
<script>
var a=1;
function a(){ 
}
alert(a);//Print out 1
</script>
<script>
function a(){ 
}
var a=1;
alert(a);//Print out 1
</script>

Here are three exceptions:

The built-in name arguments behaves strangely, as if it should be declared after the function's formal argument, but before the function's declaration. This means that if there are arguments in the parameter, it takes precedence over the built-in one. This is a bad feature, so avoid arguments in parameters;

Defining this variable anywhere can cause syntax errors, which is a good feature;

If more than one formal parameter has the same name, the last one has a priority, even if it is undefined when it is actually run.

Naming functions

You can give a function a name. If so, it is not a function declaration, and the specified function name in the function body definition (if any) is not promoted, but is ignored. Here's some code to help you understand:


foo(); // TypeError "foo is not a function"
bar(); // valid
baz(); // TypeError "baz is not a function"
spam(); // ReferenceError "spam is not defined"
var foo = function () {}; //Foo points to an anonymous function
function bar() {}; //Function declaration
var baz = function spam() {}; //Named function, only baz is promoted, spam is not promoted.
foo(); // valid
bar(); // valid
baz(); // valid
spam(); // ReferenceError "spam is not defined"

How to write code

Now that you understand scope and variable promotion, what does this mean for javascript coding? Most importantly, always define your variables with var. And I highly recommend that you always have only one var declaration in a scope for a name. If you do this, you won't have scope and variable lift problems.

What is the language specification

I find the ECMAScript reference documentation always useful. Here's what I found about scope and variable promotion:

If a variable is declared in a function body class, it is a function scope. Otherwise, it is a global scope (as a property of global). Variables will be created when execution enters scope. Blocks do not define new scopes, only function declarations and programs create new scopes. Variables are initialized to undefined when created. If there is an assignment in a variable declaration statement, the assignment occurs only when it is executed, not when it is created.

I expect this article to bring some light to those who are a bit confused about javascript. I did my best to avoid further confusion. If I say something wrong or ignore something, please let me know.

Translators supplement

A friend reminded me of the naming function promotion problem under the global scope of IE:

Here's how I tested it when I translated the article:


<script>
functiont(){
spam();
var baz = function spam() {alert('this is spam')};
}
t();
</script>

This method, which refers to the promotion of named functions in non-global scope, is consistent with the performance under ie and ff. I will change it to:

<script>
spam();
var baz = function spam() {alert('this is spam')};
</script>

Therefore, spam can be implemented under ie, but not under ff. This indicates that different browsers are different in handling this detail.

This question also led me to think about two other problems: 1. For variables acting on the global scope, var is different from non-var. Without the method of var, the variables will not be promoted. For example, the following two programs, the second one will report an error:


<script>
alert(a);
var a=1;
</script>


<script>
alert(a);
a=1;
</script>

2: local variables created in eval don't get promoted (and they don't).

<script>
var a = 1;
function t(){
 alert(a);
 eval('var a = 2');
 alert(a);
}
t();
alert(a);
</script>


Related articles: