闭包彻底学习

闭包的定义:
定义1(红宝书):有权访问另一个函数作用域的变量的函数。
定义2(你不知道的js):当函数能够记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。

闭包的表现形式(来源: http://www.w3cschool.cn/javascript_prototype/3exmpozt.html ):
1、函数作为返回值
2、函数作为参数被传递

函数嵌套有的时候并不是闭包,举例如下(来源:你不知道的js):
function foo(){
     var a=2;
     function bar(){
          console.log(a);
     }
}
foo();//输出2
其实这个不是闭包,只是正常的作用域链。

闭包的例子:

function foo(){
     var a=2;
     function bar(){
          console.log(a);
     }
return bar;
}
var baz=foo();
baz();//输出2
这个例子印证了以上闭包定义,以及闭包的表现形式。
印证定义2:
bar()函数可以访问foo()的变量,这个根据作用域链可以知道对吧。
然后,通过执行foo()函数,bar()函数被返回,并赋值给baz,baz就等价于bar;
baz执行时实际调用了bar()函数,而此时bar()函数是在词法作用域之外执行的,而它可以访问foo()函数中的a变量(可以记住并访问bar()所在的词法作用域)。
当然bar访问foo,本身就印证了定义1。
印证闭包的表现形式:
这个例子中函数bar()是作为返回值的。

(你不知道的js)
function foo(){
     var a=2;
     function baz(){
          console.log(a);
     }
     bar(baz);
}

function bar(fn){
     fn();
}
foo();
//输出2

也可以间接传递函数:
var fn;
function foo(){
  var a=2;
  function baz(){
  console.log(a);
  }
  fn=baz;
}

function bar(){
  fn();
}
foo();
bar();
//输出2
这两个例子说明:无论使用何种方式对函数类型的值进行传递,当函数在别处被调用时都可以观察到闭包。
无论通过何种手段将内部函数传递到所在的词法作用域之外。它都会持有对原始定义作用域的引用,无论在何处执行这个函数都会使用闭包。


有这么一道面试题:实现一个暴露内部变量,并且可以在外部访问并修改参数的函数。
那也很简单,就把上述例子改一下就好了。
(你不知道的js)

function foo(){
     var a=2;
     function bar(){
        a=3;  
        console.log(a);
     }
return bar;
}
var baz=foo();
baz();
//输出3


下面再举几个例子吧,都是比较容易出错的:
function wait(message){
     setTimeout(function timer(){console.log(message)},1000;);
}
wait("hello,closure!");
此时,是将函数timer()传递给setTimeout().wait函数执行1000ms后,它的内部作用域并不会消失,timer函数依然保有wait作用域的闭包,因此还保有对变量messge的引用。

循环和闭包:
for (var i=0;i<=5;i++){
     setTimeout(function timer(){
          console.log(i);
     },i*1000);
}
输出:
这段代码会在运行时,以每秒一次的频率输出5次6.
我们预期输出是分别输出1-5,每秒一个。而实际输出,却是以每秒一次的频率输出5次6.
解释:
这段代码到底有什么缺陷,导致它的行为同语义所暗示的不一致呢?
缺陷是:我们试图假设循环中的每个迭代在运行时都会给自己“捕获”一个i的副本。但是根据作用域的工作原理,实际情况是尽管循环中的5个函数是在各个迭代中分别定义的,但是他们都被封装在一个共享的全局作用域中,因此实际上只有一个i。
这样说的话,当然所有函数都共享一个i的引用。如果将延迟函数的回调重复定义5次,完全不使用循环,那它同这段代码是完全等价的。
那么缺陷到底是什么?我们需要更多的闭包作用域,特别是在循环的过程中每个迭代都需要一个闭包作用域。
而通过声明并立即执行一个函数可以创建一个作用域。
因此,要使上述代码符合我们的预期,可以额外加一个立即执行函数。
for (var i=0;i<=5;i++){
  (function(){
 var j=i;
  setTimeout(function timer(){
  console.log(j);
  },j*1000);
  })();
}
还可以进一步简化,
for (var i=0;i<=5;i++){
   (function(j){  
        setTimeout(function timer(){
            console.log(j);
        },j*1000);
    })(i);
}
这样实际上是生成了一个额外的作用域来储存每次的变量i(此时变量i不是所有函数共享的)。

还可以使用块级作用域,
for(var i=1;i<=5;i++){
     let j=i;
     setTimeout(function timer(){console.log(j);},j*1000);
}
或者
for(let i=1;i<=5;i++){
  setTimeout(function timer(){console.log(i);},i*1000);
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值