JavaScript闭包之作用域和词法作用域
接着上一篇文章JavaScript闭包之自执行函数和匿名函数,今天我们继续来说说闭包的前置知识–作用域和词法作用域。
作用域
作用域是编程语言中的一个重要概念,目前大部分编程语言都有作用域这个概念。JavaScript当然也不例外。那么什么是作用域呢?
下面我们就先来说说JavaScript的作用域
简单的来说,作用域就是定义的一组用来查找变量的规则,那么JavaScript是如何来查找变量的呢?
我们先来看个例子:
1 | function foo () { |
在执行foo()的时候,JavaScript引擎去查找定义a的地方,诶,在上方就是的,于是不继续找了直接打印。
再来看一个例子:
1 | var a = 1; |
在执行foo()的时候,JavaScript引擎试图在函数内部查找a的值,但是遗憾的是没有找到,JavaScript引擎这个时候就很聪明了,在里面找不到我就不能去外面找嘛,诶,还真让我找到了,在函数外面的第一层里面就有一个a值,找到了我就不继续找了,直接去打印吧。
注意上面的两段代码,它们之间一个共同点就是查找变量,第一段代码在函数内部直接就查找到了,第二段代码是在函数外部查找到的。
把上面这段话正式一点的说就是:第一段代码是在函数作用域查找到了变量a,第二段代码是在全局作用域查找到了变量a。所以作用域就是查找变量的地方。
至于第二段代码查找变量的过程,有一个由里向外的过程,好像是顺着一条链条从下到上查找,这条链条我们就称之为作用域链,关于作用域链我们后面会详细讲解。
静态作用域和动态作用域
先来看看维基百科给静态作用域和动态作用域的定义:
静态作用域又叫做词法作用域,采用词法作用域的变量叫词法变量。函数的作用域在函数定义的时候就决定了。
采用动态作用域的变量叫做动态变量。函数的作用域是在函数调用的时候才决定的。
让我们来看个例子就能明白它们两个的区别了。
1 | var value = 1; |
我们用假设法来分析一下这个函数的执行,假设JavaScript采用的是静态作用域,那么函数的作用域在函数定义的时候就已经确定了,当执行foo()函数的时候,先在foo()函数内部查找是否有变量value,如果没有就到全局作用域中查找,那么此时打印出来的就应该是1。
而如果JavaScript采用的是动态作用域,函数的作用域是在函数调用的时候决定的,那么当执行函数foo()的时候,先从foo()函数内部查找是否有变量value,如果没有就去函数调用的上一级作用域中查找,一级级的查找,直到全局作用域,此时函数就应该打印2。
让我们执行函数
通过函数的执行结果,和我们分析的第一段结果一样,所以JavaScript采用的是静态作用域,也就是词法作用域。
变量的查找类型
在JavaScript中,变量的查找有两种类型–LHS和RHS。
LHS,RHS 这两个术语就是出现在引擎对变量进行查询的时候。在《你不知道的Javascript(上)》也有很清楚的描述。在这里,我想引用freecodecamp 上面的回答来解释:
LHS = 变量赋值或写入内存。想象为将文本文件保存到硬盘中。
RHS = 变量查找或从内存中读取。想象为从硬盘打开文本文件。
两者的特性:
- 都会在所有作用域中查询
- 严格模式下,找不到所需的变量时,引擎都会抛出ReferenceError异常。
- 非严格模式下,LHR稍微比较特殊: 会自动创建一个全局变量。
- 查询成功时,如果对变量的值进行不合理的操作,比如:对一个非函数类型的值进行函数调用,引擎会抛出TypeError异常。
我们来看一个简单的例子:
1 | function foo () { |
这段代码在预编译的时候存在一个变量提升的过程,这个以后会详细讲解,这里并不妨碍我们理解LHS和RHS。
预编译后:
1 | var a; |
我们直接来看查找过程,在查找变量a和变量b时用的是RHS(读取内存),而查找a的时候a已经被赋值a = 1,这步操作也就是LHS(写入内存),而b此时只是被定义了还没有被赋值,所以查找到的只是undefined。
本文介绍了JavaScript的词法作用域以及两种变量查找方式,下一篇文章我们将重点介绍下JavaScript中的提升。
本文完