Skip to content

严格模式:编写更安全的 JavaScript

在航空领域,飞行员必须遵守严格的规程和检查清单,即使看似繁琐,但这些规则能防止灾难性错误。JavaScript 的严格模式(strict mode)就是类似的概念——它为语言添加了更严格的规则和检查,帮助你及早发现潜在问题,写出更安全、更可靠的代码。

什么是严格模式

严格模式是 ES5(2009 年)引入的一种 JavaScript 执行模式。它改变了一些静默错误的行为,让它们抛出明显的错误;禁止了某些容易出问题的语法;并优化了某些操作的性能。

在严格模式下,JavaScript 会:

  • 将一些静默失败的操作改为抛出错误
  • 禁用一些容易混淆或不安全的特性
  • 为未来的 ECMAScript 版本保留某些关键字
  • 提供更好的错误检查和调试信息

如何启用严格模式

启用严格模式非常简单,只需在代码顶部添加一个字符串字面量:

javascript
"use strict";

这个指令可以在两个层级使用:

1. 全局严格模式

在脚本文件的最开始使用,整个文件都会以严格模式运行:

javascript
"use strict";

// 整个文件都是严格模式
let x = 10;
console.log(x);

2. 函数级严格模式

在函数体的开头使用,只有该函数以严格模式运行:

javascript
function strictFunction() {
  "use strict";
  // 这个函数是严格模式
  let y = 20;
  console.log(y);
}

function normalFunction() {
  // 这个函数不是严格模式
  z = 30; // 在非严格模式下,这会创建全局变量
}

注意"use strict" 必须出现在脚本或函数的最开始,前面只能有注释和空白符。如果放在其他位置,会被忽略:

javascript
// ❌ 不会生效
let x = 10;
("use strict"); // 太晚了,会被当作普通字符串

// ✅ 正确
("use strict");
let y = 20;

ES6 模块自动使用严格模式

如果你使用 ES6 模块(import/export),整个模块会自动处于严格模式,不需要显式声明:

javascript
// module.js
// 自动处于严格模式,不需要 "use strict"
export function hello() {
  console.log("Hello");
}

ES6 类也自动运行在严格模式下:

javascript
class MyClass {
  constructor() {
    // 自动处于严格模式
    this.value = 10;
  }
}

严格模式的主要限制

1. 禁止隐式全局变量

在非严格模式下,给未声明的变量赋值会自动创建全局变量。这是常见的 bug 来源:

javascript
// 非严格模式
function createUser() {
  userName = "Alice"; // 创建了全局变量 userName
}

createUser();
console.log(userName); // "Alice"(全局变量被意外创建)

在严格模式下,这会抛出错误:

javascript
"use strict";

function createUser() {
  userName = "Alice"; // ReferenceError: userName is not defined
}

createUser();

这迫使你明确声明变量:

javascript
"use strict";

function createUser() {
  let userName = "Alice"; // ✅ 正确
  return userName;
}

2. 禁止删除变量、函数和参数

在严格模式下,不能使用 delete 删除变量、函数或函数参数:

javascript
"use strict";

let x = 10;
delete x; // SyntaxError: Delete of an unqualified identifier in strict mode

function test() {}
delete test; // SyntaxError

function doSomething(param) {
  delete param; // SyntaxError
}

可以删除对象的属性,但不能删除不可配置的属性:

javascript
"use strict";

let obj = { name: "Alice" };
delete obj.name; // ✅ 可以删除对象属性

delete Object.prototype; // TypeError: Cannot delete property 'prototype' of function Object()

3. 禁止重复的参数名

javascript
// 非严格模式:后面的参数覆盖前面的
function sum(a, a, c) {
  return a + a + c; // 只用第二个 a
}
console.log(sum(1, 2, 3)); // 2 + 2 + 3 = 7

// 严格模式:语法错误
("use strict");
function sum(a, a, c) {
  // SyntaxError: Duplicate parameter name not allowed
  return a + a + c;
}

