闭包彻底学习

来源:互联网 发布:js循环while偶数 编辑:程序博客网 时间:2024/06/07 17:04
闭包的定义:
定义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);}



0 0
原创粉丝点击