闭包

闭包

我们都知道函数由于作用域的存在,外部一般是无法访问函数内部局部变量的:

1      function f1() {
2             var a = 1
3         }
4         console.log(a);//Uncaught ReferenceError: a is not definedat Untitled-1.html:14

但是我们可以通过在f1中再定义一个函数f2,根据作用域链的规则,f2是可以访问f1的局部变量的,我们再把f2当作f1的函数返回值,那么f1外部不也可以读取f1的变量了!

 1      function f1() {
 2             var a = 1;
 3             function f2() {
 4                 console.log(a);
 5             }
 6             return f2;
 7         }
 8         
 9         var result = f1()
10         result()  //1            

 

这便是一个闭包产生的结果!chrom浏览器会将这个执行上下文的函数名f1称作闭包。

闭包是一种特殊对象,由某个函数以及它执行上下文共同组成,当该内部函数访问了外部上下文中的值便产生闭包

形成条件

  • 函数的嵌套
  • 内部函数引用外部函数的标变量

一个简单闭包应用例子:


1     for (var i=1; i<=5; i++) {
2         setTimeout( function timer() {
3             console.log( i );
4         }, i*1000 );
5     }  //我们想要每隔一秒分别输出12345;但实际上每隔一秒产生一个6 共输出5个6

由于setTimeout运用到js中的异步机制:setTimeout每执行时,会将其内部内容作为异步任务放入异步任务队列中,然后继续执行完所有主线任务,再一步步执行任务队列。

所以上面实际上内部函数function timer() 一直等待for循环执行完毕后,再开始执行,此时i=6,故异步输出5个6。我们可以用利用闭包来达到想要的效果:

1     for (var i=1;i<=5;i++){
2         function f1 () {                       -----------
3             var j = i;
4             setTimeout( function timer(){            在此间形成闭包f1     
5                 console.log(j);
6             }, j*1000);
7         }                                      -----------
8         f1()
9     }                  此时便会每隔一秒分别输出12345;达到我们本来想要的结果。

   不同点在于第二个做法将循环内部套上了一层函数f1,满足了闭包条件产生了闭包;                                                    通过j保存了每一次循环过后不同的i再通过闭包将其滞留;                                                          尽管异步仍然回等所有主线函数执行完毕最后再一个个执行,但闭包f1的存在导致即便执行异步队列中function timer() 执行时 闭包中f1中的变量j仍然被防止垃圾回收机制销毁.以达到我们想要的效果。

就好比每次循环都会形成新的不同的作用域,闭包导致每个不同的i都被封闭到不同的作用域中了。


还可以通过立即执行函数简化成:

1 for (var i=1; i<=5; i++) {
2   (function(j){
3     setTimeout(function timer() {
4       console.log(j);
5     }, 0)
6   })(i)
7 }

此处的j=i还只是为了方便理解,可以不写; 

 上述代码等于:

 1   var i = 1;
 2   (function f1(){
 3     var j = i;
 4     setTimeout(function timer() {
 5       console.log(j);
 6     }, j*1000)
 7   })();
 8     var i =2;
 9   (function f1(){
10     var j = i;
11     setTimeout(function timer() {
12       console.log(j);
13     }, j*1000)
14   })();
15     var i =3;
16   (function f1(){
17     var j = i;
18     setTimeout(function timer() {
19       console.log(j);
20     }, j*1000)
21   })();
22     var i =4;
23   (function f1(){
24     var j = i;
25     setTimeout(function timer() {
26       console.log(j);
27     }, j*1000)
28   })();
29     var i =5;
30   (function f1(){
31     var j = i;
32     setTimeout(function timer() {
33       console.log(j);
34     }, j*1000)
35   })()

说白了就是这个例子我们需要每次迭代时创建新的作用域,在这个作用域内形成闭包。

es6中let声明,除了不形成变量提升,可以形成块级作用域之外,还有更多的不同:可以劫持块级作用域,并在该作用域声明变量。本质上将该块转换成可关闭的作用域。

如此便可利用let完成上例:

1   for(var i=1;i<=5;i++){
2       let j = i;
3       setTimeout(function timer() {
4           console.log(j)         
5       }, j*1000);
6   }

根据你不知道的js中指出 for循环头部的let声明还会存在一个特殊行为:指出变量在循环过程中不止被声明一次,即每次迭代都会声明一次,随后每个迭代都会使用上一个迭代结束时的值来初始化该变量。既然如此,那么还有更简单的实现方法:

 

1  for(let i=1;i<=5;i++){
2       setTimeout(function timer() {
3           console.log(i)         
4       }, i*1000);
5  }  // 闭包与块级作用域联手天下无敌!

闭包的一些应用场景:往后深入学习再写

  • 模块化
  • 柯里化
原文地址:https://www.cnblogs.com/zxf906/p/15105509.html