深入理解javascript中的作用域(一)

Python部落(python.freelycode.com)组织翻译,禁止转载,欢迎转发。

在教程中,我们将深入理解JavaScript作用域(Scope)的一切知识。所以,快来学习吧。


介绍


javascript中有一个被称为作用域的概念。虽然作用域的概念对于许多新开发者来说不容易理解,但我将尽力在最简单的作用域内向您解释。理解作用域将使您的代码脱颖而出,减少错误,并帮助您使用它强大的设计模式。


什么是作用域?


作用域是在运行时代码中的某些特定部分中变量,函数和对象的可访问范围。换句话说,作用域决定了代码区域中变量和其他资源的可见性。


为什么说作用域是最小访问原则?


那么,什么是限制变量的可见性,而不是在你的代码中的任何地方?一个优点是作用域为您的代码提供了某种程度的安全性。计算机安全的一个常见原则是用户应该一次只访问他们需要的东西。


联想一下计算机管理员。由于他们对公司的系统有很多权限,因此向他们授予超级管理员权限就好。假设公司有三个计算机管理员,他们都可以完全访问系统,一切工作顺利。但突然发生了一件事,你的系统感染了恶意病毒。现在你不知道这是谁的错误?你意识到应该授予普通用户权限,并且只在需要时授予超级访问权限。这将帮助您跟踪更改,并记录谁做了什么的帐户。这被称为最小访问原则。看起来很直观?这个原则也适用于编程语言设计,在大多数编程语言中被称为作用域,包括我们接下来要研究的JavaScript。


随着编程旅程的持续,你会意识到,代码的作用域部分有助于提高效率,跟踪bug以及修复它们。作用域还解决了命名问题,在不同作用域中变量名称可以相同。记住不要将作用域与上下文混淆。它们都有不同的特征。


javascript中的作用域


作用域在javascript中有两种类型:

  • 全局作用域

  • 局部作用域


在函数内定义的变量即局部作用域,而在函数外部定义的变量即外部作用域。


全局作用域


当在文档初始处编写JavaScript时,就已经处在了全局作用域。全局作用域贯穿整个javascript文档。如果变量在函数之外定义,则变量处于全局作用域内。

blob.png


在全局作用域内的变量可以在任何其他作用域内访问和修改。

blob.png


局部作用域


局部作用域是指那些在全局域中定义的域。并且它们对于该函数的每次调用具有不同的作用域。这意味着具有相同名称的变量可以在不同的函数中使用。这是因为这些变量被绑定到它们各自具有不同作用域的相应函数,并且在其他函数中不可访问。

blob.png


块语句


块语句比如if和switch条件语句或for和while循环语句,与函数不同的是,它们不会创建一个新的作用域。在块语句内定义的变量将保留在它们已经在的作用域内。

blob.png


ECMAScript的6引入了let和const关键字。这些关键字可用于替换var关键字。

blob.png


与var关键字相反,let和const关键字支持块语句在局部作用域中声明。

blob.png

只要应用程序存在,全局范围就会存在。只要你的函数被调用和执行,局部范围就会存在。


上下文


许多开发人员经常将作用域和上下文混淆,就如同他们引用相同的概念。但事实并非如此。作用域是我们前面讨论的,Context用于指代this代码中某些特定部分的值。作用域是指变量的可见性,上下文是指this同一作用域内的值。我们也可以使用函数方法来改变上下文,我们将在后面讨论。在全局范围上下文中总是Window对象。

blob.png


如果作用域在对象的方法中,则上下文将是该方法所属的对象。

blob.png

(new User).logName()是一种将对象存储在变量中然后调用logName函数的简单方法。在这里,您不需要创建一个新的变量。


你会注意到的一个事情是,如果你使用new关键字调用你的函数上下文的值不同。然后将上下文设置为被调用函数的实例。考虑上面的示例之一,使用new关键字调用的函数。

blob.png


当在严格模式中调用函数时,上下文将默认为undefined。

执行上下文


为了消除所有的混乱与我们之前的了解,执行上下文中的上下文一词是指作用域而不是上下文。这是一个奇怪的命名约定,但由于JavaScipt规范,我们与它捆绑。


JavaScript是一个单线程语言,因此它一次只能执行一个任务。其余任务在执行上下文中排队。正如我早些时候告诉你,当JavaScript解释器开始执行你的代码时,上下文(作用域)默认设置为全局。这个全局上下文附加到你的执行上下文,它实际上是启动执行上下文的第一个上下文。


之后,每个函数调用(启用)将其上下文附加到执行上下文。当在该函数或其他地方调用另一个函数时,也会发生同样的情况。

