JavaScript闭包难点分析1.作用域基本介绍在ES6之前,只有全局作用域和函数作用域两种。ES6出现后,增加了一个新的块级作用域。1.全局作用域在JavaScript中,全局变量是挂载在window对象下的一个变量,所以你可以在网页的任何地方使用和访问这个全局变量。当我们定义很多全局变量时,很容易造成变量命名冲突,所以在定义变量的时候,要注意作用域的问题varglobalName='global'functiongetName(){console.log(globalName)//globalvarname='inner'console.log(name)//inner}getName()console.log(name)//errorconsole.log(globalName)//globalfunctionsetName(){vName='setName'}setName()console.log(vName)//setNameconsole.log(windwo.vName)//setName2。函数作用域在JavaScript中,函数定义的变量称为函数变量。这时只能在函数内部访问,所以它的作用域也是函数的内存,称为函数作用域。当函数执行完之后,这个局部变量会相应的被销毁。所以你会看到getName函数外面的名字是不可访问的functiongetName(){varname='inner'console.log(name)//inner}getName()console.log(name)//error3.Block级作用域ES6添加了一个新的块级作用域。最直接的体现就是新增了let关键字。使用let关键字定义的变量只能在块级范围内访问。有一个“临时死区”“特定”,也就是说这个变量在定义之前是不能使用的。if语句和for语句后面的{...}包含的是块级作用域console.log(a)//aisnotdefinedif(true){leta='123'console.log(a)//123}console.log(a)//a未定义2.什么是闭包?红皮书:闭包指的是一个函数可以访问另一个函数作用域内的变量MDN:一个函数与对其周围状态的引用捆绑在一起(或者函数被引用包围),这样的组合就是闭包。也就是说,闭包允许您从内部函数内部访问外部函数的范围。1、闭包的基本概念闭包实际上就是一个可以访问其他函数内部变量的函数。也就是说,一个定义在函数内部的函数,或者直接一个闭包就是一个内嵌函数。因为在正常情况下,一个函数的内部变量是不能被外部访问的(也就是全局变量和局部变量的区别),所以使用闭包就具有可以外部访问某个函数的内部变量的作用,从而使这些内部变量的值可以一直保存在内存中。functionfun1(){vara=1returnfunction(){console.log(a)}}fun1()varresult=fun1()result()//12.闭包的原因访问变量时,代码解释器会先在当前作用域中查找,如果没有找到,再到父作用域中查找,直到找到变量或者在父作用域中不存在,这样的链接就是作用域链。vara=1functionfun1(){vara=2functionfun2(){vara=3console.log(a)//3}}//fun1函数的作用域指向全局作用域(window)和自身;fun2函数的作用域指向全局作用域(window)、fun1及其自身;并从下往上搜索范围,直到找到全局范围窗口。如果全局作用域不存在,会报错functionfun1(){vara=2functionfun2(){console.log(a)//2}returnfun2}varresult=fun1()result()//是否意味着只有在函数返回时才会产生闭包?其实不是,回到闭包的本质,**我们只需要让父作用域的引用存在**varfun3functionfun1(){vara=2fun3=function(){console.log(a)}}fun1()fun3()参考视频讲解:进入学习闭包生成的精髓:当前环境中存在对父作用域的引用3.闭包的表达式返回一个函数,原因上面已经说了,这里就不赘述了。在计时器、事件监听器、Ajax请求、WebWorkers或任何异步进程中,只要使用回调函数,实际上就是在使用闭包。//2.1定时器setTimeout(functionhandler(){console.log('1')},1000)//2.2事件监听$('app').click(function(){console.log('EventListener')})作为函数参数传递的形式,例如下面的例子//3.作为函数参数传递的形式vara=1functionfoo(){vara=2functionbaz(){console.log(a)}bar(baz)}functionbar(fn){//这是闭包fn()}foo()//输出2,而不是1IIFE(立即执行的函数),创建闭包,保存全局范围(窗口)and当前函数的作用域,所以可以输出全局变量,如下图。//4.IIFE(立即执行函数)vara=2(functionIIFE(){console.log(a)//Output2})()IIFE这个函数有点特殊,它是一个自执行的匿名函数,这个匿名函数有自己的作用域。这样既可以防止外部访问这个IIFE中的变量,也不会污染全局作用域。我们经常在高级JavaScript编程中看到这样的函数。3、如何解决循环输出问题?for(vari=1;i<=5;i++){setTimeout(function(){console.log(i)},0)}//依次输出56setTimeouts作为宏任务,由于单线程eventLoopJS中的机制,宏任务是在主线程的同步任务执行完之后执行的,所以setTimeout中的回调是在循环结束后顺序执行的。因为setTimeout函数也是一个闭包,它的父作用域是window,而变量i是window上的全局变量,在开始执行setTimeout之前变量i已经是6,所以最后输出的是61。使用IIFE,当每次for循环,将此时的变量i传递给定时器。然后执行for(vari=1;i<=5;i++){(function(j){setTimeout(functiontimer(){console.log(j)},0)})(i)}2.使用ES6letlet中的让JS有块级作用域,代码的作用域是以块级为单位执行的。for(leti=1;i<=5;i++){setTimeout(function(){console.log()},0)}3.定时器传入第三个参数setTimeout作为常用定时器,它有第三个参数。在日常工作中,我们通常会用到前两个,一个是回调函数,一个是时间,第三个参数用得比较少。for(vari=1;i<=5;i++){setTimeout(function(j){console.log(j)},0,i)}第三个参数的传递改变了setTimeout的执行逻辑,从而达到我们想要的结果,这也是解决循环输出问题的一种方法
