写在前面: 闭包可以说是js中一个非常难理解得问题了,理解闭包得前提是深入理解作用域得概念,如果你不是很清楚得话,可以看一下我上一篇文章 深入理解作用域 废话不多说,切入正题 如果你还没有真正理解闭包得话,那么理解闭包可以看作某种意义上得重生 – – -《你不知道得js》 js中闭包无处不在,它并不是一个需要学习新的语法或模式才能使用得工具,闭包是基于词法作用域书写代码时所产生得自然结果。我们需要得是根据自己得意愿来识别、拥抱和影响闭包得思维环境。 我的理解定义:当函数可以记住并访问所在的词法作用域时,就产生了闭包,即:使函数是在当前词法作用域之外执行 分析这段代码,思考是闭包吗? 这段代码看起来和嵌套作用域中的示例代码很相似,基于词法作用域的查找规则,函数bar()可以访问外部作用域中的变量a,从技术上看也许是闭包,但根据前面的定义,我觉得并不是闭包!最准确的用来解释bar()对a的引用的方法是词法作用域的查找规则,这些规则是闭包中非常重要的一部分! 下面这段代码则清晰的展示了闭包 这就很清晰的可以看出来是一个闭包了! 函数bar()的词法作用域能够访问foo()的内部作用域。然后我们将bar()函数本身当作一个值类型进行传递。 foo()执行后,其返回值赋值给baz并调用baz(),实际上只是通过不同的标识符引用调动了内部的函数bar()。当foo()执行后,通常会期待foo()函数的内部作用域被销毁,但是闭包的神奇之处就是可以阻止垃圾回收器对它的回收,事实上内部作用域依然存在,因此没有被回收,因为bar()仍然在使用这个内部作用域! 函数在定义时的词法作用域以外的地方被调用,闭包使得函数可以继续访问定义时的词法作用域。当然,无论使用什么方式对函数类型的值进行传递,当函数在其他地方被调用的时候都可以观察到闭包! 把内部函数baz传递给bar,当调用这个内部函数时(即fn),它涵盖的foo()内部作用域的闭包就可以观察到了,因为它可以访问a。 间接传递函数 无论通过什么手段将内部函数传递到所在的词法作用域以外,它都会保持对原始定义作用域的引用,无论在何处执行这个函数都会使用闭包。 我们再来看一个示例 将一个内部函数(time)传递给setTimeout()。time具有涵盖wait()作用域的闭包,保持有对message的引用! 目的:我们想要这段代码分别输出数字1-5,每秒一次,每次一个!
这段代码你可能遇到过,笔者在做一些公司的笔试的时候经常看到这个题,有时候面试官也会考察这个题,如果你对闭包理解透彻的话,那么这段代码的输出结果你应该非常清楚了吧? 这段代码会输出什么呢?输出1 2 3 4 5? 事实上,这段代码会输出5次6,惊不惊喜?意不意外? 仔细想一想,延迟函数的回调会在循环结束的时候才执行,。事实上,当定时器运行时即使每个迭代中执行的是setTimeout(…,0),所有的回调函数仍然是在循环结束之后才会被执行,因此每次都输出6 那到底是什么缺陷导致这样呢???? 我们试图假设循环中的每个迭代在运行的时候都会给自己捕获一个i的脚本,但是根据作用域的原理,实际情况是尽管循环中的五个函数是在各个迭代中分别定义的,但他们都是被封闭在一个共享的全局作用域中,实际上只有一个i 所以缺陷是什么?我们需要更多的闭包作用域,特别是在循环过程中每个迭代都需要一个闭包作用域! 这样可以吗?不可以!!! 现在显然拥有更多的词法作用域了,的确每个延迟函数都会将IIFE在每次迭代中创建的作用域封闭起来,但是如果作用域是空的,那么只封闭自然是不行的,仔细看一下上述代码,IIFE是一个空作用域!!它需要拥有自己的变量,用来存储每个迭代中i的值! 所以我们修改一下上述代码,就可以实现预期效果啦 这个时候,它就会输出1 2 3 4 5啦! 这样看起来是不是舒服多啦! 那么思考一下,还可以怎么修改呢? 这样就可以了,是不是很简单? 怎么样,是不是觉得块作用域和闭包联合使用超级舒服?我不要你觉得,反正我觉得很nice,嘿嘿 那么要记住了哦 当函数可以记住并访问所在的词法作用域,函数是在当前词法作用域之外执行,这时候就产生了闭包! 到这里博文就结束啦,如果你感觉对你有帮助的话,可以哟,由于笔者能力有限,如果有读者发现了问题,感谢指正!如何理解闭包
function foo(){ var a=2; function bar(){ console.log(a); //2 } bar(); } foo();
但是从纯学术上看,可以认为bar()封闭在foo()的作用域中。也可以认为是闭包,但是这种方式定义的闭包并不能直接进行观察,也不能明白在这个代码中闭包是如何进行工作的。所以可以换一种清晰的表达方式! function foo(){ var a=2; function bar(){ console.log(a); } return bar; } var baz = foo() baz(); //2
拜bar()所声明的位置所赐,它拥有涵盖foo()内部作用域的闭包,使得该作用域能够一直存活,以供bar()在之后任何时间进行引用。bar()仍然持有对该作用域的引用,而这个引用就叫做闭包!! function foo(){ var a=2; function baz(){ console.log(a); //2 } bar(baz); } function bar(fn){ fn(); } foo();
var fn; function foo(){ var a=2; function baz(){ console.log(a); } fn = baz; //将baz分配给全局变量 } function bar(){ fn(); } foo(); bar(); //2
function wait(message){ steTimeout(function time(){ console.log(message); },1000) } wait("hello ,米老鼠");
wait()执行1秒后,它的内部作用域并不会消失,time函数依然保有wait()作用域的闭包。词法作用域保持完整,不会被销毁!
这就是闭包!闭包之经典循环
for(var i=1;i<=5;i++){ setTimeout(function time(){ console.log(i); },i*1000) }
为什么?
小朋友,你是否有很多问号?
那么怎么改成是我们想要的效果呢???
我们知道IIFE会通过声明并立即执行一个函数来创建作用域。
那么有了,我写了下面这个代码看看能实现预期效果吗? for(var i=1;i<=5;i++){ (function(){ setTimeout(function time(){ console.log(i); },i*1000) })(); }
for(var i=1;i<=5;i++){ (function(){ var j=i; setTimeout(function time(){ console.log(j); },j*1000) })() }
女朋友觉得上述代码不够优雅?
嘿嘿,可以修改一下写法 for(var i=1;i<=5;i++){ (function(j){ setTimeout(function time(){ console.log(j); },j*1000) })(i) }
利用块作用域,也可以实现(ES6新增了一个let,可以利用let实现)
如果不了解块作用域,词法作用域可以去看一看哟 理解块作用域,词法作用域,如果let 的使用不清楚的话,可以去看看我的这篇博文哟理解var let const的区别
修改之后的代码如下: for(var i=1;i<=5;i++){ let j=i; setTimeout(function time(){ console.log(j); },j*1000) }
还有一个更简单的,如果你理解了var 和let的区别,那么我们可以直接这样写: for(let i=1;i<=5;i++){ setTimeout(function time(){ console.log(i); },i*1000) }
最后
本网页所有视频内容由 imoviebox边看边下-网页视频下载, iurlBox网页地址收藏管理器 下载并得到。
ImovieBox网页视频下载器 下载地址: ImovieBox网页视频下载器-最新版本下载
本文章由: imapbox邮箱云存储,邮箱网盘,ImageBox 图片批量下载器,网页图片批量下载专家,网页图片批量下载器,获取到文章图片,imoviebox网页视频批量下载器,下载视频内容,为您提供.
阅读和此文章类似的: 全球云计算