4. 禁止八进制字面量

javascript
// 非严格模式:八进制表示
let num1 = 010; // 8(十进制)
let num2 = 0888; // 888(因为包含 8,所以当作十进制)

// 严格模式:语法错误
("use strict");
let num = 010; // SyntaxError: Octal literals are not allowed in strict mode

// ✅ 使用现代的八进制语法
let num = 0o10; // 8(ES6 的八进制语法)

5. 禁止对只读属性赋值

javascript
"use strict";

// 给只读属性赋值
let obj = {};
Object.defineProperty(obj, "x", { value: 42, writable: false });
obj.x = 100; // TypeError: Cannot assign to read only property 'x'

// 给不可扩展对象添加属性
let nonExtensible = { name: "Alice" };
Object.preventExtensions(nonExtensible);
nonExtensible.age = 30; // TypeError: Cannot add property age, object is not extensible

// 删除不可配置的属性
delete Object.prototype; // TypeError: Cannot delete property 'prototype'

在非严格模式下,这些操作会静默失败,让 bug 难以发现。

6. eval 的作用域隔离

在严格模式下,eval 中声明的变量不会污染外部作用域:

javascript
// 非严格模式
eval("var x = 10");
console.log(x); // 10(eval 创建了外部变量)

// 严格模式
("use strict");
eval("var y = 20");
console.log(y); // ReferenceError: y is not defined

这使得 eval 更安全(虽然仍然应该避免使用 eval)。

7. this 的行为变化

在非严格模式下,函数中的 this 如果是 undefinednull,会自动指向全局对象(浏览器中是 window):

javascript
// 非严格模式
function showThis() {
  console.log(this); // window(浏览器环境)
}

showThis();

在严格模式下,this 保持原值:

javascript
"use strict";

function showThis() {
  console.log(this); // undefined
}

showThis();

// 对象方法中的 this 仍然正常工作
let obj = {
  method: function () {
    console.log(this); // obj
  },
};
obj.method();

这能防止意外修改全局对象:

javascript
// 非严格模式:危险!
function dangerous() {
  this.value = 100; // 创建了全局变量 window.value
}
dangerous();

// 严格模式:安全
("use strict");
function safe() {
  this.value = 100; // TypeError: Cannot set property 'value' of undefined
}
safe();

8. 保留字

严格模式为未来的 ECMAScript 版本保留了一些关键字,不能用作变量名:

javascript
"use strict";

let implements = 10;  // SyntaxError
let interface = 20;   // SyntaxError
let let = 30;         // SyntaxError
let private = 40;     // SyntaxError
let protected = 50;   // SyntaxError
let public = 60;      // SyntaxError
let static = 70;      // SyntaxError
let yield = 80;       // SyntaxError

9. 禁止 with 语句

javascript
// 非严格模式:with 语句(不推荐)
let obj = { a: 1, b: 2 };
with (obj) {
  console.log(a); // 1
  console.log(b); // 2
}

// 严格模式:语法错误
("use strict");
with (obj) {
  // SyntaxError: Strict mode code may not include a with statement
  console.log(a);
}

with 语句会让代码难以理解和优化,应该避免使用。

实际应用场景

1. 及早发现错误

javascript
"use strict";

function calculateTotal(price, quantity) {
  // ❌ 拼写错误会立即被发现
  totla = price * quantity; // ReferenceError: totla is not defined
  return total; // 这行不会执行
}

// 如果没有严格模式,totla 会成为全局变量,bug 难以发现

2. 防止意外的全局变量

javascript
"use strict";

function processData(data) {
  // ❌ 忘记声明变量
  result = data.map((x) => x * 2); // ReferenceError
  return result;
}

// ✅ 正确
function processData(data) {
  let result = data.map((x) => x * 2);
  return result;
}

3. 更安全的 this 绑定

javascript
"use strict";

