Home  >  Article  >  Web Front-end  >  Understanding closures in javascript

Understanding closures in javascript

高洛峰
高洛峰Original
2017-01-20 11:41:471020browse

Reading Contents

What is a closure?

Characteristics of closures

The role of closures:

Code examples of closures

Notes

Summary

Closure is a relatively important concept in JavaScript, and it is also a technology used frequently in daily work. Let’s make a small summary of it

What is a closure?

Official statement:

A closure refers to a function that has access to a variable in the scope of another function. A common way to create a closure is to create another function inside a function and access the local variables of this function through another function

The following is a simple closure:

function A(){
 var text="hello world";
 function B(){
 console.log(text);
 }
 return B;
}
var c=A();
c(); // hello world 

According to the literal meaning: function B has the right to access the variable (text) in the scope of function A, and access the local variable text of this function through another function C. Therefore function B forms a closure. It can also be said that C is a closure, because C actually executes function B.

It should be noted that there will be no reaction if A(); is executed directly. Because return B is not executed unless it is return B();

Characteristics of closure

Closure has three characteristics:

1. Function nested function

2. External parameters and variables can be referenced inside the function

3. Parameters and variables will not be recycled by the garbage collection mechanism

Explain point 3, why the parameters of closure and Will variables not be recycled by the garbage collection mechanism?

First of all, let’s understand the principle of garbage collection in javascript:

(1) In javascript, if an object is no longer referenced, then the object will be GC (garbage collection) Recycling;

(2) If two objects refer to each other and are no longer referenced by a third party, then the two objects that refer to each other will also be recycled.

In the above example code, A is the parent function of B, and B is assigned to a global variable C (the life cycle of the global variable will not end until the browser unloads the page), which causes B to always be in the memory. , and the existence of B depends on A, so A is always in memory and will not be recycled by the garbage collection mechanism (garbage collection) after the call is completed.

The role of closure:

In fact, the role of closure is also determined by the characteristics of closure. According to the above closure characteristics, the role of closure is as follows:

1. You can read the variables inside the function instead of defining global variables to avoid polluting the environment

2. Keep the values ​​of these variables in memory.

Code examples of closures

The following mainly introduces several common closures and analyzes them:

demo1 Accumulation of local variables.

function countFn(){
 var count=1;
 return function(){  //函数嵌套函数
 count++;
 console.log(count);
 }
}
var y = countFn(); //外部函数赋给变量y;
y(); //2 //y函数调用一次,结果为2,相当于countFn()()
y(); //3 //y函数调用第二次,结果为3,因为上一次调用的count还保存在内存中,没有被销毁,所以实现了累加
y=null; //垃圾回收,释放内存
y(); // y is not a function

Since the first execution is completed, the variable count is still stored in the memory, so it will not be recycled, so that the last value can be accumulated during the second execution. . When y=null is introduced, the reference is destroyed and the memory is released

demo2 Using closures in loops

The code is as follows (three code examples below): Our purpose is to use closures in each loop Call loop sequence number:

demo2-1

for (var i = 0; i < 10; i++) {
 var a = function(){
 console.log(i)
 }
 a() //依次为0--9
}

The result of this example is correct, we printed out 0-9

in sequence Each level of anonymous function and variable i form a closure, but there is no problem in the loop, because the function is executed immediately in the loop body

demo2-2

But It's different in setTimeout

for(var i = 0; i < 10; i++) {
 setTimeout(function() {
 console.log(i); //10次10
 }, 1000);
}

We expect to print out 0--10 in sequence, but the actual situation is to print out 10 10 times. Even if the setTimeout time is changed to 0, 10 10s will be printed. Why is this?

This is because of the mechanism of setTimeout. SetTimeout starts timing when the task queue ends. If there is a process in front that has not ended, then it waits until it ends before starting timing. Here, the task queue is its own loop.

setTimeout does not start timing until the end of the loop, so no matter what, the i in setTimeout is the i of the last loop. In this code, the last i is 10, so 10 10s are printed.

This is why the callback of setTimeout does not take the value of each loop, but the last value

demo2-3

Solve the problem that the above setTimeout cannot print out the loop in sequence

for(var i=0;i<10;i++){
 var a=function(e){
 return function(){
  console.log(e); //依次输入0--9
 }
 }
 setTimeout(a(i),0);
}

Because the first parameter of setTimeout requires a function, a function is returned to When it returns, it passes i as a parameter and caches i through the formal parameter e. That is to say, the e variable is equivalent to a copy of i and is brought into the returned function.

When setTimeout is executed, it has a reference to e, and this value will not be changed by the loop.

