Home>

What exactly is a javascript closure?

More than a year with javascript,Closures are always confusing to monks.One after another came into contact with some closure knowledge,I also made several mistakes because I didn't understand closures.More than a year, I also read some information,But I still do n’t fully understandRecently, I accidentally looked at the appendix of the basic tutorial of jQuery and found that the introduction of JavaScript closures in Appendix a is simple and easy to understand.So sum up by offering flowers to the Buddha.

1.Definition

Closure:A function that has access to a variable in the scope of another function.A common way to create closures is to create another function inside one function.

Directly to the example

function a () {
 var i=0;
 function b () {
  alert (++ i);
 }
 return b;
}
var c=a ();
c ();

This code has two characteristics:

1) Function b is nested inside function a;

2) Function a returns function b.

In this way, after executing var c=a (), the variable c actually points to function b. After executing c (), a window pops up to display the value of i (the first time is 1). This code actually creates a closure,why?Because the variable c outside the function a refers to the function b inside the function a, that is to say:

When the internal function b of function a is referenced by a variable outside function a,A closure is created.

I guess you still don't understand closures,Because you don't know what closures do,Let us continue to explore.

2. What is the purpose of closures?

in short,The effect of the closure is that after a is executed and returned,The closure makes JavaScript's garbage collection mechanism gc not reclaim the resources occupied by a,Because the execution of a's internal function b depends on the variables in a.This is a very straightforward description of the role of closures,Unprofessional and rigorous,But it probably means that,Understanding closures requires a gradual process.

In the example above,Due to the existence of the closure, after the function a returns, i in a always exists.In this way, every time c () is executed, i will alert the value of i after adding 1.

So let's imagine another situation,If a does not return function b, the situation is completely different.Because after a is executed,b is not returned to the outside of a, it is only referenced by a, and at this time a will only be referenced by b. Therefore, functions a and b refer to each other but are not disturbed by the outside (referenced by the outside). Will be recycled by gc. (The garbage collection mechanism of javascript will be described in detail later)

3.The micro world inside the closure

If we want to learn more about closures and the relationship between functions a and nested functions b, we need to introduce several other concepts:function execution context, call object, scope, role Scope chain. Take the function a from definition to execution as an example to illustrate these concepts.

1) When defining function a, the js interpreter sets the scope chain of function a to the "environment" where a is defined when a is defined. If a is a global functionThere is only a window object in the scope chain.

2) When the function a is executed,a will enter the corresponding execution context.

3) In the process of creating the execution environment,First, a scope attribute is added to a, that is, the scope of a.Its value is the scope chain in step 1. That is, the scope chain of a.scope=a.

4) Then the execution environment will create a call object. The active object is also an object with properties,But it has no prototype and cannot be accessed directly through javascript code.After creating the active object,Add the active object to the top of the scope chain of a.At this time, the scope chain of a contains two objects:the active object of a and the window object.

5) The next step is to add an arguments property to the active object, which holds the parameters passed when calling function a.

6) Finally, all the formal parameters of function a and internal function b references are also added to the active object of a.In this step,The definition of function b is completed,So as in step 3, the scope chain of function b is set to the environment defined by b,The scope of a.

At this point, the steps from definition to execution of the entire function a are complete.At this time, a returns a reference to function b to c, and the scope chain of function b contains a reference to the active object of function a.In other words, b can access all variables and functions defined in a.Function b is referenced by c, and function b depends on function a, so function a will not be collected by gc after it returns.

When the function b is executed, it will be the same as the above steps.Therefore, the scope chain of b during execution contains 3 objects:the active object of b, the active object of a, and the window object. When accessing a variable in function b,The search order is to search its own active objects first,Return if it exists,If there is no active object that will continue to search for function a,Find them one by one,Until you find it.If the entire scope chain cannot be found,It returns undefined. If a prototype object exists for function b,After looking for its own active object, it first looks for its own prototype object.Find it again.This is the variable lookup mechanism in JavaScript.

4.Application scenarios of closures

1) Protect the variables in the function.Take the first example as an example,Function i can only be accessed by function b,And cannot be accessed through other channels,Therefore, the security of i is protected.

