前言
还记得第一次在代码里看到 'use strict'
的时候吗?
那时候跟着教程敲代码,看到文件开头总有这么一行,也不知道干什么用的,复制粘贴就完事了。直到有一天,删掉这行之后,一些"莫名其妙能跑"的代码突然就能运行了,才意识到:这玩意儿不简单。
更让人好奇的是:为什么 JavaScript 需要一个"严格模式"?难道还有"随便模式"吗?这背后到底有什么故事?
这篇文章会带你深入了解严格模式的前世今生,以及它到底解决了什么问题。
目录
- 一个真实的困惑:为什么有些错误被"吃掉"了?
- [JavaScript 的历史遗留问题](#JavaScript 的历史遗留问题 "#javascript-%E7%9A%84%E5%8E%86%E5%8F%B2%E9%81%97%E7%95%99%E9%97%AE%E9%A2%98")
- [严格模式的诞生:ES5 的救赎之举](#严格模式的诞生:ES5 的救赎之举 "#%E4%B8%A5%E6%A0%BC%E6%A8%A1%E5%BC%8F%E7%9A%84%E8%AF%9E%E7%94%9Fes5-%E7%9A%84%E6%95%91%E8%B5%8E%E4%B9%8B%E4%B8%BE")
- 严格模式到底做了什么?
- 实战:看看严格模式如何拯救你的代码
- 现代开发中的严格模式
- 最佳实践
一个真实的困惑:为什么有些错误被"吃掉"了?
先看一段代码,你能发现问题吗?
javascript
function calculateTotal(price, quantity) {
totol = price * quantity; // 注意:这里把 total 拼错了
return totol;
}
console.log(calculateTotal(100, 5)); // 500
console.log(totol); // 500 - WTF?
问题来了:
- 代码能正常运行,没有报错
- 但
totol
这个变量我们从来没声明过 - 更诡异的是,它变成了全局变量
这就是 JavaScript 早期设计的"宽容":你不声明变量?没关系,我帮你创建一个全局的。
听起来很贴心?实际上是灾难的开始。
JavaScript 的历史遗留问题
诞生于仓促,成长于妥协
JavaScript 诞生于 1995 年,当时 Brendan Eich 用10天设计出了这门语言。是的,你没看错,就是 10 天。
为了让它容易上手,JavaScript 做了很多"宽容"的设计:
1. 自动创建全局变量
javascript
function test() {
myVar = 'hello'; // 没有 var/let/const,自动变成全局变量
}
test();
console.log(myVar); // 'hello' - 全局污染
2. 静默失败
javascript
var obj = {};
Object.defineProperty(obj, 'name', {
value: 'John',
writable: false
});
obj.name = 'Mike'; // 赋值失败,但不报错
console.log(obj.name); // 'John' - 赋值被静默忽略
3. 意外的 this 绑定
javascript
function showName() {
console.log(this.name);
}
var name = 'Global';
showName(); // 'Global' - this 指向了全局对象
4. 八进制字面量的陷阱
javascript
var num = 010; // 猜猜这是几?
console.log(num); // 8 - 以 0 开头被当成八进制
问题的本质
这些"宽容"的设计初衷是好的,但在实际项目中带来了:
- 调试困难:错误被静默忽略,找 bug 像大海捞针
- 全局污染:变量满天飞,不知道哪里会被修改
- 行为难以预测:同样的代码在不同上下文表现不一样
随着 JavaScript 项目越来越大,这些问题变得不可忍受。
严格模式的诞生:ES5 的救赎之举
2009 年的转折点
ECMAScript 5(ES5)在 2009 年发布时,语言设计者面临一个两难选择:
- 修复这些问题 → 破坏向后兼容性,无数老代码会崩溃
- 保持兼容 → 继续背着历史包袱前行
最终,他们想出了一个优雅的解决方案 :严格模式。
严格模式就像给 JavaScript 加了一个"安全模式"开关,开启后会:
- 把静默错误变成显式抛出
- 禁止使用容易出错的语法
- 为未来的语言特性保留空间
设计哲学
严格模式的设计遵循三个原则:
1. 向后兼容
- 老代码不加
'use strict'
继续按原来的方式运行 - 新代码主动选择加入严格模式
2. 渐进增强
- 可以在全局启用,也可以只在函数内启用
- 给开发者充分的选择权
3. 更安全的 JavaScript
- 消除语言的不安全特性
- 为优化器提供更好的优化空间
严格模式到底做了什么?
1. 禁止意外的全局变量
非严格模式:
javascript
function loose() {
myVar = 'oops'; // 创建全局变量
}
loose();
console.log(myVar); // 'oops'
严格模式:
javascript
'use strict';
function strict() {
myVar = 'oops'; // ReferenceError: myVar is not defined
}
strict();
比喻:就像门禁系统,非严格模式是"没卡也能进",严格模式是"必须刷卡"。
2. 让静默失败变成抛出错误
给不可写属性赋值:
javascript
'use strict';
var obj = {};
Object.defineProperty(obj, 'x', { value: 42, writable: false });
obj.x = 9; // TypeError: Cannot assign to read only property
删除不可删除的属性:
javascript
'use strict';
delete Object.prototype; // TypeError: Cannot delete property
给只读的全局对象赋值:
javascript
'use strict';
undefined = 5; // TypeError: Cannot assign to read only property
NaN = 42; // TypeError: Cannot assign to read only property
3. 禁止重复的参数名
非严格模式(最后一个参数"赢"):
javascript
function sum(a, a, c) {
return a + a + c;
}
console.log(sum(1, 2, 3)); // 2 + 2 + 3 = 7
严格模式(直接报错):
javascript
'use strict';
function sum(a, a, c) { // SyntaxError: Duplicate parameter name
return a + a + c;
}
4. 禁止八进制语法
非严格模式(容易误解):
javascript
var num = 010; // 8,不是 10
var price = 099; // 99(因为 9 不是有效的八进制数字)
严格模式(明确报错):
javascript
'use strict';
var num = 010; // SyntaxError: Octal literals are not allowed
现代替代方案:
javascript
'use strict';
var num = 0o10; // 明确的八进制语法,值为 8
5. 禁止 with 语句
with
语句看起来方便,实际上是性能杀手 和可读性噩梦:
非严格模式:
javascript
var obj = { a: 1, b: 2 };
with (obj) {
console.log(a); // 1 - a 来自哪里?不确定
console.log(b); // 2 - 可能是 obj.b,也可能是外层的 b
}
严格模式:
javascript
'use strict';
with (obj) { // SyntaxError: Strict mode code may not include a with statement
// ...
}
为什么禁止?
- 变量来源不明确,难以优化
- 引擎无法在编译时确定变量作用域
6. this 不再自动指向全局对象
非严格模式(意外的全局绑定):
javascript
function show() {
console.log(this); // Window(浏览器环境)
}
show();
严格模式(undefined,更合理):
javascript
'use strict';
function show() {
console.log(this); // undefined
}
show();
实际应用:
javascript
'use strict';
function Person(name) {
this.name = name;
}
// 非严格模式:忘记 new,this 指向 window,污染全局
// 严格模式:忘记 new,this 是 undefined,立即报错
var person = Person('John'); // TypeError: Cannot set property 'name' of undefined
这个特性帮你及时发现构造函数调用错误。
7. 保留未来关键字
严格模式为 JavaScript 的未来发展保留了一些关键字:
javascript
'use strict';
var implements = 'test'; // SyntaxError
var interface = 'test'; // SyntaxError
var let = 'test'; // SyntaxError(现在已经是关键字了)
var package = 'test'; // SyntaxError
var private = 'test'; // SyntaxError
var protected = 'test'; // SyntaxError
var public = 'test'; // SyntaxError
var static = 'test'; // SyntaxError
var yield = 'test'; // SyntaxError(现在已经是关键字了)
实战:看看严格模式如何拯救你的代码
案例 1:避免变量污染
问题代码(非严格模式):
javascript
function updateUser(user) {
nmae = user.name; // 拼写错误:name 写成了 nmae
age = user.age;
// ... 100 行代码之后
console.log(name); // undefined - 因为 name 没被赋值
}
updateUser({ name: 'Alice', age: 25 });
console.log(nmae); // 'Alice' - 全局变量污染
修复后(严格模式):
javascript
'use strict';
function updateUser(user) {
nmae = user.name; // ReferenceError: nmae is not defined
// 立即发现拼写错误,不会污染全局
}
案例 2:捕获静默失败的赋值
问题代码(配置对象被意外修改):
javascript
const CONFIG = {};
Object.defineProperty(CONFIG, 'API_KEY', {
value: 'secret-key',
writable: false
});
// 某个开发者在代码的其他地方
CONFIG.API_KEY = 'new-key'; // 非严格模式:静默失败
console.log(CONFIG.API_KEY); // 'secret-key' - 赋值没生效,但没报错
// 导致 bug:开发者以为修改成功了,实际没有
严格模式(立即发现问题):
javascript
'use strict';
const CONFIG = {};
Object.defineProperty(CONFIG, 'API_KEY', {
value: 'secret-key',
writable: false
});
CONFIG.API_KEY = 'new-key'; // TypeError: Cannot assign to read only property
// 立即知道这个属性是只读的
案例 3:防止 this 绑定错误
问题代码(事件处理器):
javascript
class Counter {
constructor() {
this.count = 0;
}
increment() {
this.count++;
}
}
const counter = new Counter();
const btn = document.getElementById('btn');
// 常见错误:直接传递方法引用
btn.addEventListener('click', counter.increment);
// 点击按钮时:
// 非严格模式:this 指向 btn,没有 count 属性,静默失败
// 严格模式:this 是 undefined,立即报错
正确做法:
javascript
'use strict';
// 方法 1:箭头函数
btn.addEventListener('click', () => counter.increment());
// 方法 2:bind
btn.addEventListener('click', counter.increment.bind(counter));
现代开发中的严格模式
ES6 模块和类默认启用严格模式
好消息:如果你使用现代 JavaScript,很多情况下自动就是严格模式了:
ES6 模块:
javascript
// module.js
export function test() {
// 默认严格模式,不需要 'use strict'
x = 1; // ReferenceError
}
ES6 类:
javascript
class MyClass {
constructor() {
// 类的代码默认严格模式
x = 1; // ReferenceError
}
}
构建工具的处理
现代构建工具(Webpack、Vite、Rollup 等)通常会:
- 在打包时自动添加
'use strict'
- 或者使用 ES6 模块,天然严格模式
你还需要手动添加吗?
需要,在以下场景:
- 传统的脚本文件(非模块)
- Node.js 的 CommonJS 模块(不是 ES 模块)
- 内联的
<script>
标签
不需要,在以下场景:
- ES6 模块(
import
/export
) - ES6 类
- 现代框架的组件文件(React、Vue、Svelte 等)
最佳实践
1. 新项目:默认使用严格模式
在文件顶部添加:
javascript
'use strict';
// 你的代码
或者使用 ES6 模块(自动严格模式):
javascript
// 不需要 'use strict'
export function myFunction() {
// ...
}
2. 老项目:逐步迁移
不要一次性在全局启用,可能会导致大量代码报错。
推荐策略:
javascript
// 在新写的函数中启用
function newFeature() {
'use strict';
// 只影响这个函数作用域
}
// 老代码保持不变
function legacyCode() {
// 继续以非严格模式运行
}
3. 配合 ESLint 使用
在 .eslintrc.js
中配置:
javascript
module.exports = {
rules: {
'strict': ['error', 'global'], // 要求全局严格模式
}
};
ESLint 会检查你的代码,确保正确使用严格模式。
4. 注意兼容性
严格模式在 所有现代浏览器 中都支持(IE 10+)。
如果需要支持更老的浏览器,可以:
javascript
(function() {
'use strict';
// 你的代码在 IIFE 中,不会影响外部
})();
严格模式解决了什么问题?总结
问题类型 | 非严格模式 | 严格模式 | 影响 |
---|---|---|---|
未声明变量 | 创建全局变量 | 抛出 ReferenceError | 防止全局污染 |
只读属性赋值 | 静默失败 | 抛出 TypeError | 及时发现错误 |
删除不可删除属性 | 静默失败 | 抛出 TypeError | 保护关键属性 |
重复参数名 | 最后一个生效 | 抛出 SyntaxError | 避免混淆 |
八进制字面量 | 0 开头表示八进制 | 抛出 SyntaxError | 防止误解 |
with 语句 | 允许使用 | 抛出 SyntaxError | 提升性能 |
this 自动绑定 | 指向全局对象 | undefined | 防止意外修改全局 |
eval 作用域 | 污染外部作用域 | 独立作用域 | 更安全的 eval |
深入思考:严格模式的哲学
严格模式的诞生,体现了编程语言设计的一个重要原则:
Fail fast, fail loud(快速失败,明确失败)
与其让错误静默传播 ,在几百行代码之后才爆发成难以调试的 bug,不如在错误发生的第一时间大声报错。
这也是现代编程语言(TypeScript、Rust、Swift)的共同趋势:
- 编译时捕获错误 优于 运行时调试
- 明确的类型系统 优于 隐式转换
- 不可变性 优于 随意修改
严格模式,是 JavaScript 向这个方向迈出的第一步。
相关资源
- MDN - 严格模式 - 最权威的文档
- ECMAScript 5 规范 - 严格模式的官方定义
- You Don't Know JS: Scope & Closures - 深入理解作用域和严格模式
结语
严格模式不是 JavaScript 的"负担",而是它成熟的标志。
从 1995 年的"10 天速成",到 2009 年的严格模式,再到今天的 TypeScript、ES6+,JavaScript 一直在进化,变得更安全、更可靠、更适合构建大型应用。
而 'use strict'
这简单的一行代码,代表的是:
我们选择更严格的标准,因为我们要写更好的代码。
下次再看到 'use strict'
时,你会知道它背后的故事,以及它默默守护你代码的方式。