面试中关于 JavaScript 做用域的 5 个陷阱

做者:Dmitri Pavlutin

翻译:疯狂的技术宅javascript

原文:https://dmitripavlutin.com/ja...前端

未经容许严禁转载java

img

在 JavaScript 中,代码块、函数或模块为变量建立做用域。例如 if 代码块为变量 message 建立做用域:程序员

if (true) {
  const message = 'Hello';
  console.log(message); // 'Hello'
}
console.log(message); // throws ReferenceError

if 代码块做用域内能够访问 message。可是在做用域以外,该变量不可访问。面试

好的,这是做用域的简短介绍。若是你想了解更多信息,建议阅读个人文章用简单的词解释 JavaScript 做用域segmentfault

如下是 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); // ???

当你打印 li 变量时会发生什么?多线程

答案

console.log(l) 输出数字 3 ,而 console.log(i) 则抛出 ReferenceErrorapp

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,访问外部做用域的变量 pp +1 = 1 + 1 = 2

5. 函数声明与类声明

如下代码在代码块内定义了一个函数和一个类:

if (true) {
  function greet() {
    // function body
  }

  class Greeter {
    // class body
  }
}

greet();       // ???
new Greeter(); // ???

是否能够在块做用域以外访问 greetGreeter(考虑 ES2015 环境)

答案

functionclass 声明都是块做用域的。因此在代码块做用域外调用函数 greet() 和构造函数 new Greeter() 就会抛出 ReferenceError

6. 总结

必须注意 var 变量,由于它们是函数做用域的,即便是在代码块中定义的。

因为 ES2015 模块系统是静态的,所以你必须在模块做用域内使用 import 语法(以及 export)。

函数参数具备其做用域。设置默认参数值时,请确保默认表达式内的变量已经用值初始化。

在 ES2015 运行时环境中,函数和类声明是块做用域的。可是在 ES2015 以前的环境中,函数声明仅在函数做用域内。

但愿这些陷阱可以帮你巩固做用域知识!


本文首发微信公众号:前端先锋

欢迎扫描二维码关注公众号,天天都给你推送新鲜的前端技术文章

欢迎扫描二维码关注公众号,天天都给你推送新鲜的前端技术文章

欢迎继续阅读本专栏其它高赞文章:


相关文章
相关标签/搜索