共计 2244 个字符,预计需要花费 6 分钟才能阅读完成。
导读 | 以下是 5 种有趣的情况,其中 JavaScript 作用域的行为与你预期的不同。你可能会研究这些案例以提高对作用域的了解,或者只是为面试做准备。 |
在 JavaScript 中,代码块、函数或模块为变量创建作用域。例如 if 代码块为变量 message 创建作用域:
if (true) { | |
const message = 'Hello'; | |
console.log(message); // 'Hello' | |
} | |
console.log(message); // throws ReferenceError |
在 if 代码块作用域内可以访问 message。但是在作用域之外,该变量不可访问。
以下是 5 种有趣的情况,其中 JavaScript 作用域的行为与你预期的不同。你可能会研究这些案例以提高对作用域的了解,或者只是为面试做准备。
1. for 循环内的 var 变量
思考以下代码片段:
const colors = ['red', 'blue', 'white']; | |
for (let i = 0, var l = colors.length; i | |
当你打印 l 和 i 变量时会发生什么? | |
答案: | |
console.log(l) 输出数字 3,而 console.log(i) 则抛出 ReferenceError。 | |
l 变量是使用 var 语句声明的。你可能已经知道,var 变量仅受函数体作用域限制而并非代码块。 | |
相反,变量 i 使用 let 语句声明。因为 let 变量是块作用域的,所以 i 仅在 for 循环作用域内才可访问。 | |
修复: | |
把 l 声明从 var l = colors.length 改为 const l = colors.length。现在变量 l 被封装在 for 循环体内。 | |
2. 代码块中的函数声明 | |
在以下代码段中: | |
// ES2015 env | |
{function hello() {return 'Hello!';} | |
} | |
hello(); // ??? | |
调用 hello() 会怎样?(代码段在 ES2015 环境中执行) | |
答案: | |
因为代码块为函数声明创建了作用域,所以在 ES2015 环境中调用 hello() 会引发 ReferenceError: hello is not defined。 | |
有趣的是,在 ES2015 之前的环境中,在执行上述代码段时不会抛出错误。 | |
3. 你可以在哪里导入模块? | |
你可以在代码块中导入模块吗? | |
if (true) {import { myFunc} from 'myModule'; // ??? | |
myFunc();} | |
答案: | |
上面的脚本将触发错误:'import' and 'export' may only appear at the top-level。 | |
你只能在模块文件的最顶级作用域 (也称为模块作用域) 中导入模块。 | |
修复: | |
始终从模块作用域导入模块。另外一个好的做法是将 import 语句放在源文件的开头: | |
import {myFunc} from 'myModule'; | |
if (true) {myFunc(); | |
} | |
ES2015 的模块系统是静态的。通过分析 JavaScript 源代码而不是执行代码来确定模块的依赖关系。所以在代码块或函数中不能包含 import 语句,因为它们是在运行时执行的。 | |
4. 函数参数作用域 | |
思考以下函数: | |
let p = 1; | |
function myFunc(pp = p + 1) {return p;} | |
myFunc(); // ??? | |
调用 myFunc() 会发生什么? | |
答案: | |
当调用函数 myFunc() 时,将会引发错误:ReferenceError: Cannot access 'p' before initialization。 | |
发生这种情况是因为函数的参数具有自己的作用域(与函数作用域分开)。参数 p = p + 1 等效于 let p = p + 1。 | |
让我们仔细看看 p = p + 1。 | |
首先,定义变量 p。然后 JavaScript 尝试评估默认值表达式 p + 1,但此时绑定 p 已经创建但尚未初始化(不能访问外部作用域的变量 let p = 1)。因此抛出一个错误,即在初始化之前访问了 p。 | |
修复: | |
为了解决这个问题,你可以重命名变量 let p = 1,也可以重命名功能参数 p = p + 1。 | |
让我们选择重命名函数参数: | |
let p = 1; | |
function myFunc(q = p + 1) {return q;} | |
myFunc(); // => 2 | |
函数参数从 p 重命名为 q。当调用 myFunc() 时,未指定参数,因此将参数 q 初始化为默认值 p + 1。为了评估 p +1,访问外部作用域的变量 p:p +1 = 1 + 1 = 2。 | |
5. 函数声明与类声明 | |
以下代码在代码块内定义了一个函数和一个类: | |
if (true) {function greet() {// function body} | |
class Greeter {// class body} | |
} | |
greet(); // ??? | |
new Greeter(); // ??? | |
是否可以在块作用域之外访问 greet 和 Greeter?(考虑 ES2015 环境) | |
答案: | |
function 和 class 声明都是块作用域的。所以在代码块作用域外调用函数 greet() 和构造函数 new Greeter() 就会抛出 ReferenceError。 | |
6. 总结 | |
必须注意 var 变量,因为它们是函数作用域的,即使是在代码块中定义的。 | |
由于 ES2015 模块系统是静态的,因此你必须在模块作用域内使用 import 语法(以及 export)。 | |
函数参数具有其作用域。设置默认参数值时,请确保默认表达式内的变量已经用值初始化。 | |
在 ES2015 运行时环境中,函数和类声明是块作用域的。但是在 ES2015 之前的环境中,函数声明仅在函数作用域内。 | |
希望这些陷阱能够帮你巩固作用域知识! | |
阿里云 2 核 2G 服务器 3M 带宽 61 元 1 年,有高配 | |
腾讯云新客低至 82 元 / 年,老客户 99 元 / 年 | |
代金券:在阿里云专用满减优惠券 | |
正文完
星哥玩云-微信公众号
