闭包彻底学习

xiaoxiao2021-02-28  163

闭包的定义: 定义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); }
转载请注明原文地址: https://www.6miu.com/read-29216.html

最新回复(0)