JavaScript 严格模式完全指南
JavaScript 严格模式完全指南
Introduction
JavaScript 严格模式(Strict Mode)是 ECMAScript 5 引入的一种特殊执行模式,通过在脚本或函数顶部添加 "use strict" 指令来启用。在严格模式下,JavaScript 引擎会对代码执行更严格的检查,消除一些语言中的静默错误(silent errors),让代码行为更加明确、可预测,同时为未来的 ECMAScript 特性做好准备。
严格模式并不是 JavaScript 的一个子集——它是对语法的扩展,在语义上做了重要改变。很多在非严格模式( sloppy mode)下被容忍的"问题代码"在严格模式下会直接抛出错误。掌握严格模式不仅能帮助我们写出更高质量的代码,还能更好地理解 JavaScript 语言的设计缺陷与演进方向。本篇文章将从基础语法出发,逐一讲解严格模式的行为变更、代码示例、运行效果以及常见问题。
基础语法
1. 如何启用严格模式
严格模式通过字符串字面量 "use strict"(或 'use strict')来启用。它必须出现在脚本或函数体的最顶部,任何语句之前。
// 整个脚本启用严格模式(脚本顶部)
"use strict";
const message = "Hello strict mode";
console.log(message);
// 函数级别严格模式
function strictFunction() {
"use strict";
// 函数内所有代码都处于严格模式
return "I am strict!";
}
function normalFunction() {
// 这里不是严格模式
return "I am normal!";
}
// ES6 模块默认处于严格模式,无需声明
// module.js(自动严格模式)
export const name = "module";
export default function() {
// 隐式严格模式
imei = 123; // 直接报错!
};
2. 严格模式主要行为变化
- 禁止隐式全局变量:未声明的变量赋值会抛出
ReferenceError,而非自动创建全局变量。 - 禁止删除不可删除的属性:对
var、function等不可删除的属性执行delete会抛出SyntaxError。 - 禁止函数参数名重复:命名参数在同一个函数中不能出现重复。
- 禁止八进制字面量:
0123这样的八进制数字语法在严格模式下是语法错误。 - 禁止
with语句:严格模式下不允许使用with。 - eval 和 arguments 的特殊限制:不能对
eval和arguments进行赋值或操作。 - 只读属性赋值报错:对只读属性赋值、对 getter-only 属性赋值都会抛出错误。
- this 的行为变化:在非严格模式下,
this在函数调用中可能指向全局对象;严格模式下this为undefined(除非被显式指定)。
代码示例
示例一:禁止隐式全局变量
"use strict";
// 未声明变量直接赋值
try {
undeclaredVar = "Hello"; // ReferenceError: undeclaredVar is not defined
console.log(undeclaredVar);
} catch (e) {
console.error("错误:", e.message);
}
// 正确做法:先声明
const declaredVar = "Hello, declared!";
console.log(declaredVar); // Hello, declared!
示例二:this 在严格模式下的行为
"use strict";
function showThis() {
console.log(this);
}
showThis(); // undefined(在非严格模式下会输出全局对象 Window)
// 构造函数
function Person(name) {
this.name = name;
console.log(this); // Person { name: "Alice" }
}
const alice = new Person("Alice");
// 显式指定 this
const obj = { value: 42 };
showThis.call(obj); // { value: 42 }
showThis.apply(obj); // { value: 42 }
showThis.bind(obj)(); // { value: 42 }
示例三:禁止八进制字面量
"use strict";
try {
eval("const num = 010; console.log(num);"); // 语法错误
} catch (e) {
console.error("八进制错误:", e.message);
}
// ES6 推荐写法
const numHex = 0o10; // 八进制(显式写法)
console.log(numHex); // 8
const numBin = 0b1010; // 二进制
console.log(numBin); // 10
示例四:禁止删除不可删除属性
"use strict";
const obj = { a: 1 };
Object.defineProperty(obj, "readOnly", {
value: 100,
writable: false,
configurable: false
});
try {
delete obj.readOnly; // TypeError: Cannot delete property
} catch (e) {
console.error("删除只读属性错误:", e.message);
}
// 可以删除的配置属性
delete obj.a;
console.log(obj); // {}(成功删除)
示例五:禁止函数参数名重复
"use strict";
try {
eval("function sum(a, a, c) { return a + a + c; }"); // 语法错误
} catch (e) {
console.error("参数重复错误:", e.message);
}
// 替代方案:使用剩余参数
function sum(...args) {
return args.reduce((acc, val) => acc + val, 0);
}
console.log(sum(1, 2, 3)); // 6
示例六:arguments 对象的限制
"use strict";
function trackArgs(a) {
console.log("初始:", arguments[0]);
a = 999;
console.log("修改后:", arguments[0]); // 在严格模式下与 a 不同步了
console.log("a =", a);
console.log("arguments[0] =", arguments[0]);
}
trackArgs(10);
// 输出:
// 初始: 10
// 修改后: 10
// a = 999
// arguments[0] = 10
示例七:eval 作用域隔离
"use strict";
const evalVar = "外部变量";
try {
eval("const evalVar = 123; console.log(evalVar);"); // 123
} catch (e) {
console.error("eval 错误:", e.message);
}
// eval 创建的变量不会泄漏到外部
console.log(typeof evalVar); // "string"(外部变量不受影响)
示例八:禁止 with 语句
"use strict";
try {
eval("with (Math) { console.log(PI, sqrt(2)); }"); // SyntaxError
} catch (e) {
console.error("with 错误:", e.message);
}
// 替代写法
console.log(Math.PI, Math.sqrt(2)); // 3.141592653589793 1.4142135623730951
运行效果
在浏览器控制台或 Node.js 环境运行上述所有示例代码,主要输出如下:
// 示例一
错误: undeclaredVar is not defined
Hello, declared!
// 示例二
undefined
Person { name: "Alice" }
{ value: 42 }
// 示例三
八进制错误: Unexpected eval or arguments in strict mode
8
10
// 示例四
删除只读属性错误: Cannot delete property
{}
// 示例五
参数重复错误: Unexpected eval or arguments in strict mode
6
// 示例六
初始: 10
修改后: 10
a = 999
arguments[0] = 10
// 示例七
123
string
// 示例八
with 错误: Strict mode code may not include with statements
3.141592653589793 1.4142135623730951
常见问题
Q1:严格模式会影响 JavaScript 性能吗?
从理论上讲,严格模式省略了一些动态检查(如隐式全局变量的创建),引擎可以进行更多优化,实际上严格模式往往性能更好。现代 JavaScript 引擎(如 V8)已经对严格模式做了充分优化,不必担心性能损失。反而是非严格模式需要维持向后兼容,引擎内部仍有大量历史包袱。
Q2:严格模式和非严格模式可以混合使用吗?
可以,但需要注意作用域边界。一个脚本文件可以同时包含两种模式的代码:全局脚本顶部启用严格模式,则整个文件都是严格模式;仅在函数顶部启用,则只有该函数内部是严格模式。不过在实践中,建议整个项目统一使用严格模式,避免行为不一致。
// 文件顶部
"use strict";
// 这里都是严格模式
function mixed() {
// 这里也是严格模式
"use strict";
// 仅这里更严格(函数级别的严格模式)
}
Q3:严格模式会让现有代码报错吗?
有可能。如果现有代码存在未声明变量赋值、参数名重复、使用八进制字面量等"问题写法",引入严格模式后会立即报错。建议在引入严格模式前先使用 ESLint 等工具扫描问题代码,逐步修复。
// 非严格模式下合法,严格模式下报错的一些代码
"use strict";
// 报错 1:未声明变量
// imei = 123; // ReferenceError
// 报错 2:八进制
// const port = 0755; // SyntaxError
// 报错 3:参数名重复
// function foo(a, b, a) {} // SyntaxError
// 报错 4:delete 变量
// const x = 1;
// delete x; // SyntaxError
Q4:严格模式和 ES6 模块有什么关系?
ES6 模块(<script type="module"> 或使用 import/export 的文件)默认处于严格模式,无需显式声明 "use strict"。这意味着在 ES6 模块中书写的所有代码都遵循严格模式的规则。这也是为什么在使用 import 时要注意变量必须先声明再使用。
// ES6 模块中,以下代码会报错(自动严格模式)
// imei = 123; // ReferenceError
export const version = "1.0.0";
Q5:严格模式对 try...catch 有什么影响?
严格模式下,catch 子句中的异常参数不再共享同一个作用域,且该参数不能在 catch 块外访问。这实际上让代码更加安全,避免了异常变量泄漏到外部作用域。
"use strict";
try {
JSON.parse("{invalid}");
} catch (e) {
console.error("解析错误:", e.message);
}
// e 在此处不可访问
try {
console.log(e.message);
} catch (e) {
// 这里是新的作用域
console.log("e 的作用域已结束");
}
Q6:严格模式下 arguments 和形参是分离的吗?
在严格模式下,函数的 arguments 对象与形参之间不再保持同步。修改形参的值不会影响 arguments 数组,反之亦然。这与非严格模式不同(非严格模式下两者保持联动)。这使得函数行为更加可预测,减少了意外的副作用。
延伸阅读
- MDN Web Docs - 严格模式:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Strict_mode
- 《JavaScript 高级程序设计》(第4版) 第 3 章"基本概念",关于严格模式有详细说明。
- 《你不知道的 JavaScript(上卷)》 第 1 章"作用域",深入分析了 JS 的语义与严格模式设计动机。
- ESLint 严格模式规则:https://eslint.org/docs/rules/#strict
- TC39 严格模式规范:https://tc39.es/ecma262/#sec-strict-mode-of-ecmascript
严格模式是 JavaScript 从"草莽时代"走向"成熟语言"的重要一步。虽然它不是银弹,但通过强制消除语言中一些历史遗留的糟粕,让代码质量更高、更安全。建议在所有新项目中默认启用严格模式,并通过 ESLint 的 strict 规则确保代码合规。对于遗留代码,可以逐步迁移,在修复问题时顺势引入严格模式。