In depth understanding of javascript scopes and closures

  • 2020-03-30 03:59:52
  • OfStack

scope

Scope is the scope of a variable and function, javascript function in the statement of all variables in the function body is always visible, has global scope in javascript and local scope, but there is no block level scope, the local variable priority is higher than the global variables, through a few examples to get to know the javascript in the scope of the "hidden rules" (these are frequently asked questions on the front end the interview).

1. Declare variables in advance
Example 1:


var scope="global";
function scopeTest(){
  console.log(scope);
  var scope="local" 
}
scopeTest(); //undefined

The output here is undefined, and there is no error, because the declaration in the function we mentioned earlier is always visible in the function body, and the above function is equivalent to:


var scope="global";
function scopeTest(){
  var scope;
  console.log(scope);
  scope="local" 
}
scopeTest(); //local

Note that if you forget var, the variable is declared as a global variable.

2. No block-level scope

Unlike most languages we use, 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); //The output of 10
  }
  console.log(j);//Output 1

}

In javascript, the scope of variables is at the function level, that is, all variables in a function are defined in the entire function, which brings us to some "unwritten rules" that we may encounter without paying much attention to:


var scope = "hello";
function scopeTest() {
  console.log(scope);// 1. 
  var scope = "no";
  console.log(scope);// 2. 
}

The output value in is undefined. It's insane. We've already defined the value of the global variable. In fact, the above code is equivalent to:


var scope = "hello";
function scopeTest() {
  var scope;
  console.log(scope);// 1. 
  scope = "no";
  console.log(scope);// 2. 
}

Given the fact that global variables are declared ahead of time and local variables have a lower priority, it's not hard to see why the output is undefined.

The scope chain

In javascript, each function has its own execution context. When the code is executed in this environment, the scope chain of variable objects is created. The scope chain is a list of objects or a chain of objects, which guarantees the orderly access of variable objects.
The front end of the scope chain is the variable object of the current code execution environment, which is often referred to as the "active object". The variable search will start from the first object in the chain. If the object contains variable properties, the search will stop.

Step by step lookup of scope chain will also affect the performance of the program. The longer the scope chain of variables is, the greater the impact on the performance. This is also the main reason why we try to avoid using global variables.

closure

Basic concept
Scope is a prerequisite for understanding closures, which are variables in an external scope that are always accessible 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:hello

The above example returns two closures in a function, both of which maintain references to the external scope, so variables in the external function can always be accessed wherever the call is made. A function defined inside a function adds an active object of an external function to its scope chain, so the properties of an external function can be accessed through an internal function in the example above, which is also a way for javascript to simulate private variables.

Note: because closures have extra scope for accompanying functions (internal anonymous functions carry scope for external functions), they take up more memory than other functions, and excessive usage can lead to increased memory usage.

Variables in closures

When using closures, due to the scoped chain mechanism, the closure can only get the last value of the inner function, a side effect of which is that if the inner function is in a loop, the value of the variable is always the last value.


  //This example is not very reasonable and has a delay factor, mainly to illustrate the problems in closure loops
  function timeManage() {
    for (var i = 0; i < 5; i++) {
      setTimeout(function() {
        console.log(i);
      },1000)
    };
  }

The above program did not input 1-5 Numbers as we expected, but output 5 all 5 times. Here's another example:


function createClosure(){
  var result = [];
  for (var i = 0; i < 5; i++) {
    result[i] = function(){
      return i;
    }
  }
  return result;
}

The call to createClosure()[0]() returns 5, and the return value to createClosure()[4]() is still 5. Through the above two examples can be seen that the closure with the cycle of internal functions when using the problem: because each function are preserved in the scope of the chain to external function (timeManage, createClosure) of the active object, therefore, they all refer to the same variable I, when the outer function returns, this time I value of 5, so within each function value is also I to 5.
So how to solve this problem? We can use the anonymous wrapper (anonymous self-executing function expression) to force the return of the expected result:


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 a closed 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,5
function 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] 2 () output

Both the anonymous wrapper and the nested anonymous functions are in principle passed by value, so the value of the variable I is copied to the argument num, and an anonymous function is created inside the anonymous function to return num, so that each function has a copy of num, without affecting each other.

This in the closure

Be careful when using this in closures; a little carelessness can cause problems. In general, we understand that this object is bound at runtime based on functions. In the global function, this object is the window object, and when the function is called as a method in the object, this is equal to this object. Since the scope of the anonymous function is global, the this of the 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 inner anonymous function in the closure would carry the scope of the outer function, so why didn't we get the this of the outer function? When each function is called, this and arguments are automatically created. When the internal anonymous function is searching, it finds the variables we want in the active object, so it stops looking for variables in the external function, and it will never be possible to directly access variables in the external function. In summary, when a function in a closure is called as a method of an object, it is important to note that the this of an anonymous function within that method refers to a global variable.

Fortunately, we can easily solve this problem by simply storing this in an external function scope 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()() The return value is local . 

Memory and performance

Due to the closure and functions contained in the run-time context in the same scope chain reference, therefore, will produce certain negative effect, when the function of active objects and the run-time context is destroyed, because there are still necessary to the active object reference, lead to the active object cannot be destroyed, which means that the closure than normal function takes up more memory space, under the Internet explorer browser may also lead to a memory leak problems, 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, resulting in a circular reference. The solution is to reduce circular references to external variables and manually reset objects by creating a copy of target.name:


 function bindEvent(){
  var target = document.getElementById("elem");
  var name = target.name;
  target.onclick = function(){
    console.log(name);
  }
  target = null;
 }

If there is access to an external variable in a closure, there is no doubt that it increases the lookup path for the identifier, which in some cases also results in a performance penalty. The solution to this problem, as we mentioned earlier, is to store as many external variables as possible into local variables and reduce the length of the scoped chain.

Conclusion: closures are not a unique feature of javascript, but they have their own unique expression in javascript. With closures, we can define some private variables in javascript, and even simulate block-level scope.


Related articles: