Scope
Scope is the scope of function of a variable and function. All variables declared in a function in JavaScript are always visible in the function body. There are global scopes and local scopes in JavaScript, but there is no block-level scope. The priority of local variables is higher than that of global variables. Through several examples, we can understand the "unspoken rules" of scope in JavaScript (these are also questions that are often asked in front-end interviews).
1. Variable declaration in advance
Example 1:
var scope="global";function scopeTest(){ console.log(scope); var scope="local" }scopeTest(); //undefinedThe output here is undefined and there is no error. This is because the declarations in the function we mentioned above are always visible in the function body. The above function is equivalent to:
var scope="global";function scopeTest(){ var scope; console.log(scope); scope="local" }scopeTest(); //localNote that if var is forgotten, the variable is declared as a global variable.
2. No block-level scope
Unlike other languages we use commonly, there is no block-level scope in Javascript:
function scopeTest() { var scope = {}; if (scope instanceof Object) { var j = 1; for (var i = 0; i < 10; i++) { //console.log(i); } console.log(i); //output 10 } console.log(j);//output 1}In JavaScript, the scope of function of variables is function-level, that is, all variables in the function are defined in the entire function, which also brings some "unspoken rules" that we will encounter if we are not careful:
var scope = "hello";function scopeTest() { console.log(scope);//① var scope = "no"; console.log(scope);//②}The value output at ① was actually undefined, which is crazy. We have defined the value of the global variable. Shouldn't this place be hello? In fact, the above code is equivalent to:
var scope = "hello";function scopeTest() { var scope; console.log(scope);//① scope = "no"; console.log(scope);//②}Declare early and global variables have lower priority than local variables. According to these two rules, it is not difficult to understand why the output is undefined.
Scope chain
In JavaScript, each function has its own execution context. When the code is executed in this environment, a scope chain of variable objects will be created. The scope chain is an object list or object chain, which ensures orderly access to variable objects.
The front end of the scope chain is the variable object of the current code execution environment, which is often called "active object". The search for variables starts from the object of the first chain. If the object contains variable attributes, then the search will be stopped. If not, the search will continue to search for the superior scope chain until the global object is found:
The search for scope chains step by step will also affect the performance of the program. The longer the scope chain of variables, the greater the impact on performance. This is also a major reason why we try to avoid using global variables.
Closure
Basic concepts
Scope is a prerequisite for understanding closures. Closures refer to the ability to access variables in the external scope within the current scope.
function createClosure(){ var name = "jack"; return { setStr:function(){ name = "rose"; }, getStr:function(){ return name + ":hello"; } }}var builder = new createClosure();builder.setStr();console.log(builder.getStr()); //rose:helloThe above example returns two closures in the function, both of which maintain references to the external scope, so variables in the external function are always accessible wherever they are called. Functions defined inside a function will add the active object of the external function to its own scope chain. Therefore, in the above example, the internal function can access the properties of the external function through the internal function. This is also a way for javascript to simulate private variables.
Note: Since closures will have additional scopes of functions (internal anonymous functions carry scopes of external functions), closures will occupy more memory space than other functions, and excessive use may lead to increased memory usage.
Variables in closures
When using closures, due to the influence of the scope chain mechanism, the closure can only obtain the last value of the internal function. One side effect of this is that if the internal function is in a loop, the value of the variable is always the last value.
//This instance is not reasonable and has certain delay factors. This is mainly to illustrate the problems in the closure loop function timeManage() { for (var i = 0; i < 5; i++) { setTimeout(function() { console.log(i); },1000) }; }The above program does not input numbers 1-5 as we expected, but outputs 5 all 5 times. Let’s take a look at another example:
function createClosure(){ var result = []; for (var i = 0; i < 5; i++) { result[i] = function(){ return i; } } return result;}Calling createClosure()[0]() returns 5, and createClosure()[4]() returns value is still 5. From the above two examples, we can see the problem that closures exist when using internal functions with loops: because the scope chain of each function stores active objects for external functions (timeManage, createClosure), they all refer to the same variable i. When the external function returns, the value of i at this time is 5, so the value of each internal function i is also 5.
So how to solve this problem? We can force the return of the expected result through an anonymous wrapper (anonymous self-executing function expression):
function timeManage() { for (var i = 0; i < 5; i++) { (function(num) { setTimeout(function() { console.log(num); }, 1000); })(i); }}Or return an anonymous function assignment in the closure anonymous function:
function timeManage() { for (var i = 0; i < 10; i++) { setTimeout((function(e) { return function() { console.log(e); } })(i), 1000) }}//timeManager(); Output 1,2,3,4,5function createClosure() { var result = []; for (var i = 0; i < 5; i++) { result[i] = function(num) { return function() { console.log(num); } }(i); } return result;}//createClosure()[1]() output 1; createClosure()[2]() output 2Whether it is an anonymous wrapper or nested anonymous functions, in principle, since the function is passed by value, the value of the variable i will be copied to the actual parameter num, and an anonymous function is created inside the anonymous function to return num, so that each function has a copy of num, which will not affect each other.
This in closure
Pay special attention when using this in closures, as a little carelessness may cause problems. Usually we understand that this object is bound by function at runtime. In the global function, this object is a window object. When the function is called as a method in the object, this is equal to this object (TODO does a sorting process about this). Since the scope of anonymous functions is global, this closure usually points to the global object window:
var scope = "global";var object = { scope:"local", getScope:function(){ return function(){ return this.scope; } }}Calling object.getScope()() returns the value global instead of the local we expected. We said earlier that the internal anonymous functions in the closure will carry the scope of the external function, so why not get this of the external function? When each function is called, this and arguments will be automatically created. When searching for internal anonymous functions, they search for the variables we want in the active object. Therefore, stop searching for external functions, and it is never possible to directly access variables in external functions. In short, when a function is called as a method of an object in a closure, it is important to pay special attention that this in the anonymous function within the method points to a global variable.
Fortunately, we can solve this problem very simply, just store this in the scope of the external function in a variable that can be accessed by a closure:
var scope = "global";var object = { scope:"local", getScope:function(){ var that = this; return function(){ return that.scope; } }}object.getScope()()() returns the value local.Memory and performance
Since the closure contains the same scope chain reference as the function runtime context, it will have a certain negative effect. When the active object and the runtime context in the function are destroyed, the active object cannot be destroyed because there is still a reference to the active object, which means that the closure occupies more memory space than ordinary functions, and may also cause memory leakage in the IE browser, as follows:
function bindEvent(){ var target = document.getElementById("elem"); target.onclick = function(){ console.log(target.name); } }In the above example, the anonymous function generates a reference to the external object target. As long as the anonymous function exists, the reference will not disappear, and the target object of the external function will not be destroyed, which creates a circular reference. The solution is to reduce circular references to external variables by creating a copy of target.name and manually reset the object:
function bindEvent(){ var target = document.getElementById("elem"); var name = target.name; target.onclick = function(){ console.log(name); } target = null; }If there is access to external variables in the closure, the search path for identifiers will undoubtedly be added, and under certain circumstances, this will also cause performance losses. We have mentioned earlier: try to store external variables into local variables to reduce the search length of scope chains.
Summary: Closures are not unique to JavaScript, but they have their own unique manifestations in JavaScript. Using closures, we can define some private variables in JavaScript, and even imitate block-level scopes. However, during the use of closures, we also need to understand the existing problems in order to avoid unnecessary problems.