做者:Dmitri Pavlutin翻译:疯狂的技术宅javascript
原文:https://dmitripavlutin.com/ja...前端
未经容许严禁转载java
在 JavaScript 中,代码块、函数或模块为变量建立做用域。例如 if
代码块为变量 message
建立做用域:程序员
if (true) { const message = 'Hello'; console.log(message); // 'Hello' } console.log(message); // throws ReferenceError
在 if
代码块做用域内能够访问 message
。可是在做用域以外,该变量不可访问。面试
好的,这是做用域的简短介绍。若是你想了解更多信息,建议阅读个人文章用简单的词解释 JavaScript 做用域。segmentfault
如下是 5 种有趣的状况,其中 JavaScript 做用域的行为与你预期的不一样。你可能会研究这些案例以提升对做用域的了解,或者只是为面试作准备。服务器
思考如下代码片断:微信
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
。app
l
变量是使用 var
语句声明的。你可能已经知道,var
变量仅受函数体做用域限制而并不是代码块。
相反,变量 i
使用 let
语句声明。由于 let
变量是块做用域的,因此 i
仅在 for
循环做用域内才可访问。
把 l
声明从 var l = colors.length
改成 const l = colors.length
。如今变量 l
被封装在 for
循环体内。
在如下代码段中:
// ES2015 env { function hello() { return 'Hello!'; } } hello(); // ???
调用 hello()
会怎样? (代码段在 ES2015 环境中执行)
由于代码块为函数声明建立了做用域,因此在 ES2015 环境中调用 hello()
会引起 ReferenceError: hello is not defined
。
有趣的是,在 ES2015 以前的环境中,在执行上述代码段时不会抛出错误。 你知道为何吗?请在下面的评论中写下你的答案!
你能够在代码块中导入模块吗?
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
语句,由于它们是在运行时执行的。
思考如下函数:
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
。
如下代码在代码块内定义了一个函数和一个类:
if (true) { function greet() { // function body } class Greeter { // class body } } greet(); // ??? new Greeter(); // ???
是否能够在块做用域以外访问 greet
和 Greeter
? (考虑 ES2015 环境)
function
和 class
声明都是块做用域的。因此在代码块做用域外调用函数 greet()
和构造函数 new Greeter()
就会抛出 ReferenceError
。
必须注意 var
变量,由于它们是函数做用域的,即便是在代码块中定义的。
因为 ES2015 模块系统是静态的,所以你必须在模块做用域内使用 import
语法(以及 export
)。
函数参数具备其做用域。设置默认参数值时,请确保默认表达式内的变量已经用值初始化。
在 ES2015 运行时环境中,函数和类声明是块做用域的。可是在 ES2015 以前的环境中,函数声明仅在函数做用域内。
但愿这些陷阱可以帮你巩固做用域知识!