前言,和大家一起学习作用域链与闭包。本文大纲:什么是作用域链?什么是词法作用域?什么是闭包?大家可以在评论区留言~scopechaintextstart是什么~请想想下面demo的名字打印什么functiontest(){console.log(name)}functiontest1(){constname='nameoftest1'test()}constname='globalname'test1()通过执行上下文分析代码的执行流程,当执行测试函数时:测试函数中的名称是什么?这就涉及到作用域链的定义:变量和函数的查找链就是作用域链。它决定了每一级上下文中的代码访问变量和函数的顺序:查找变量和函数时,首先在当前执行上下文中查找,如果没有当前执行上下文,则转到下一个执行上下文,如果没有,则进行下一个,直到全局执行Context,没有使用未定义的变量或函数就报错。在每个执行上下文的变量环境中,包含一个外部引用outer,指向外部执行上下文。链式结构是当前执行上下文>包含当前上下文的上下文1>包含上下文1的上下文2...。而这个demo会打印global的名字,原因是test执行上下文的outer指向了全局执行上下文,包括test1的outer也指向了全局执行上下文:可能有同学会疑惑为什么outeroftest指向全局执行上下文而不是test1,这是因为在JavaScript执行过程中,它的作用域链是由词法作用域决定的。什么是词法作用域?词法作用域由函数声明在代码中的位置决定。它是一个静态范围,通过它可以预测代码在执行期间如何查找标识符,以及它与函数的关系。你叫什么并不重要。所以刚才的例子打印了全局的名称。我们看一个具体的例子:constcount=0functiontest(){constcount=1functiontest1(){constcount=2functiontest2(){constcount=3}}}它的包含关系和作用域链:其实在GlobalScope全局作用域(Window)之前,还有一个ScriptScope脚本作用域,里面存放的是当前脚本中可以访问的let变量和const变量,而Global中存放的var变量是不在ScriptScope中的,类似于So脚本范围的全局范围。在下面的演示中再举一个例子。什么是闭包?闭包是引用另一个函数范围内的变量的函数,通常在嵌套函数中实现。像这个例子:varglobalVariable=1constscriptVariable=2functiontest(){letname='Jaychou'return{getName(){constcount=1returnname},setName(newValue){name=newValue}}}consttestFun=test()console.log(testFun.getName())//周杰伦testFun.setName('小明')console.log(testFun.getName())//小明大家可以根据scopechain到console.log(testFun.getName())的getName时,scopechain是什么样子的~我们用浏览器的开发者工具看看:scopechain就是当前作用域》测试函数的闭包》和脚本Domain>的函数为什么Global作用域叫测试函数的闭包?因为consttestFun=test()这个测试函数在执行的时候,test的函数执行上下文已经被销毁了,但是它返回的{getName(){},setName(){}}对象被testFun引用了,getName而setName引用的是测试函数中定义的name变量,所以这些引用的变量仍然需要保存在内存中,这些变量的集合称为一个闭包Closure;目前闭包中的name变量只能通过getName和setName来访问和设置,这也是闭包的作用之一:封装私有变量;scriptVariable变量保存在刚才提到的ScriptScope中,而globalVariable变量是用var声明的,所以是在GlobalScope(Window)中。看另一个具体案例来理解闭包:constglobalCount=0functiontest(){constcount=0returntest1functiontest1(){constcount1=1returntest2functiontest2(){constcount2=2console.log('test2',globalCount+count+count1+count2)}}}当test()()()执行到test2内部的console.log行时,其作用域链为当前作用域"closureoftest1"closureoftestPackage>ScriptScope>GlobalScopeClosure使用建议:当你不需要使用它的时候,注意解引用闭包的变量,这样闭包就会被释放。比如第一种情况下的testFun不需要,就释放掉testFun=null。闭包封装私有变量的实际用例只是getName和setName的情况,通过getName获取name,通过setName设置name封装singletonconstSingle=(function(){letinstance=nullreturnfunction(){if(!instance){instance={name:'jaychou',age:40}}returninstance}})()constobj1=newSingle()constobj2=newSingle()console.log(obj1===obj2)//true这只是一个例子。具体实例类型和支持的功能以实际项目为准。去抖和节流debounce:functiondebounce(fn,delay){lettimer=null;返回函数(){让上下文=this;让args=参数;计时器&&清除超时(计时器);timer=setTimeout(function(){fn.apply(context,args);},delay);}}油门:函数油门(fn,间隔){letlast=0;返回函数(){letnow=+newDate()if(now-last>=interval){fn.apply(this,arguments);最后=现在;更完整的防抖和节流实现可以参考Lodash,这里主要是演示闭包的使用,总结一下闭包的使用场景很多,很强大,可以说是经常看到在ReactHooks等前端项目中,这里只是列举几个非常简单实用的应用场景。小结本文主要介绍作用域链和闭包。结合第一部分扎实基础图解JavaScript执行机制一起看应该更容易理解。如果对大家有帮助,欢迎点赞关注哦~
