先记住这个答案
ES 模块的代码天生就是严格模式,不需要写 "use strict",也没有办法关掉。原因很直接:严格模式在 ES5 时只能靠手动开启,是因为要兼容海量老代码;模块是全新的语法,不存在老代码,规范就趁机把更安全的规则设成默认值,class 的类体也是同样的处理。影响落到模块里的每个函数上:普通调用时 this 是 undefined 而不是全局对象,给未声明的变量赋值会抛 ReferenceError,arguments 不再和形参联动,with 语句、重名参数都成了语法错误。
- 模块代码自动严格模式,不需要也关不掉
- 原因是模块没有历史包袱,可以直接用更安全的规则
- 模块里普通调用函数,
this是undefined - 未声明变量赋值会抛引用错误
模块里的函数和普通脚本里有什么不同
在模块里写的每个函数都是严格模式。下面用 'use strict' 模拟模块内的函数,和非严格函数对比。
function sloppyThis() {
return this === globalThis
}
function strictThis() {
'use strict' // 模块里的函数不写也是这个效果
return this
}
console.log(sloppyThis(), strictThis())
function sloppyArgs(a) {
a = 2
return arguments[0]
}
function strictArgs(a) {
'use strict'
a = 2
return arguments[0]
}
console.log(sloppyArgs(1), strictArgs(1))
function leak() {
'use strict'
try {
userName = 'tom' // 忘了写 let
} catch (e) {
console.log(e.name)
}
}
leak()查看输出与解释
true undefined
2 1
ReferenceError第一行:非严格函数普通调用时,this 会被兜底成全局对象;严格模式下没人调用它就是 undefined。第二行:非严格模式里改形参 a,arguments[0] 跟着变,严格模式下两者各是各的。第三行:忘写 let 直接赋值,非严格模式会悄悄造一个全局变量,严格模式直接报错。
为什么规范要强制,而不是让开发者选
严格模式修的问题,大部分是「写错了不报错」,这类问题靠自觉开启是不够的。
- 新语法是改默认值的唯一机会
- JavaScript 有一条铁律:不能破坏已经上线的网页。所以 ES5 引入严格模式时只能做成可选项,靠一行字符串开启。模块不一样,所有模块代码都是在有了模块语法之后才写的,把默认值改掉不会影响任何老网页。
强制严格模式还让模块更容易做静态分析。
with会让变量指向哪里在运行时才能确定,非严格的eval能往当前作用域里塞新变量,这两样一禁,一个标识符引用的是哪个变量,看代码就能确定,打包器做 tree shaking、引擎做优化都受益。
容易答错的地方
- 以为只有模块顶层是严格模式
- 整个模块文件里的所有代码都是严格模式,包括嵌套函数、回调、类方法。不存在「顶层严格、函数里不严格」的情况。
- 以为在模块里写
"use strict"有别的作用 - 写了也没有影响,只是多余。反过来,没有任何指令能把模块切回非严格模式。
- 老代码迁移成模块时只关注
import和export - 把老脚本改成模块后,最常见的报错不是导入导出,而是严格模式带来的:某个函数里忘写声明的变量报
ReferenceError,某个构造函数被漏了new调用后this是undefined导致TypeError。迁移时要把这些当成隐藏 bug 一起修掉。
面试官还会怎么问?
CommonJS 模块默认是严格模式吗?
不是。CommonJS 本质上是 Node.js 把文件包进一个函数里执行,没有改变模式,需要手动写 "use strict"。这也是 CommonJS 和 ESM 迁移时容易冒出新报错的原因之一。
还有哪些地方是自动严格模式?
class 的类体,包括构造函数、方法、字段初始化,都自动是严格模式,普通脚本里的 class 也一样。另外用 TypeScript 且开启了 alwaysStrict 时,编译产物会自动带上 "use strict"。
模块里的函数被普通脚本调用,按什么模式执行?
按定义它的代码的模式执行。模块里的函数永远是严格模式,谁调用都一样。这一点和 this 的指向不同,this 看调用方式,严格模式看函数写在哪里。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。