# 严格模式

ECMAScript 5 引入了严格模式，它通过特意改变 JavaScript 的部分语义和运行时行为来提高语言的弹性。更改的内容通常分为以下几类：

1. 把一些 JavaScript 的静默错误（mistakes）改成了直接抛出错误（errors）
2. 修复了一些让 JavaScript 引擎难以进行优化的错误（所以严格模式下的代码有时会运行得更快）
3. 禁止了一些可能会在未来的 ECMAScript 版本中定义的语法

严格模式有浏览器的兼容性问题，在使用之前最好做下特性检测（feature-testing），其中 IE10+, Node 0.6+, Chrome 13+ 都已经全方位地支持了严格模式，详见 [Strict Mode Compatibility Table](https://kangax.github.io/compat-table/es5/)。

<figure><img src="/files/qfxWfX7CCpjesxHbFGN2" alt=""><figcaption></figcaption></figure>

严格模式和非严格模式可以共存，所以可以选择让 JavaScript 代码逐步支持严格模式，比如从单个文件，甚至是单个函数开始。

> 非严格模式有时也称草率模式（sloppy mode），但这不是官方术语，知道即可

## 1. 开启严格模式

`"use strict";` 或 `'use strict';` 语句就能开启严格模式，写在代码的最开头。

严格模式可以用于整个脚本或是单个函数，但不能单独作用于块级结构 `{}`。&#x20;

### 1.1 整个脚本

为整个脚本启用严格模式。

```javascript
'use strict';
const s = 'Hi! I\'m a strict mode script!';
```

### 1.2 特定函数

为特定函数启用严格模式。

```javascript
function myStrictFunction() {
    // 函数级的严格模式
    'use strict';
    function nested() {
        return 'And so am I!';
    }
    return `Hi! I'm a strict mode function! ${nested()}`;
}

function myNotStrictFunction() {
    return 'I\'m not strict.';
}
```

### 1.3 类和模块

JavaScript 类和模块中的所有代码都会自动开启严格模式，无需开发者手动声明去开启。

## 2. 更改的内容

更改的内容通常分为以下几类：

1. 把一些 JavaScript 的静默错误（mistakes）改成了直接抛出错误（errors）
2. 修复了一些让 JavaScript 引擎难以进行优化的错误（所以严格模式下的代码有时会运行得更快）
   1. 简化了变量的特定用法
   2. 简化了 `eval` 和 `arguments`
   3. 让编写安全的 JavaScript 代码变得更加容易
3. 禁止了一些可能会在未来的 ECMAScript 版本中定义的语法

### 2.1 将 mistakes 改成 errors

JavaScript 被设计为对新手友好，因此它有时会把本应是 errors 的操作视为 non-error 的语义，这种处理方式是可以解决一些眼前的问题，但有可能在未来造成更严重的问题。

所以，严格模式将一些以前可接受的 mistakes 变成了 errors，以便提前发现问题并及时修复。

第一，不能再无意间创建全局变量了。

```javascript
'use strict';

mistypeVarible = 33; // ReferenceError: mistypeVarible is not defined
function f() {
    a = 1; // ReferenceError: a is not defined
}
f();
console.log(this.a);

// non-strict mode 会创建一个全局变量
```

第二，之前是默默失败的赋值操作，现在会抛出异常。包括但不限于：给不可写的全局变量或者属性赋值、给仅 getter 的属性赋值、给不可扩展对象的新属性赋值等等。

```javascript
'use strict';

// 给全局变量赋值
NaN = 2;          // TypeError: Cannot assign to read only property 'NaN' of object '#<Window>'
undefined = true; // TypeError: Cannot assign to read only property 'undefined' of object '#<Window>'

// 给不可写的属性赋值
const o = {};
Object.defineProperty(o, "x", { value: 1, writable: false });
o.x = 2; // TypeError: Cannot assign to read only property 'x' of object '#<Object>'
console.log(o.x);

