前端JavaScript基础Web前端之路

深刻理解Javascript中的闭包概念,需要换一种视角。

2017-03-05  本文已影响123人  帅华君

去年10月写了一篇Javascript原型链的文章,反响不错(看评论里有说有其他博主没有注明原文出处还替我打抱不平,哈哈,你们好可爱,也不能怪他们呀,忽然想起了去年野狗复制我的文章到他们的微信平台也没有注明出处,我还傻傻的感谢他们,如果这篇也敢搬运就更好了,不用注明出处都可以的,还能让平权前进20年,哈哈^_^),看到那么多鼓励的评论,文章能帮助到部分上进的盆友真的很开心,也很有成就感!新年开始,未来的日子里,不断学习和提升生活品质,工作上继续在其位谋其职,写博客,分享能量,感恩生活,敬畏生命,就这样。

同样,闭包和原型链都是Js中有点难度的知识点,所以看本文之前需要较熟悉Javascript基础语法。

这篇文章纠结了几个月一直想不到合适的栗子来一气呵成的讲解JS中闭包的概念,上一篇介绍原型链的时候举的是生物进化链和遗传的例子。终于过了一个年,想到了一个也许有些敏感但是蛮合适的栗子,受去年的毕业设计启发, 想到了这样的一个栗子————不同国家对同性婚姻的态度对家庭和个人的作用。

要理解闭包,就不得不先理解什么是作用域、作用域链。

一、作用域

开篇那张图能帮助你理解作用域么?稍作解释下。

不同国家可以看作是不同的编程语言,亦或是运行环境,亦或是不同函数体。

不同国家法律对于同性婚姻的态度,可以看作是不同编程语言的语法规则,运行环境的系统要求,不同函数的某一局部变量。

国家、家庭、个人都可以看作是一个个闭包。

(1)、“家庭”、“个人”这些闭包可以访问到“国家”这一全局作用域下关于同性婚姻是否受法律保护这一布尔属性。

不同国家规定的“游戏规则”(同性是否可以结婚)这件事;决定了每个家庭是否是一男一女结婚(“家庭”可以访问“国家”关于同性是否可以结婚这个布尔属性,最后“renturn”出符合国家要求的所谓“正确”的婚姻方式),因为你在这个如此定义游戏规则的国家,家庭处于该国家所有“属性”起作用的作用域内,so...;决定了男人要娶女人,女人要去嫁男人结婚这回事,因为每个人可以间接访问到(爸爸妈妈就是一男一女结婚的)国家关于同性是否可以结婚这一布尔属性。每个人都在家庭传统观念属性集合起作用的作用域内,每个人也都在国家的属性起作用的作用域内,so...;

(2)、“国家”未必能访问到“家庭”、“个人”这些闭包的“小心思和行踪”。