2) Maintain a variable in memory.Still as before,Because of closures,The i of function a always exists in memory,So every time c () is executed, i is incremented by one.

The above two points are the most basic application scenarios of closures.Many classic cases originate from this.

5, JavaScript's garbage collection mechanism

In javascript, if an object is no longer referenced,Then this object will be recycled by gc. If two objects reference each other,And no longer referenced by the third person,Then these two references to each other will also be recycled.Because function a is referenced by b and b is referenced by c outside a, this is why function a is not recycled after execution.

There is no block-level scope in javascript,Generally, in order to declare some local variables that can only be used by a function,We will use closures,This way we can greatly reduce the variables in the global scope,Purge global scope.

The benefits of using closures are as above,Of course, such benefits come at a price.The cost is memory usage.

How to understand the above sentence?

Execution of each function,Will create a function execution environment related to the function,Or the function execution context.There is an attribute scope chain in this execution context, this pointer points to a scope chain structure,The pointers in the scope chain all point to the active objects corresponding to each scope.normal situation,A function creates the function execution context and the corresponding scope chain when the call begins execution,The space occupied by the function execution context and the corresponding scope chain is released after the function execution ends.

//Declaring functions
function test () {
 var str="hello world";
 console.log (str);
}
//call function
test ();

When the function is called, the following structure is generated in memory:

But the situation with closures is a bit special,Since closure functions can access variables in outer functions,So after the execution of the outer function,The scope active object will not be released (note that the execution environment and the corresponding scope chain will be destroyed after the execution of the outer function ends), but will be referenced by the scope chain of the closure functionUntil the closure function is destroyed,Only the scoped active objects of the outer function are destroyed.This is why closures take up memory.

So using closures is good,There are also disadvantages,Abuse of closures will cause a lot of memory consumption.

There are other side effects of using closures,It can be said to be a bug, or it can be said not,Relatively different businesses may have different views.

This side effect is that the closure function can only take the final value of the outer function variable.

The test code is as follows:(jquery object is used here)