// 给仅 getter 的属性赋值
const o1 = {
    get x() {
        return 1;
    }
};
o1.x = 2; // TypeError: Cannot set property x of #<Object> which has only a getter
console.log(o1.x);

// 给不可扩展对象的新属性赋值
const fixed = {};
Object.preventExtensions(fixed);
fixed.newProp = 'hi'; // TypeError: Cannot add property newProp, object is not extensible
console.log(fixed);

// non-strict mode 会依次输出：
//   1
//   1
//   {}
```

第三，删除不可删除的属性。

```javascript
'use strict';

// TypeError: Cannot delete property 'prototype' of function Object() { [native code] }
delete Object.prototype;
```

第四，函数的参数名必须唯一。

```javascript
// SyntaxError: Duplicate parameter name not allowed in this context
function sum(a, b, c, a) {
    'use strict';
    return a + b + c;
}
```

第五，禁止以 0 为前缀的八进制文字或八进制转义序列。

```javascript
'use strict';
var n = 010;  // SyntaxError: Octal literals are not allowed in strict mode.
console.log(n); 

// non-strict mode 会输出 8，因为被当成了八进制
```

第六，禁止在原始值上设置属性。

```javascript
'use strict';

false.true = '';         // TypeError: Cannot create property 'true' on boolean 'false'
(14).sailing = 'home';   // TypeError: Cannot create property 'sailing' on number '14'
'with'.you = 'far away'; // TypeError: Cannot create property 'you' on string 'with'

// non-strict mode 虽没报错，但其实是执行失败了——没啥效果
```

### 2.2 更多保留字

2.1 部分的修改（将一些以前可接受的 mistakes 变成了 errors），也为语言未来的语义变化留下了空间，从而更好地向后兼容。

未来的保留字是不能作为变量名或函数名的，比如：`implements`, `interface`, `let`, `package`, `private`, `protected`, `public`, `static`, `yield` 。

### 2.3 变量相关

很多编译器优化都依赖从变量名到变量定义（在哪存怎么存）的映射，而 JavaScript 代码有时需要到运行时才能明确这种基本的映射关系，进而在一定程度上阻碍了编译器的优化。严格模式的引入就消除了导致这类结果的大部分情况，以便让编译器更彻底地优化代码。

1. 禁止使用 `with`
2. `eval` 不会给周围作用域引入新的变量
3. 禁止删除正常的变量

```javascript
'use strict';

let a;
delete a; // SyntaxError: Delete of an unqualified identifier in strict mode.
```

```javascript
'use strict';

var a = 1;
var evalA = eval('var a = 2; a;');
console.log(a, evalA);
// non-strict mode: 2 2
//     strict mode: 1 2
```

```javascript
var a = 1;
var evalA = eval('"use strict"; var a = 2; a;');
console.log(a, evalA); // 1 2
```

### 2.4 `eval` 和 `arguments`

严格模式将 `eval` 和 `arguments` 视为关键字。

1. `eval` 和 `arguments` 不能被绑定（即当标识符），也不能被赋值
2. 不会给 `arguments` 对象的属性设置别名，即函数的形参和 `arguments` 对象是完全独立的
3. 不再支持 `arguments.callee`（因为不利于内联函数的优化）
   * `arguments.callee` 是一个不可删除的属性，访问和设置时都会报错

```javascript
'use strict';

eval = '';      // SyntaxError: Unexpected eval or arguments in strict mode
arguments = []; // SyntaxError: Unexpected eval or arguments in strict mode
```

```javascript
'use strict';

function add(a, b) {
    arguments[0] = 11;
    b = 22;
    console.log(a, b);
    return a + b;
}
add(1, 2);
// non-strict mode: 11 22
//     strict mode:  1 22
```

```javascript
'use strict';

