💡严格模式

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

  1. 把一些 JavaScript 的静默错误(mistakes)改成了直接抛出错误(errors)

  2. 修复了一些让 JavaScript 引擎难以进行优化的错误(所以严格模式下的代码有时会运行得更快)

  3. 禁止了一些可能会在未来的 ECMAScript 版本中定义的语法

严格模式有浏览器的兼容性问题,在使用之前最好做下特性检测(feature-testing),其中 IE10+, Node 0.6+, Chrome 13+ 都已经全方位地支持了严格模式,详见 Strict Mode Compatibility Table

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

非严格模式有时也称草率模式(sloppy mode),但这不是官方术语,知道即可

1. 开启严格模式

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

严格模式可以用于整个脚本或是单个函数,但不能单独作用于块级结构 {}

1.1 整个脚本

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

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

1.2 特定函数

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

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. 简化了 evalarguments

    3. 让编写安全的 JavaScript 代码变得更加容易

  3. 禁止了一些可能会在未来的 ECMAScript 版本中定义的语法

2.1 将 mistakes 改成 errors

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

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

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

'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 的属性赋值、给不可扩展对象的新属性赋值等等。

'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
//   {}

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

'use strict';

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

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

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

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

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

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

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

'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. 禁止删除正常的变量

'use strict';

let a;
delete a; // SyntaxError: Delete of an unqualified identifier in strict mode.
'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
var a = 1;
var evalA = eval('"use strict"; var a = 2; a;');
console.log(a, evalA); // 1 2

2.4 evalarguments

严格模式将 evalarguments 视为关键字。

  1. evalarguments 不能被绑定(即当标识符),也不能被赋值

  2. 不会给 arguments 对象的属性设置别名,即函数的形参和 arguments 对象是完全独立的

  3. 不再支持 arguments.callee(因为不利于内联函数的优化)

    • arguments.callee 是一个不可删除的属性,访问和设置时都会报错

'use strict';

eval = '';      // SyntaxError: Unexpected eval or arguments in strict mode
arguments = []; // SyntaxError: Unexpected eval or arguments in strict mode
'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
'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 值始终是个对象:undefinednull 会被自动设置成全局对象,其它原始值会被自动转成相应的对象,当通过 call(), apply()bind() 来指定特定 this 的时候。然而,自动装箱的操作不仅会降低性能,更有安全隐患——把全局对象暴露出去了,而全局对象可能提供本该加以限制的安全性行为。

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

eg1. 全局作用域

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

eg2. 全局作用域里的函数

'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

'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.callerfun.arguments 得到 JavaScript 的调用堆栈,所以严格模式中 fun.callerfun.arguments 这两个属性都是不可删除的,在设置和检索时会报错。如下:

non-deletable properties, set or retrieved

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. 使用 evalarguments 当标识符(变量名/函数名/类名),或者给它们赋值

  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.callerfnName.arguments

    4. 在原始值上设置属性 =

  3. 语义的更改

    1. 对于函数的 this 值,不再进行自动装箱操作

    2. 函数的形参和 arguments 对象是完全独立的

    3. eval 不会给周围作用域引入新的变量

4. 主要参考

Last updated