You can also use the following writing method, which is similar to the above:

for(var i = 0; i < 10; i++) {
 (function(e) {
 setTimeout(function() {
  console.log(e); //依次打印出0-9
 }, 0);
 })(i);
}

demo3 Add events in the loop

Look at a typical demo below.

We hope that every time we click li, the index value of li will be alerted, so we use the following code:

<ul id="test">
 <li>第一个</li>
 <li>第二个</li>
 <li>第三个</li>
 <li>第四个</li>
</ul>
var nodes = document.getElementsByTagName("li");
for(i = 0,len=nodes.length;i<len;i++){
 nodes[i].onclick = function(){
 alert(i); //值全是4
 };
}

Backfired, no matter which li is clicked, it will alert(4), which is the index value after the alert loop ends. Why is this?

This is because events are bound to different elements in the loop. If a variable related to the loop is called in the event callback function, this variable will take the last value of the loop.

由于绑定的回调函数是一个匿名函数,所以上面的代码中, 这个匿名函数是一个闭包,携带的作用域为外层作用域(也就是for里面的作用域),当事件触发的时候,作用域中的变量已经随着循环走到最后了。

还有一点就是,事件是需要触发的,而绝大多数情况下,触发的时候循环已经结束了,所以循环相关的变量就是最后一次的取值。

要实现点击li,alert出li的索引值,需要将上面的代码进行以下的修改:

<ul id="test">
 <li>第一个</li>
 <li>第二个</li>
 <li>第三个</li>
 <li>第四个</li>
</ul>
var nodes=document.getElementsByTagName("li");
for(var i=0;i<nodes.length;i++){
 (function(e){
 nodes[i].onclick=function(){
  alert(e);
 };
 })(i)
}

解决思路: 增加若干个对应的闭包域空间(这里采用的是匿名函数),专门用来存储原先需要引用的内容(下标)。

当立即执行函数执行的时候,e 值不会被销毁,因为它的里面有个匿名函数(也可以说是因为闭包的存在,所以变量不会被销毁)。执行后,e 值 与全局变量 i 的联系就切断了,

也就是说,执行的时候,传进的 i 是多少,立即执行函数的 e 就是多少,但是 e 值不会消失,因为匿名函数的存在。

也可以用下面的解法,原理是一样的:

<ul id="test">
 <li>第一个</li>
 <li>第二个</li>
 <li>第三个</li>
 <li>第四个</li>
</ul>
var nodes=document.getElementsByTagName(&#39;li&#39;);
for(var i = 0; i<nodes.length;i++){
 (function(){
 var temp = i;
 nodes[i].onclick = function () {
  alert(temp);
 }
 })();
}

注意事项

1、造成内存泄露

由于闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存。过度使用闭包可能会导致内存占用过多,所以只有在绝对必要时再考虑使用闭包。

2、在闭包中使用this也可能会导致一些问题。

其实我们的目的是想alert出object里面的name

var name="The Window";
 var object={
 name:"My Object",
 getNameFunc:function(){
  return function(){
  return this.name;
  }
 }
 }
 alert(object.getNameFunc()()); // The Window

因为在全局函数中,this等于window,而当函数被作为某个对象的方法调用时,this等于那个对象。不过,匿名函数的执行环境具有全局性,因此其this对象通常指向window。

每个函数在被调用时,都会自动取的两个特殊变量:this和 arguments。内部函数在搜索这两个变量时,只会搜索到其活动对象为止。也就是说,里面的return function只会搜索

到全局的this就停止继续搜索了。因为它永远不可能直接访问外部函数中的这两个变量。

稍作修改,把外部作用域中的this对象保存在一个闭包能够访问的变量里。这样就可以让闭包访问该对象了。

var name="The Window";
 var object={
 name:"My Object",
 getNameFunc:function(){
  var that=this;
  return function(){
  return that.name;
  }
 }
 }
 alert(object.getNameFunc()()); // My Object

我们把this对象赋值给了that变量。定义了闭包之后闭包也可以访问这个变量。因此,即使在函数返回之后,that也仍引用这object,所以调用object.getNameFunc()()就返回 “My Object”了。

总结

当在函数内部定义了其他函数,就创建了闭包。闭包有权访问包含函数内部的所有变量。

闭包的作用域包含着它自己的作用域、包含函数的作用域和全局作用域。

当函数返回一个闭包时,这个函数的作用域会一直在内存中保存到闭包不存在为止。

使用闭包必须维护额外的作用域,所有过度使用它们可能会占用大量的内存

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持PHP中文网!

更多理解javascript中的闭包相关文章请关注PHP中文网!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn