本篇文章给大家分享 5 个关于 JavaScript 作用域的陷阱. 有一定的参考价值, 有需要的朋友可以参考一下, 希望对大家有所帮助.
在 JavaScript 中, 代码块, 函数或模块为变量创建作用域. 例如 if 代码块为变量 message 创建作用域:
- if (true) {
- const message = 'Hello';
- console.log(message); // 'Hello'
- }
- console.log(message); // throws ReferenceError
在 if 代码块作用域内可以访问 message. 但是在作用域之外, 该变量不可访问.
好的, 这是作用域的简短介绍. 如果你想了解更多信息, 建议阅读我的文章用简单的词解释 JavaScript 作用域 https://dmitripavlutin.com/javascript-scope/ .
以下是 5 种有趣的情况, 其中 JavaScript 作用域的行为与你预期的不同. 你可能会研究这些案例以提高对作用域的了解, 或者只是为面试做准备.
1. for 循环内的 var 变量
思考以下代码片段:
- const colors = ['red', 'blue', 'white'];
- for (let i = 0, var l = colors.length; i <l; i++) {
- console.log(colors[i]); // 'red', 'blue', 'white'
- }
- console.log(l); // ???
- console.log(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(p = 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 之前的环境中, 函数声明仅在函数作用域内.
希望这些陷阱能够帮你巩固作用域知识!
来源: http://www.css88.com/web/javascript/18255.html