每个函数创建自己的执行上下文


一旦浏览器完成该上下文中的代码,那么该上下文将从执行上下文弹出,并且执行上下文中的当前上下文的状态将被传送到父上下文。浏览器总是执行堆栈顶部的执行上下文(这实际上是代码中最内层的作用域)


无论多少函数上下文,都只能有一个全局上下文。


执行上下文有创建和代码执行的两个阶段。

创建阶段

第一阶段是创建阶段,调用函数但其代码尚未执行时出现。在创建阶段发生的三个主要事情是:

  • 创建变量(激活)对象

  • 创建范围链

  • context(this)的值的设置


变量对象



变量对象,也称为激活对象,包含在执行上下文的特定分支中定义的所有变量,函数和其他声明。当一个函数被调用时,解释器扫描它所有的资源,包括函数参数,变量和其他声明。包装成一个单一的对象即成为变量对象。

blob.png

域链

在执行上下文的创建阶段,域链在变量对象之后创建。域链本身包含变量对象。域链用于解析变量。当被要求解析变量时,JavaScript总是从代码嵌套的最内层开始,并且继续跳回到父作用域,直到它找到变量或任何其他正在寻找的资源。域链可以简单地定义为包含其自身执行上下文的变量对象的对象,以及其父对象的所有其他执行上下文,一个具有很多其他对象的对象。

blob.png


执行上下文对象

执行上下文可以表示为一个抽象对象,如下所示:

blob.png


代码执行阶段


在执行上下文的第二阶段(即代码执行阶段)中,分配其他值并最终执行代码。



词法作用域


词法作用域意味着在嵌套的函数组中,内部函数可以访问其父作用域的变量和其他资源。这意味着子函数在词法域上绑定到他们父级的执行上下文。词法作用域有时也称为静态作用域。


blob.png


你会注意到词法作用域的事情是它的向前工作,意味着name可以通过它的子级的执行上下文访问。但是它不能向其父对象反向工作,这意味着变量likes不能被其父对象访问。这也告诉我们,在不同执行上下文中具有相同名称的变量从执行堆栈的顶部到底部获得优先级。在最内层函数(执行堆栈的最上层上下文)中,具有类似于另一变量的名称的变量将具有较高优先级。


闭包


闭包的概念与词法域密切相关,我们在上面讲过。当内部函数尝试访问其外部函数的作用域链,意味着词法作用域之外的变量时,创建闭包。闭包包含它们自己的域链,它们父作用域链和全局作用域。


闭包不仅可以访问在其外部函数中定义的变量,还可以访问外部函数的参数。


闭包也可以访问其外部函数的变量,即使在函数返回后。这允许返回的函数维护对外部函数的所有资源的访问。


从函数返回内部函数时,尝试调用外部函数时,不会调用返回的函数。您必须首先将外部函数的调用保存在单独的变量中,然后将该变量调用为函数。考虑这个例子:


blob.png


这里要注意的关键是,that greetLetter函数可以访问greet函数的名称变量,即使在返回之后。从greet没有变量赋值的函数调用返回的函数的一种方法是使用括号()两次,()()如下所示:


blob.png


私有域和公共域

在许多其他编程语言中,您可以使用公共,专用和受保护的范围来设置类的属性和方法的可见性。考虑使用PHP语言的这个例子:


blob.png



来自公共(全局)作用域的封装函数使它们免受弱势攻击。但在JavaScript中,没有公共或私有作用域的东西。但是,我们可以使用闭包来模拟此功能。为了保持一切与全局分离,我们必须首先将我们的函数封装在如下所示的函数中:


blob.png


函数结束处的括号告诉解释器在没有调用的情况下立即执行它。我们可以在其中添加函数和变量,它们不会在外部访问。但是,如果我们想在外面访问它们,如果我们想要其中一些是公开的,而其中一些是私有的呢?一种类型的闭包,我们可以使用,被称为模块模式,它允许我们使用对象中的公共和私有范围来限定我们的函数。



模块模式


模块模式如下所示:

blob.png


模块的return语句包含我们的公共函数。私有函数只是那些不返回的函数。不返回函数使它们在模块命名空间之外不可访问。但是我们的公共函数可以访问我们的私有函数,这使它们对于帮助函数,AJAX调用和其他事情很方便。


blob.png


私有函数一个惯例是用下划线开始,并返回包含我们的公共函数的匿名对象。这使得它们很容易在长对象中管理。这是它的样子:

blob.png





英文原文:https://scotch.io/tutorials/understanding-scope-in-javascript
译者:winston