/* Closure defects * /
 (function ($) {
  var result=new array (),  i=0;
  for (;i<10;i ++) {
   result [i]=function () {
    return i;
   };
  }
  $.res1=result;
 }) (jquery);
 //execute the function in the array
 $.res1 [0] ();

The above code first opens up a private scope through anonymous function expressions.This anonymous function is the outer function we said above,The outer function has one parameter $, and also defines the variables result and i. An anonymous function is assigned to the array result through a for loop.This anonymous function is a closure,He accesses the variable i of the outer function. In theory, the resulti array will return the corresponding array index value.The actual situation is not as expected.

The execution result of the above code $.res10 is 10.

Why is this so,Because the final value of i is 10.

Below we explain in detail through the following figure,What exactly happened in memory when the above code was executed:

So is there a way to fix this side effect?of course can!

We can achieve our expectations with the following code.

/* Fix closure defects * /
 (function ($) {
  var result=new array (),  i=0;
  for (;i<10;i ++) {
   result [i]=function (num) {
    return function () {
     return num;
    }
   } (i);
  }
  $.res2=result;
 }) (jquery);
 //Call the closure function
 console.log ($. res2 [0] ());

What happened to the above code in memory?We also use the following figure to explain in detail.Understand the picture above,It is not difficult to understand the following figure.

6. Simple example

Let's start with a classic mistake,There are several divs on the page, we want to bind them to an onclick method, so we have the following code

<div>
 <span>0</span><span>1</span><span>2</span>
</div>
<div>
 <span>0</span><span>1</span><span>2</span>
</div>
$(document) .ready (function () {
  var spans=$("#divtest span");
  for (var i=0;i<spans.length;i ++) {
   spans [i] .onclick=function () {
    alert (i);
   }
  }
});

Very simple function, but it is wrong.Every time the value of alert is 4, simple modification is enough.

var spans2=$("#divtest2 span");
$(document) .ready (function () {
 for (var i=0;i<spans2.length;i ++) {
  (function (num) {
   spans2 [i] .onclick=function () {
    alert (num);
   }
  }) (i);
 }
});

7. Internal functions

Let's start with some basic knowledge,First look at internal functions.An internal function is a function defined in another function.E.g:

function outerfn () {
 functioninnerfn () {}
}

innerfn is an internal function that is wrapped in the outerfn scope.this means,It is valid to call innerfn inside outerfn,Calling innerfn outside outerfn is invalid.The following code will cause a javascript error:

function outerfn () {
 document.write ("outer function<br />");
 function innerfn () {
  document.write ("inner function<br />");
 }
}
innerfn ();/uncaught referenceerror:innerfn is not defined

But calling innerfn inside outerfn will run successfully:

function outerfn () {
   document.write ("outer function<br />");
   function innerfn () {
    document.write ("inner function<br />");
   }
   innerfn ();
  }
  outerfn ();

8. Great escape (how internal functions escape external functions)

JavaScript allows developers to pass functions like any type of data,That is,Internal functions in JavaScript can escape the external functions that define them.

There are many ways to escape,For example, you can assign an internal function to a global variable:

//define global variables to escape
var globalvar;
function outerfn () {
 document.write ("outer function<br />");
 function innerfn () {
  document.write ("inner function<br />");
 }
 globalvar=innerfn;
}
outerfn ();//outer function inner function
globalvar ();//outer function inner function
innerfn ();//referenceerror:innerfn is not defined

When outerfn is called, the global variable globalvar is modified. At this time, its reference becomes innerfn. After that, calling globalvar is the same as calling innerfn. In this case, calling innerfn directly outside outerfn will still cause an error,This is because the internal function escapes by keeping the reference in a global variable,But the name of this function still exists only in the scope of outerfn.

You can also get the internal function reference by the return value of the parent function

function outerfn () {
 document.write ("outer function<br />");
 function innerfn () {
  document.write ("inner function<br />");
 }
 return innerfn;
}
var fnref=outerfn ();
fnref ();

There is no global variable modification inside outerfn.Instead, a reference to innerfn was returned from outerfn. This reference can be obtained by calling outerfn,And this reference can be stored in a variable.

The fact that internal functions can still be called by reference even when they are out of scope,Means that whenever there is a possibility of calling an internal function,JavaScript needs to keep referenced functions.And the JavaScript runtime needs to keep track of all variables that reference this internal function,Until the last variable is discarded,JavaScript's garbage collector can release the corresponding memory space (the red part is the key to understanding closures).

After talking about it for a long time, it has something to do with closures.Closures are functions that have access to variables in the scope of another function,A common way to create closures is to create another function inside one function,Is the internal function we said above,So what I just said is not nonsense,Also related to closures ^ _ ^

Scope of variables

Internal functions can also have their own variables,These variables are restricted to the scope of the internal function:

function outerfn () {
 document.write ("outer function<br />");
 function innerfn () {
  var innervar=0;
   innervar ++;
   document.write ("inner function \ t");
   document.write ("innervar =" + innervar + "<br />");
  }
  return innerfn;
}
  var fnref=outerfn ();
  fnref ();
  fnref ();
  var fnref2=outerfn ();
  fnref2 ();
  fnref2 ();

Whenever this internal function is called by reference or otherwise,Will create a new innervar variable, then add 1 and finally show

outer function
inner function innervar=1
inner function innervar=1
outer function
inner function innervar=1
inner function innervar=1

Internal functions can also reference global variables like other functions:

var globalvar=0;
function outerfn () {
 document.write ("outer function<br />");
 function innerfn () {
  globalvar ++;
  document.write ("inner function \ t");
  document.write ("globalvar =" + globalvar + "<br />");
 }
 return innerfn;
}
 var fnref=outerfn ();
 fnref ();
 fnref ();
 var fnref2=outerfn ();
 fnref2 ();
 fnref2 ();

Now every time an internal function is called, the value of this global variable is continuously incremented:

outer function
inner function globalvar=1
inner function globalvar=2
outer function
inner function globalvar=3
inner function globalvar=4

But what if this variable is a local variable of the parent function?Because the internal function will refer to the scope of the parent function (if you are interested, you can learn about the scope chain and active objects), the internal function can also refer to these variables

function outerfn () {
 var outervar=0;
 document.write ("outer function<br />");
 function innerfn () {
  outervar ++;
  document.write ("inner function \ t");
  document.write ("outervar =" + outervar + "<br />");
 }
 return innerfn;
}
var fnref=outerfn ();
fnref ();
fnref ();
var fnref2=outerfn ();
fnref2 ();
fnref2 ();

The results this time were very interesting,Maybe or unexpected

outer function
inner function outervar=1
inner function outervar=2
outer function
inner function outervar=1
inner function outervar=2

What we see is the combined effect of the previous two cases,By calling innerfn with each reference, the outervar is incremented independently. That is to say, the second call to outerfn does not continue to use the value of outervar, but creates and binds a new outervar instance in the scope of the second function call. The two counters are completely unrelated.

When an internal function is referenced outside its scope,A closure is created for this internal function.In this case we call neither a local variable of the internal function,Nor are the variables whose parameters are free variables,The calling environment of the external function is called the environment of the closure.Essentially,If an internal function references a variable located in an external function,Equivalent to authorizing the variable to be deferred.Therefore, when the external function call is completed,The memory of these variables is not released (the last value is saved), the closure still needs to use them.

10. Interaction between closures

When there are multiple internal functions,It is likely that unexpected closures will occur.We define an increasing function,This function is incremented by 2

function outerfn () {
  var outervar=0;
  document.write ("outer function<br />");
  function innerfn1 () {
   outervar ++;
   document.write ("inner function 1 \ t");
   document.write ("outervar =" + outervar + "<br />");
  }
  function innerfn2 () {
   outervar +=2;
   document.write ("inner function 2 \ t");
   document.write ("outervar =" + outervar + "<br />");
  }
  return {"fn1":innerfn1, "fn2":innerfn2};
}
var fnref=outerfn ();
fnref.fn1 ();
fnref.fn2 ();
fnref.fn1 ();
var fnref2=outerfn ();
fnref2.fn1 ();
fnref2.fn2 ();
fnref2.fn1 ();

We map to return references to two internal functions,Any internal function can be called with the returned reference,result:

outer function
inner function 1 outervar=1
inner function 2 outervar=3
inner function 1 outervar=4
outer function
inner function 1 outervar=1
inner function 2 outervar=3
inner function 1 outervar=4

innerfn1 and innerfn2 refer to the same local variable,So they share a closed environment.When innerfn1 is incremented by one for outervar,A long absence innerfn2 sets a new starting value for outervar,vice versa.We also saw that subsequent calls to outerfn will also create new instances of these closures,It also creates new closed environments,Essentially creates a new object,Free variables are instance variables of this object,The closure is the instance method of this object,And these variables are also private,Because these variables cannot be referenced directly outside the scope that encapsulates them,This ensures the exclusivity of object-oriented data.

11. Unsolve

Now we can go back and look at the example written at the beginning, it is easy to understand why the first writing method will alert 4 every time.

for (var i=0;i<spans.length;i ++) {
 spans [i] .onclick=function () {
  alert (i);
 }
}

The above code will be executed after the page loads,When the value of i is 4, the judgment condition does not hold,After the for loop is executed,But because the onclick method of each span is an internal function at this time,So i is referenced by the closure (the closure reference is passed by reference), the memory cannot be destroyed,The value of i will remain 4 until the program changes it or all onclick functions are destroyed (actively assigning the function to null or the page is unloaded) will not be recycled.In this way, every time we click on the span, the onclick function will find the value of i (the scope chain is a reference method), a check is equal to 4, and it will alert us.

The second way is to use a function that executes immediately and create a layer of closures.Function declarations become expressions in parentheses,Add parentheses after the call.At this time, i is passed as a parameter,The function executes immediately,num holds the value of i each time.

This pass must be the same as me,Know something about closures,Of course, to fully understand the function's execution environment and scope chain.

  • Previous Explain related knowledge about object manipulation in JavaScript
  • Next Implement common layouts based on Android code