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.块级作用域ES6添加了一个新的块级作用域。最直接的体现就是新增了let关键字。使用let关键字定义的变量只能在块级范围内访问。area”,这意味着这个变量在定义之前不能使用。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(立即执行的函数),闭包被创建,全局作用域(窗口)保存的是当前函数的作用域,所以可以输出全局变量,如下图://4.IIFE(立即执行函数)vara=2(functionIIFE(){console.log(a)//输出2})()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了,所以最后输出的总是6。1.使用IIFE每次执行for循环时使用IIFE将变量i传递给定时器,然后执行:for(vari=1;i<=5;i++){(function(j){setTimeout(functiontimer(){console.log(j)},0)})(i)}2.在ES6中使用letlet让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的执行逻辑,达到了想要的结果,这也是解决循环输出问题的一种方式。