// TypeError: 
//    'caller', 'callee', and 'arguments' properties may not be accessed 
//     on strict mode functions or the arguments objects for calls to them
function fn() {
    return arguments.callee;
}
fn();
```

### 2.5 安全的代码

严格模式让编写安全的 JavaScript 代码变得更容易。

#### 2.5.1 `this` 值

在非严格模式中，`this` 值始终是个对象：`undefined` 和 `null` 会被自动设置成全局对象，其它原始值会被自动转成相应的对象，当通过 `call()`, `apply()` 或 `bind()` 来指定特定 `this` 的时候。然而，自动装箱的操作不仅会降低性能，更有安全隐患——把全局对象暴露出去了，而全局对象可能提供本该加以限制的安全性行为。

在严格模式中，`this` 值不再被强制转换成对象了。也就是说，如果 `this` 值没被指定，那它的值就是 `undefined`。

eg1. 全局作用域

```javascript
'use strict';
console.log(this === globalThis); // true
```

eg2. 全局作用域里的函数

```javascript
'use strict';

function fn() {
    console.log(this); // undefined
    this.a = 1;          // TypeError: Cannot set properties of undefined (setting 'a')
    console.log(this.a); // TypeError: Cannot read properties of undefined (reading 'a')
}
fn(); // 等价于 fn.call();
```

eg3. 通过 `call()`, `apply()` 或 `bind()` 给函数指定特定的 `this`

```javascript
'use strict';

function fn() {
    this.a = 1;
    console.log(this);
    console.log(this.a);
}

const o = { a: 10, b: 11 };
fn.call(o);
// {a: 1, b: 11}
// 1

fn.call({});
// {a: 1}
// 1

fn.call(null);
// TypeError: Cannot set properties of null (setting 'a')
// null
// TypeError: Cannot read properties of null (reading 'a')

fn.call();
// TypeError: Cannot set properties of undefined (setting 'a')
// undefined
// TypeError: Cannot read properties of undefined (reading 'a')
```

#### 2.5.2 函数栈不可追踪

当一个函数 `fun` 被调用时，`fun.caller` 表示最近调用 `fun` 的函数，`fun.arguments` 表示调用 `fun` 的参数。考虑到有些 ECMAScript 扩展会通过 `fun.caller` 和 `fun.arguments` 得到 JavaScript 的调用堆栈，所以严格模式中 `fun.caller` 和 `fun.arguments` 这两个属性都是不可删除的，在设置和检索时会报错。如下：

> non-deletable properties, set or retrieved

```javascript
function fn() {
    'use strict';
    
    // TypeError: 
    //   'caller', 'callee', and 'arguments' properties may not be accessed
    //    on strict mode functions or the arguments objects for calls to them
    fn.caller;
    fn.arguments;
}

function privilegedInvoker() {
    return fn();
}
privilegedInvoker();
```

## 3. 清单

### 3.1 语法相关

会报 SyntaxError

1. `function` 的参数名有重复
2. `delete` 变量
3. 以 `0` 为前缀的八进制文字或八进制转义序列
4. `with` 语句
5. 使用 `eval` 和 `arguments` 当标识符（变量名/函数名/类名），或者给它们赋值
6. 未来的保留字：`let`, `yield`, `public`, `protected`, `private`, `static`, `interface`, `package`, `implements`

### 3.2 运行时相关

1. 报错 ReferenceError
   1. 没声明直接赋值的变量，不再自动创建全局变量了
2. 报错 TypeError
   1. 默默失败的赋值操作 `=`，比如给不可写的全局变量或者属性赋值、给仅 getter 的属性赋值、给不可扩展对象的新属性赋值等
   2. `delete` 不可删除的属性
   3. 访问函数的以下属性：`arguments.callee`, `fnName.caller` 和 `fnName.arguments`
   4. 在原始值上设置属性 `=`
3. 语义的更改
   1. 对于函数的 `this` 值，不再进行自动装箱操作
   2. 函数的形参和 `arguments` 对象是完全独立的
   3. `eval` 不会给周围作用域引入新的变量

## 4. 主要参考

1. <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode>
2. <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode/Transitioning_to_strict_mode>


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://anjia1.gitbook.io/web/js/syntax/strict-mode.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
