范围和闭包在JavaScript中非常重要。但是刚开始学JavaScript的时候,很难理解。本文将通过一些示例帮助您理解它们。让我们先从范围开始。范围JavaScript的范围定义了您可以访问哪些变量。作用域有两种:全局作用域和局部作用域。全局范围在函数声明或花括号之外定义的所有变量都在全局范围内。但是,这个规则只在运行在浏览器中的JavaScript中有效。如果你在Node.js中,全局范围内的变量是不同的,但本文不讨论Node.js。`constglobalVariable='somevalue'`一旦你声明了一个全局变量,你就可以在任何地方使用它,包括在函数内部。consthello='你好CSS-Tricks读者!functionsayHello(){console.log(hello)}console.log(hello)//'HelloCSS-TricksReader!'sayHello()//'你好CSS-Tricks读者!'虽然您可以在全局范围内定义变量,但不建议这样做。由于命名冲突的可能性,两个或多个变量使用相同的变量名。如果在定义变量时使用const或let,当出现命名冲突时会得到错误信息。这是不可取的。//不要这样做!letthing='something'letthing='somethingelse'//错误,thing已经被声明如果在定义变量时使用var,第二个定义将覆盖***第二个定义。它还使代码更难调试并且是不可取的。//不要这样做!varthing='something'varthing='somethingelse'//也许在你的codeconsole.log(thing)中完全不同的地方//'somethingelse'所以,你应该尝试使用局部变量,而不是全局变量,局部范围在代码的特定范围内使用的变量可以在本地范围内定义。这是一个局部变量。JavaScript中有两种局部作用域:函数作用域和块作用域。我们从功能范围开始。函数作用域当您在函数内定义变量时,它在函数内的任何地方都可用。在函数之外,您无法访问它。例如,在下面的例子中,sayHello函数中的hello变量:functionsayHello(){consthello='HelloCSS-TricksReader!'console.log(hello)}sayHello()//'HelloCSS-TricksReader!'console.log(hello)//错误,未定义hello块级作用域当你在使用大括号声明一个const或let变量时,您只能在大括号内使用此变量。在下面的示例中,hello只能在花括号内使用。{consthello='你好CSS-Tricks读者!'console.log(hello)//'HelloCSS-TricksReader!'}console.log(hello)//错误,未定义hello块级作用域是函数字段的子集,因为函数是用大括号定义的(除非您显式使用return语句和箭头函数)。函数提升和范围当用函数定义时,函数被提升到当前范围的顶部。因此,下面的代码是等价的://这与下面的代码相同sayHello()functionsayHello(){console.log('HelloCSS-TricksReader!')}//这与上面的代码相同sayHello(){console.log('HelloCSS-TricksReader!')}当使用函数表达式定义sayHello()时,该函数不会提升到变量作用域的顶部。sayHello()//错误,sayHello未定义constsayHello=function(){console.log(aFunction)}由于这里有两个变量,函数提升可能会造成混淆,所以不会生效。所以一定要在使用前定义函数。函数不能访问其他函数的作用域当不同的函数分别定义时,虽然一个函数可以在一个函数中被调用,但是一个函数仍然不能访问其他函数的作用域。在以下示例中,second无法访问变量firstFunctionVariable。functionfirst(){constfirstFunctionVariable=`I'mpartoffirst`}functionsecond(){first()console.log(firstFunctionVariable)//错误,firstFunctionVariable未定义}如果嵌套作用域定义在函数function内部,则内层函数可以访问外层函数的变量,反之则不行。这种效果是词法范围。外部函数不能访问内部函数的变量。functionouterFunction(){constouter=`我是外部函数!`functioninnerFunction(){constinner=`我是内部函数!`console.log(outer)//我是外部函数!}console.log(inner)//Error,innerisnotdefined}如果将作用域的机制形象化,你可以想象一个双向镜(单面透视玻璃)。你可以从里面看到外面,但外面的人看不到你。函数作用域就像一面双向镜子。你可以从里面向外看,但外面看不到你。嵌套作用域是一种类似的机制,只是相当于拥有更多的双向镜像。多层功能意味着多个双向镜像。通过理解前面关于作用域的部分,你可以理解什么是闭包。闭包当您在函数内部创建一个新函数时,它等同于创建一个闭包。内部函数是闭包。通常,为了让外部函数的内部变量可以访问,一般会返回这个闭包。functionouterFunction(){constouter=`我看到了外部变量!`functioninnerFunction(){console.log(outer)}returninnerFunction}outerFunction()()//我看到了外部变量!因为内部函数是返回值,所以可以简化函数声明部分:functionouterFunction(){constouter=`我看到外部变量了!`)()//我看到了外部变量!由于闭包可以访问外部函数中的变量,因此它们通常有两个目的:减少副作用创建私有变量使用闭包来控制副作用当您在函数返回值时执行某些操作时,通常会发生一些副作用。副作用在很多情况下都会发生,比如Ajax调用、超时,甚至是console.log输出语句:function(x){console.log('Aconsole.logisasideeffect!')}当你使用当使用使用闭包来控制副作用,您实际上需要考虑可能会混淆代码工作流程的事情,例如Ajax或超时。为了澄清事情,看例子更方便:例如,你想为你朋友的生日做一个蛋糕。做这个蛋糕可能需要1秒,所以你写一个函数记录1秒后,蛋糕做好了。为了使代码简短易读,我使用了ES6箭头函数:functionmakeCake(){setTimeout(_=>console.log(`Madeacake`,1000))}:延迟。更进一步,假设您希望您的朋友能够选择蛋糕的口味。然后向蛋糕makeCake函数添加一个参数。functionmakeCake(flavor){setTimeout(_=>console.log(`Madea${flavor}cake!`,1000))}所以当你调用这个函数时,一秒钟后这个新口味的蛋糕就准备好了。makeCake('banana')//做了一个香蕉蛋糕!但这里的问题是你不想马上知道蛋糕的味道。你只需要知道时间到了,蛋糕准备好了。为了解决这个问题,可以写一个保存蛋糕味道的prepareCake函数。然后,返回内部调用prepareCake的闭包makeCake。从这里,你需要的时候可以调用它,蛋糕会在一秒钟内准备好。functionprepareCake(flavor){returnfunction(){setTimeout(_=>console.log(`Madea${flavor}cake!`,1000))}}constmakeCakeLater=prepareCake('banana')//稍后在你的代码...makeCakeLater()//做了一个香蕉蛋糕!这是使用闭包来减少副作用:您可以创建一个可以驱动的内部闭包。私有变量和闭包如前所述,函数内的变量不能在函数外访问。由于无法访问,因此可以称为私有变量。但是,有时您确实需要访问私有变量。这就是闭包派上用场的地方。functionsecret(secretCode){return{saySecretCode(){console.log(secretCode)}}}consttheSecret=secret('CSSTricksisamazing')theSecret.saySecretCode()//'CSSTricksisamazing'在这个例子中saySecretCode函数将变量secretCode暴露在原始函数之外。因此,它也被称为特权功能。使用DevTools调试Chrome和Firefox的开发者工具可以让我们轻松调试当前范围内可以访问的各种变量。一般来说,有两种方法。第一种方法是在代码中使用debugger关键字。这使得在浏览器中运行的JavaScript可以暂停以进行调试。这是一个prepareCake示例:functionprepareCake(flavor){//添加调试器debuggerreturnfunction(){setTimeout(_=>console.log(`Madea${flavor}cake!`,1000))}}constmakeCakeLater=prepareCake('banana')打开Chrome的开发者工具,导航到Source页面(或者Firefox的Debugger页面),可以看到可以访问的变量。使用调试器调试prepareCake的范围。您还可以将debugger关键字放在闭包中。注意比较变量的范围:functionprepareCake(flavor){returnfunction(){//AddingdebuggerdebuggersetTimeout(_=>console.log(`Madea${flavor}cake!`,1000))}}constmakeCakeLater=prepareCake('banana')第二种调试闭包内部作用域的方法是直接在代码对应位置打断点,点击对应行号即可。通过断点调试作用域总结闭包和作用域并不难理解。一旦你使用双向镜像来理解它们,它们就非常简单了。当你在函数内声明一个变量时,你只能在函数内访问它。这些变量的范围仅限于函数。如果你在一个函数内部定义了一个内部函数,那么这个内部函数就被称为闭包。它仍然可以访问外部函数的范围。只要问你是否有任何问题。我尽量尽快回答你的问题。如果您喜欢这篇文章,也许您会喜欢我在博客和时事通讯中发布的其他与前端开发相关的文章。我刚刚创建了我的新品牌,(而且是免费的!)电子邮件课程:JavaScriptRoadmap。(希望你喜欢!)
