严格模式是 JavaScript 更严格的变体
JavaScript 历史悠久,也背负着许多值得商榷的设计:会悄悄创建全局变量的赋值,含义随调用方式而变的 this,以及重复的参数名居然也能……正常运行。改掉其中任何一个都会让网络上的大量页面崩溃,所以 TC39(维护这门语言的委员会)选择了另一条路:提供一种可选的模式,把这些锋利的边角磨平。
这种可选模式就叫严格模式。在文件或函数顶部写一行就能开启:
运行它,你会得到 ReferenceError: x is not defined。如果没有这条指令,同样的代码会愉快地创建一个名为 x 的全局变量,并打印 10。同一门语言,两套不同的规则。
'use strict' 指令
这条指令只是一个字符串字面量:'use strict'; 或 "use strict";。它必须是脚本或函数的第一条语句。只要它前面有任何东西,哪怕只是一个多余的表达式,引擎都会把它当作普通字符串而忽略掉。
你可以把指令放进某个函数内部,让严格模式只作用于这一个函数。实际上现在已经没人这么做了:要么写模块(默认就是严格模式),要么给整个文件开启。
严格模式到底改变了什么
严格模式不是一条规则,而是一组规则。要点如下:
- 给未声明的变量赋值会抛出错误,而不是创建全局变量。
- 普通函数调用中的
this是undefined,而不是全局对象。 - 重复的参数名(
function f(a, a) {})是语法错误。 - 给只读属性赋值会抛出错误,而不是静默失败。
- 删除普通变量或函数会抛出错误。
- 几个为将来保留的关键字(
implements、interface、package、private、protected、public、static、yield)不能用作变量名。
简单看一下:
每一条都是“引擎更早地注意到了一个可能的错误”。这就是严格模式的全部价值。
静默的全局变量 bug
这是严格模式存在的最大理由。没有它,一个拼写错误就会变成一个全局变量:
// Non-strict mode (don't do this)
function setup() {
usernmae = 'Ada'; // typo - creates window.usernmae
}
setup();
console.log(username); // undefined - the real variable was never set
程序照常运行,没有报错。这个 bug 要到后面才会暴露出来:某个本该读取 username 的地方拿到了 undefined。在严格模式下,同样的拼写错误一运行就会抛出错误,你几秒钟就能修好,而不是花上几个小时。
现代 JavaScript 默认就是严格模式
这一点常让人意外:你今天写的大多数 JavaScript 已经处于严格模式中,而你根本没在任何地方写过 'use strict'。
自动启用严格模式的两大来源:
- ES 模块。 任何
.mjs文件、任何<script type="module">,以及任何使用import/export导入的代码,都运行在严格模式下。不需要指令。 - 类体。
class { ... }内部的每条语句都是严格模式,即使所在文件不是。
那什么时候才需要自己写 'use strict'; 呢?只有在经典脚本中:用普通 <script> 标签加载的旧式 JS,或者使用 require 且没有模块打包的较旧的 Node.js 文件。对于任何新代码,模块都会替你完成这件事。
严格模式改变了普通函数中的 this
有一个行为值得特别说明,因为它常坑到新手。在非严格模式下,不通过对象直接调用一个普通函数时,this 会绑定到全局对象(浏览器中是 window,Node 中是 global)。在严格模式下,它绑定到 undefined:
这通常正是你想要的。如果 this 是 undefined,忘记绑定方法的 bug 会立刻暴露出来。如果它悄悄指向全局对象,bug 就会一直藏着,直到后面某个地方出问题。
严格模式不做什么
严格模式是在编译期和运行时收紧规则,它不会给你类型,不会捕获空值访问,也不会提示未使用的变量。这些是 TypeScript、ESLint 和编辑器的工作。把严格模式看作修复一组特定的语言级陷阱,而不是一张完整的安全网。
它也不等于“现代 JavaScript”。你可以在严格模式下写出糟糕的代码,也可以不写这条指令就写出出色的代码。它只是让几种特定的错误更难犯。
要点回顾
- 在文件或函数顶部写上
'use strict';,就会启用 JavaScript 更严格的变体。 - 拼写错误不再创建全局变量,
this不再悄悄变成全局对象,其他一些陷阱也会变成报错。 - ES 模块和
class类体会自动启用严格模式,你很少需要手写这条指令。 - 它的意义在于更早发现 bug,而不是启用新功能。
下一篇:注释
你已经见过指令、分号以及引擎在意的规则。接下来是你写给未来读者的说明:JavaScript 注释如何工作,以及什么时候值得写注释。
常见问题
JavaScript 中的严格模式是什么?
严格模式是 JavaScript 的一种可选变体,它把一些静默的错误变成真正的报错。在文件或函数顶部写上字符串 'use strict'; 就能启用它。它让语言更严格:给未声明的变量赋值会抛出错误,重复的参数名是非法的,普通函数调用中的 this 是 undefined 而不是全局对象。
怎样在 JavaScript 中启用严格模式?
把 'use strict'; 作为脚本或函数的第一条语句。它前面可以有注释和空行,但只要有任何其他语句,它就会变成一个没有任何作用的普通字符串。在现代代码中你很少需要自己写它:ES 模块(.mjs 文件或 <script type="module">)和任何 class 的类体都会自动启用严格模式。
为什么要在 JavaScript 中使用严格模式?
它能更早地发现 bug。在非严格模式下,像 usernmae = 'Ada' 这样的拼写错误会悄悄创建一个全局变量;在严格模式下,它会抛出 ReferenceError。严格模式还会保留未来的关键字,在较旧的引擎中禁止重复的属性名,并让 this 的行为可预测,这些都让代码更容易理解。
现代 JavaScript 还需要 'use strict' 吗?
通常不需要。ES 模块和 class 类体默认就是严格模式,而大多数现代项目要么是前者,要么是后者。只有在旧式的经典脚本中,也就是用普通 <script> 标签、不带 type="module" 加载的脚本中,你才需要显式写上 'use strict';。