I'm watching ES2015 practice recently, there is a saying in it that says
There is no block-level scope in JavaScript
You may not understand this issue, let's take a look at an example first
var a = []for(var i = 0; i < 10; i++){ a[i] = function(){ console.log(i); }}a[6]();I think many people think the result of this question is 6, but unfortunately, the answer is 10. Try something else. a[7](), a[8](), and a[8]() all have 10!!
Since JS often wraps the primitive variables into corresponding objects when processing, for example, for var str = "hello world";str.slice(1).
The real process of JS is probably var str = "hello world";new String(str).slice(1). Such a process may cause trouble for us to understand the problem.
In order to explain this problem here, i belongs to the number type in the primitive type, I explicitly declare it as the Number type. Since the assignment process of the basic type is to reapply memory and modify the direction of the variable, we also use the process of re-new Number object to simulate this process. The modified code is as follows:
var a = []var i = new Number(0);for(; i < 10; i = new Number(i+1)){ a[i] = function(){ console.log(i.toString()); }}a[6](); // 10a[7](); // 10a[8](); // 10a[9](); // 10a[9](); // 10Let's take a look at the relative memory addresses of these variables in combination with a program.
(function() { var id = 0; function generateId() { return id++;}; Object.prototype.id = function() { var newId = generateId(); this.id = function() { return newId; }; return newId; };})();var a = []var i = new Number(0);console.log(i.id());// 0for(; i < 10; i = new Number(i+1),i.id()){ a[i] = function(){ console.log(i.id()); console.log(i.toString()); }}a[6](); // 10 10a[7](); // 10 10a[8](); // 10 10a[9](); // 10 10console.log(i.id())// 10Here we have indeed simulated the entire "assignment" effect of our i, and the relative address of i changed from 0 to 10 (in the end, it needs to be added once before it can jump out of the for loop).
While looking at the relative address of i, we found a problem: when the function corresponding to a[x](x:0~9) is executed, the relative address of i referenced is 10. Why?
Here we will involve the block-level scope issue. Here we quote a passage from Ruan Yifeng in the introduction to ES6:
ES5 only has global scope and function scope, but no block-level scope.
ES5 is the most widely used version of JS. This sentence says that in javascript, there is no block scope. There is only global scope and block-level scope.
How to understand? For example
for(var i = 0;i < 10; i++){ console.log(i);}console.log(i);//10console.log(window.i);//10Intuitively, we think that the for loop is a code block and should belong to a block-level scope. However, not only can output 0 to 9 normally, but it can also output 10 externally in the for loop. At the same time, we found that although we defined i on the for loop, it seems that i is hanging on the global window object (if it is the execution environment of nodejs, it will be hanging on the global object)
Therefore, blocks like for loops in JavaScript will not have the effect of a block-level scope. Defining variables in code blocks such as for loops is no different from directly defining variables in the current scope.
But we can isolate the scope through functions:
(function(){ for(var i = 0;i < 10; i++){ console.log(i); } console.log(i);})()console.log(i);////i is not definedAt the same time, if console.log(window.i); is executed, the undefined result will be obtained. Here we use an immediate execution function to form a scope. It plays a role similar to a code block. After this function scope, the variable i can no longer be accessed. However, i can be accessed at any time within the function scope.
Go back to the previous question, and then we will understand it in combination with only the global scope and block-level scope in JavaScript. In the for loop, the i defined must be defined in the current scope, that is, the window scope. In the loop body, we assign a function to a[i]. When we execute this function, the situation is as follows:
I do not exist in the function, so we follow the scope chain to find i. The i we output at this time is this i. Since i jumps out of the last +1 of the loop, i becomes 10, so the output result is always 10. But what we really need is not the last i, but i in the intermediate process. If we want to solve this problem, we need to put aside the variable i (because the last i inevitably becomes 10). We need to let the function corresponding to a[0] refer to the value 0, and let the function corresponding to a[1] refer to the value 1. As shown in the figure below:
Go back to our previous code.
The arrow in the figure shows that we can access i (0~9). Here, because the for loop does not form a block-level scope by itself, we access the i defined by the for loop when we follow the scope chain.
Here we wrap our code with an immediate execution function, which can form a scope, and we pass the value i for it. The following:
var a = []var i = new Number(0);console.log(i.id());// 0for(; i < 10; i = new Number(i+1),i.id()){ (function(i){ a[i] = function(){ console.log(i.id()); console.log(i.toString()); } })(i);a[6](); // 6 6a[7](); // 7 7a[8](); // 8 8a[9](); // 9 9console.log(i.id());// 10}Since this immediate execution function refers to the numerical value 0~9, when we execute function a[i], we will first find the scope of the immediate execution function along the scope chain. The immediate execution function maintains the numerical reference from 0~9, and we can correctly output the value of i in the function a[i]. Through the execution result, we can see that not only the execution result is correct, but the relative memory address of the value we reference is also correct. Then we change the Number object that was originally explicitly declared for testing. As follows:
var a = [];for(var i = 0; i < 10; i++){ (function(i){ a[i] = function(){ console.log(i); } })(i);}Finally, let's take a look at the ES6 syntax recommended using let instead of var and compiling and generating ES5 code through baable:
//ES6 code var a = []for(let i = 0; i < 10; i++){ a[i] = function(){ console.log(i); }}a[6]();//babel compiled and generated ES5 code "use strict";var a = [];var _loop = function _loop(i) { a[i] = function () { console.log(i); };};for (var i = 0; i < 10; i++) { _loop(i);}a[6]();Let's see if our solution is very similar to the ES6 solution. Here our immediate execution function is equivalent to the execution of _loop function and _loop(i) in the generated ES5 code.