海阔以鱼跃,天高任鸟飞。喂喂喂!我是毛利Molly闭包已经是老生常谈的问题了,不同的人对闭包的理解也不同。今天我就来说说闭包。让我们一起来探讨一下“闭包”这个话题,希望能够擦出一些不一样的火花。在理解闭包之前,我们首先要理解“上下文”和“作用域”这两个知识点。上下文浏览器引擎在解析js代码的时候,大致会经历两个阶段。解析阶段和执行阶段解析阶段:一段代码只是一段有规律的代码文本,所以当js引擎拿到代码后,会提前解析代码。在初始化过程中,将变量、参数、函数、表达式、运算符等提取并存储在一起,并将变量默认赋值给undefined,函数默认赋值给功能块,以及判断上下文等一系列准备工作执行阶段关系:从上到下逐行执行代码,遇到对应的变量或函数,到仓库中匹配执行console.log(str);console.log(fun);varstr="molly";letstr1;console.log(str1);functionfun(a,b){returna+b;}定义:上下文可以分为全局上下文和局部上下文。上下文决定了哪些数据变量或函数可以访问,以及它们的行为(已经在初始化阶段确定)。每个上下文都有一个变量对象(环境记录),在这个上下文中定义的所有变量和函数都会存储在这个变量对象中。我们不能通过代码直接访问这个变量对象,但是可以通过断点查看。全局上下文会在程序退出前(如关闭网页或退出浏览器)被销毁,局部上下文会在其代码执行完毕后被销毁。那么这个可变对象长什么样子呢?别着急,我们再往下看上下文执行栈的定义:每个函数调用都有自己的上下文。当函数执行时,函数的上下文将被压入上下文执行栈。函数执行完毕后,上下文执行栈弹出函数的上下文,将控制权返回到之前的执行上下文。//一个简单的例子,断点调试调用栈和上下文变量对象vara_like2="热爱运动";a_say(a_like);var="222";控制台日志(一);lettest="Comehere?";}functiona_say(a_like){letcode="敲代码";console.log(a_like);}a_molly();执行上面的示例代码,我们在控制台设置断点观察进程的执行栈(callstack)注意左边的调用栈(executionstack)和右边的作用域(environmentrecord)和断点位置通过观察断点调试结果,我们可以得到如下结论:当脚本程序初始化时,会在所有上下文执行栈的底部压入一个全局上下文,即全局属性每当函数执行时,一个“函数上下文”将被添加到执行堆栈。函数执行后,会在每个上下文中清除相应的“函数上下文”,并确定可访问的数据范围和范围链上下文。执行代码时,会创建变量对象的作用域链。这个作用域链决定了每个上下文级别中的代码访问变量和函数的顺序。作用域是一种包含关系,全局包含局部。总结:上下文关联变量对象决定了函数可以访问哪些数据,而作用域决定了数据访问的规则。简单来说,作用域的访问规则可以概括为:由内向外查找访问,内部可以访问外部,外部不能访问内部,这样的访问方式可以称为作用域链函数参数被视为当前上下文中的变量,因此与上下文中的其他变量遵循相同的访问规则。闭包既然我们已经对“上下文”和“作用域”这两个知识点有了简单的了解,那么谈闭包就很友好了。闭包的定义红皮书:闭包是指那些引用另一个函数作用域的函数MDN中变量的函数:一个函数被绑定到它周围状态(lexicaenvironment,lexicalenvironment)的引用(或者函数被引用包围),比如一个组合就是一个闭包(closure)。也就是说,闭包允许您从内部函数内部访问外部函数的范围。在JavaScript中,无论何时创建一个函数,都会在创建该函数的同时创建一个闭包。阮一峰:闭包就是一个可以读取其他函数内部变量的函数。只有函数内部的子函数才能读取局部变量,所以闭包可以简单理解为“函数内部定义的函数”。总结一下:一个函数的执行可以触发另一个函数的定义(函数声明、函数表达式),支持在另一个函数作用域内引用变量。所以这个函数是一个闭包。为什么会有闭包?总结以上理论,我们可以知道局部作用域可以访问全局作用域,但是全局作用域不能访问局部,两个不相关的局部不能相互访问。那么,只要思路不走偏,解决办法总是比困难多。我们需要解决这类问题,结论是:“关闭”。闭包就像一座桥梁,将多个不相关的作用域串联起来,实现互通。闭包的原理是利用变量环境和作用域链访问规则。关闭的目的。闭包最大的用处就是有两个可以从函数体中读取的内部变量,从而使闭包的变量始终以闭包的形式保存在内存中。:把函数当作一等公民,当作普通变量1:返回一个函数functionfun(){varaaa=111returnfunction(b){returnaaa+b}}fun()()经典场景:反-shakeThrottle2:返回一个函数变量functionfun(a){letfn=function(b){returna+b}returnfn;}fun()()3:作为一个全局闭包函数varcall;functionfun(a){call=function(b){returna+b}}fun()call()4:passfunctionfun1(fn){fn()//这个fn函数是一个闭包}functionfun2(){letstr='molly'functionfun3(){console.log(str)}fun1(fun3)}fun2()5:回调函数functionajax(data){console.log(data)}functionsync(){constobj={name:'molly',a:a}ajax(obj)}6:IIFE立即执行函数;!(function(){...});经典场景:for(vari=1;i<=5;i++){(function(j){setTimeout(functiontimer(){console.log(j)},0)})(i)}jquery自定义封装插件也是从立即执行函数开始的闭包的优点:可以像沙箱一样访问两个不相关的作用域变量,存储变量代码封装,使用效用函数作为值。入参返回闭包的坏处:内存泄漏如何避免内存泄漏?将函数指针指向null,将其纳入垃圾回收范围,完成闭包函数fun(a){returnnfunction(b){returna+b}}leta=fun()a=null或者闭包也执行a()这里简单说一下为什么闭包会导致内存泄露,以及js的垃圾回收机制大致分为“标记清理”和“计数参考”两种。当闭包中的变量在另一个函数中使用时,该变量不会被识别为垃圾,但不会在常驻内存中被清理。导致额外的内存消耗问题?你知道有哪些巧妙使用闭包的场景或代码吗?欢迎在评论区留言讨论!谢谢,欢迎关注我的个人公众号前端有技巧,每天都会给大家送上新鲜优质的好文章。回复“福利”,即可获得我精心准备的前端知识大礼包。愿你眼里有光,前行!有兴趣的朋友也可以加我微信:毛利molly或者前端交流群,和众多优秀的前端攻城狮交流技术,一起玩!
