自从ES6更新后,js有三种作用域
- 全局作用域
- 函数作用域
- 块级作用域
从执行上下文的角度来看作用域是什么?
全局作用域是全局执行上下文,函数作用域与函数执行上下文相关联
块级作用域在ES6中被介绍,它不同于以上两种作用域。
全局作用域的一个例子
最简单的方式理解块级作用域是将它与其他两中作用域比较
变量在全局作用域和函数作用域中工作相似,所以在这篇文章我们仅仅讨论全局作用域和块级作用域。
在这个例子中,我们仅有一个全局执行上下文和一个全局变量环境。
第二个apple覆盖了前一个,在程序执行后,这里仅仅只有一个apple变量,它的值为“banana”。
从作用域的角度,我们可以认为apple变量是在全局作用域。
块级作用域
我们可以通过let来重写上面的例子来介绍一种新的作用域--块级作用域
控制台打印两个不同的值,第一个apple变量保存的值为“apple”,而在if条件语句块中的apple变量值为“banana”。
为什么我们有两个同名的变量?
词法环境(Lexical environment)
让我们通过两个步骤来揭秘它是如何工作的
在编译阶段,值为undefined的apple变量被添加进全局执行上下文中
此刻,js引擎决定跳过第二个apple变量因为如下两个原因:
- 它是一个let创建的变量
- 它在一个块级作用
接下来,执行阶段开始,第一个apple变量被赋值为“apple”。
当读到if代码块时,一个嵌套的编译步骤发生。第二个值为undefined的apple变量被创建。
这个apple变量被添加到词法环境,而不是在变量环境中创建它。
马上,在词法环境中,一个“banana”值被被赋值到apple变量。
现在,我们有两个同名变量被管理在两个环境中,那就是js引擎如何处理let声明,并且它向前兼容var。
词法环境中的作用域栈
为了更好地理解let和var之间的不同,让我们将他们合并在一个有趣的例子中。
在编译阶段,apple和grape变量在变量环境中被初始化,grape初始化被提升。与此同时,banana变量在词法环境中被创建。
执行阶段开始,变量apple被赋值为“global apple”,而banana值为“global banana”。
是时候关注代码块里的变量了
它是另一个banana变量,在同一个词法环境里能够有两个相同名字的banana变量吗?
当在块级作用域里看见let或const声明的变量时,js引擎会为分别它们创建一个分开的区域,词法环境会为它内部的变量维护一个类似与栈的结构,所以同名变量不会相互冲突。
在这里,变量banana和orange放置在一个单独的作用域
接下来,所有的变量被分配一个相关的值。
执行完最后的赋值语句后,所有的变量准备完毕。
当打印第一个变量时,js引擎首先尝试在词法环境中从顶部到底部查找apple变量,然后它检查全局变量环境并找到apple变量,打印“global apple”。
当搜索banana变量时,js引擎根据同样的步骤,找到它并打印“block banana”。
此刻,代码块里没有更多可执行的代码了,所以块级作用域被移除。
脚本继续执行,在全局变量环境里找到banana和grape变量并相应地打印“global banana”和“global grape”。
当脚本查找orange变量时,这个变量不存在于任何地方,因为orange存在的作用域已经被移除。所以会抛出错误“orange is not defined”。
整个脚本执行完毕。
除了作用域,这里还有其他与let和const相关的问题吗?
创建,初始化和赋值的技巧
从编译到执行,一个变量经历三个步骤:
- 创建
- 初始化
- 赋值
上面的代码将会打印什么?是“apple”?或者是“banana”?
出人意料,它会报错“Cannot access apple
before initialization”
这个错误与提升有关。
- 对于一个let声明的变量,它的创建会被提升,但是初始化和赋值不会被提升。
- 对于一个var声明的变量,它的创建和初始化会被提升,赋值不会被提升。
- 对于一个函数,它的创建,初始化和赋值在同一时间都被提升。
人们把代码初始化之前的区域称为暂时性死区
- 如果你尝试在一个变量创建之前访问它,你将会看见一个错误“[variable name] is not defined.”
- 如果你决定在一个变量初始化之前访问它,你将会看见错误“Cannot access [variable name] before initialization.”
- 如果你在一个变量赋值之前打印它,你将会看见打印undefined。
总结:
- 词法环境是执行环上下文组成的另一个部分
- let和const声明的变量在块级作用中是在执行阶段被创建而不是编译阶段中被创建。
- 这些变量存储在词法环境中。
- 多个块级作用域在词法环境中是维护在一个类似栈的结构中。
- 当js引擎执行完块级作用域中所有的代码,let和const声明的相关变量会被移除。