class User {
  constructor(name) {
    this.name = name;
  }

  greet() {
    console.log(`Hello, ${this.name}`);
  }
}

let user = new User("Alice");
let greetFn = user.greet;

// 非严格模式:this 是 window,this.name 是 undefined
// 严格模式:this 是 undefined,会报错
greetFn(); // TypeError: Cannot read property 'name' of undefined

// ✅ 正确的做法:绑定 this
let boundGreet = user.greet.bind(user);
boundGreet(); // Hello, Alice

4. 配合工具和框架

现代的 JavaScript 工具和框架通常假设使用严格模式:

javascript
// React 组件(自动使用严格模式)
import React from "react";

function MyComponent() {
  // 这里是严格模式
  return <div>Hello</div>;
}

// ES6 模块(自动使用严格模式)
export function myFunction() {
  // 这里是严格模式
}

全局严格模式 vs 函数严格模式

全局严格模式的风险

在整个文件使用严格模式时要小心,特别是当你的代码会与其他脚本合并时:

javascript
// file1.js
"use strict";
// 整个文件是严格模式

// file2.js(第三方库,非严格模式)
// 可能依赖非严格模式的特性

// 如果两个文件被合并,file2.js 也会受严格模式影响,可能导致错误

推荐的做法

  1. 使用 ES6 模块:自动严格模式,作用域隔离
  2. 使用 IIFE 包装:为单个文件创建独立作用域
  3. 或使用函数级严格模式:只在需要的函数中启用
javascript
// 方案 1:ES6 模块(推荐)
// module.js
export function myFunction() {
  // 自动严格模式
}

// 方案 2:IIFE
(function () {
  "use strict";
  // 这个IIFE内部是严格模式
})();

// 方案 3:函数级
function strictFunction() {
  "use strict";
  // 只有这个函数是严格模式
}

性能影响

严格模式在某些情况下能提升性能,因为它:

  • 禁用了一些难以优化的特性(如 witharguments.callee
  • 让 JavaScript 引擎能做出更多假设和优化
  • 简化了变量查找(不需要检查全局对象)

但这种性能提升通常很小,不应该是使用严格模式的主要原因。主要好处是更好的错误检查和代码质量。

常见陷阱

1. 忘记添加"use strict"

javascript
// ❌ 写在了注释后面
// This is my script
"use strict"; // ✅ 但这里仍然有效,因为注释不算

// ❌ 写在了代码后面
let x = 10;
("use strict"); // ❌ 不会生效

2. 混合严格和非严格代码

javascript
function mixed() {
  // 非严格模式
  x = 10; // 创建全局变量

  function inner() {
    "use strict";
    y = 20; // ReferenceError
  }

  inner();
}

最好整个项目统一使用严格模式(通过 ES6 模块)。

3. arguments 对象的变化

javascript
function test(a) {
  "use strict";

  a = 42;
  console.log(arguments[0]); // 在严格模式下不随 a 变化
}

test(10); // 输出: 10(非严格模式下会是 42)

总结

严格模式是 JavaScript 的"安全带",虽然它增加了一些限制,但这些限制能帮助你写出更安全、更可靠的代码。在现代 JavaScript 开发中,由于 ES6 模块和类自动使用严格模式,你可能无需显式声明就已经在使用它了。

关键要点:

  • 使用 "use strict" 启用严格模式
  • 严格模式禁止隐式全局变量
  • 禁止重复参数、八进制字面量等容易出错的语法
  • this 不再自动指向全局对象
  • ES6 模块和类自动使用严格模式
  • 推荐在所有新项目中使用严格模式
  • 使用现代构建工具和 ES6 模块可以自动获得严格模式的好处

严格模式不是限制你的自由,而是帮助你避免常见的陷阱。就像安全带不会限制你开车,但会在危险时刻保护你一样。养成使用严格模式的习惯,是成为专业 JavaScript 开发者的重要一步。