上有“政策(全局变量)”,下有“对策(局部变量,且局部变量如果和全部变量同名冲突,将会导致当前闭包作用域内所有用到被修改的全局变量的数据发生些奇妙的变化)”。该家庭是否是形婚这一布尔属性,“国家”是无法访问到的(除非...这个家庭有某种【闭包】被“return”,导致该秘密对外可访问。

部分个体选择去到那些同性婚姻是否合法为True的国家,组成家庭。全局变量随之改变了,so,家庭,个人的行为也将受到该作用域的影响,随之改变。

如果说伏羲与女娲创始婚姻的目的是为了在原始未开化的时期保护女性、孩子能知道爸爸是谁、而非野合、性行为更合理,那后来这太平盛世婚姻的目的不应该被重新定义么。

二、作用域链(scope chain)

如果能完全看懂我上面对作用域的解释,相信作用域链你也就理解了。这里重新解释下。

一个国家规定了同性婚姻是否合法(全局作用域中有一个数据类型为布尔值的属性被声明)。

在这个国家每出生一个新生命,该国家所有游戏规则伴随着他慢慢长大,他也被动接受者身处该作用域下应该懂得和承担的东西。(全局作用域下的A函数可以调用全局作用域下的其他函数和访问其属性(其实一切皆对象),而A函数下的Ainner函数可以调用A函数作用域下的其他函数和访问其属性及A函数的上一级闭包的作用域下....)

结合上图,我们看这样的一段代码:

functionfunA(){varfunA_age =28;returnfunctionfunB(){varfunB_age =20;returnfunctionfunC(){varfunC_age =18;returnfunctionfunD(){varfunD_age =12;      }    }  }}//将函数funA的属性输出;console.dir(funA);

执行上面代码,看看控制台输出了什么。

我们看到在Scopes内有1个元素,展开他发现,他就是所有全局变量。所以我们才可以在funA函数内直接访问window中的各种方法和属性,比如window.setInterval()等。

接着,我将输出改一下。

functionfunA(){varfunA_age =28;returnfunctionfunB(){varfunB_age =20;returnfunctionfunC(){varfunC_age =18;returnfunctionfunD(){varfunD_age =12;      }    }  }}//将funA()的返回值(也就是函数funB)的属性输出;console.dir(funA());

同样能在控制台看到函数funB内可以访问window中的所有方法和属性。以此类推,结果都一样,不管函数嵌套多深,好像window这个全局变量的方法和属性在哪里都能访问到,这难道就是作用域链起到的神奇作用么。好戏还在后头。

functionfunA(){varfunA_age =28;returnfunctionfunB(){varfunB_age =20;//在函数funB中访问funA中定义的funA_age,能获取到么?;console.log(funA_age);returnfunctionfunC(){varfunC_age =18;returnfunctionfunD(){varfunD_age =12;      }    }  }}//将funA()的返回值(也就是函数funB)的属性输出;console.dir(funA());

真的有哎!而且你有沒有发现,js很聪明,因为在第一次没有console.dir()来输出funA_age而只是输出函数funA的时候,我们并没有在控制台的[[Scopes]]中找到被定义的funA_age变量,所以说,作用域链上每个函数作用域内用到了其父级什么变量就存什么变量,没有用到的就不存。

比如说,我们改写函数funD。

functionfunA(){varfunA_age =28;returnfunctionfunB(){varfunB_age =20;returnfunctionfunC(){varfunC_age =18;returnfunctionfunD(){varfunD_age =12;console.log(funA_age);console.log(funB_age);console.log(funC_age);console.log(funD_age);      }    }  }}//执行funD(),将其属性输出;console.dir(funA()()());

这下明白了,funD中需要输出非funD作用域中的funA_age、funB_age、funC_age变量,所以我们在[[Scopes]]中发现了funA作用域中的funA_age;funB作用域中的funB_age;funC作用域中的funC_age... ...

这就是作用域链!

三、闭包

讲完了作用域和作用域链,闭包显而易见,是我所说的“国家”、“家庭”、“个人”、funA、funB、funC、funD。他们都是闭包。

现在就差一个代码示例来帮助你理解闭包了,

(1)、在for循环中执行耗时的异步请求的正确姿势。

闭包一个用处就是在内存中永久存储数据。

执行Ajax请求很耗时间,如果直接将Ajax写入for循环中,你会的到最后一次循环的异步请求 结果。这里我用延时模拟一下这种糟糕的情况。

for(vari=0; i<3; i++){  setTimeout(function(){console.log(i);  },1000);}

最后输出了3次3,并不是我们希望的0,1,2,难道我写了一个假循环!

闭包来了~

for(vari=0; i<3; i++){  print(i);}functionprint(num){console.log(num);}

也许你会问,print怎么算闭包,闭包不是应该签到在函数里的么,白眼.jpg。请把全局作用域也考虑在内,全局作用域内的函数都是闭包,所以print函数也是啊。

也许你还会问,参数也可以被保存进内存?白眼.jpg。是的!如果没有传进来实参,那么在该函数作用域内访问才参数会返回undefined。如果你觉得不舒服,可以将传入的参数重新赋值给你自己定义的变量,后续就用这个变量,当然我就经常喜欢重新赋值一下。

(2)、面向对象的编程,返回闭包当作对象的公用方法,而“对象”内的变量不会污染全局变量。

functionA(){var_age =0;returnfunction(){console.log(_age++);  }}vara = A();a();a();a();a();

猜猜,控制台输出什么,4个0么?

当然不是啦,每次执行一次函数a,都相当于在当前私有变量_age的基础上加1。注意我喜欢私有变量前边加上一个下划线(JS里变量首字母可以是字母、美刀符号和下划线),不然容易和全局变量同名引起副作用。

也许聪明的你早已看穿了这一切,这个A不就是个构造函数嘛!~没错滴。

我再用另一种写法写出相同的功能。这种写法就不存在同名冲突的问题。

functionPerson(){this.age =0;}Person.prototype.add =function(){console.log(this.age++);}varcsh =newPerson();csh.add();csh.add();csh.add();csh.add();

这就是我理解的作用域、作用域链和闭包,希望你也理解了,甚至比我更好的理解了。

我们的目标是解决实际问题,而不是炫耀技术和理解力,就像闭包,他不是内置函数,只是一个概念,就算你以前不知道他,可你只要写过函数就一定在无时无刻用着它的特性。多像老子口中的哪位智者呐,不见行踪又无处不在,好像无用实则有大用。

原文链接:http://shuaihua.cc/article/javascript/closure-of-javascript.php

转载请注明出处,谢谢合作。

上一篇下一篇

猜你喜欢

热点阅读