概述

严格模式是采用具有限制性JavaScript变体的一种方式,从而使代码显示地 脱离“马虎模式/稀松模式/懒散模式“(sloppy)模式。

严格模式是一个受限制的子集:它的产生是为了形成与正常代码不同的语义。
不支持严格模式与支持严格模式的浏览器在执行严格模式代码时会采用不同行为。所以在没有对运行环境展开特性测试来验证对于严格模式相关方面支持的情况下,就算采用了严格模式也不一定会取得预期效果。严格模式代码和非严格模式代码可以共存,因此项目脚本可以渐进式地采用严格模式。

严格模式对正常的JavaScript语义做了一些更改。

  • 严格模式会使引起静默失败(silently fail,即不报错也没有任何效果)的赋值操抛出异常;

  • 严格模式修复了一些导致 JavaScript引擎难以执行优化的缺陷:有时候,相同的代码,严格模式可以比非严格模式下运行得更快;

  • 严格模式禁用了在ECMAScript的未来版本中可能会定义的一些语法。

进入标志

1
'use strict';

老版本的浏览器会把它当作一行普通字符串,加以忽略。

调用

针对整个脚本文件

  • 将”use strict”放在脚本文件的第一行,则整个脚本都将以”严格模式”运行。

    1
    2
    'use strict';
    // doSomething;
  • 如果这行语句不在第一行,则无效,整个脚本以”正常模式”运行。如果不同模式的代码文件合并成一个文件,这一点需要特别注意。

    1
    2
    3
    4
    5
    6
    7
    8
    a = 1
    'use strict';
    i = 1
    console.log(i); // 输出为1,未进入严格模式
    ------------------------
    'use strict';
    i = 1;
    console.log(i); // Uncaught ReferenceError
  • 严格地说,只要前面不是产生实际运行结果的语句,’use strict’可以不在第一行,比如直接跟在一个空的分号后面。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    'a = 1;'
    'use strict';
    i = 1;
    console.log(i); // Uncaught ReferenceError
    -------------------------
    'a = 1;'
    'use strict';
    var i = 1;
    console.log(i); // 输出为1,严格模式

针对单个函数

将”use strict”放在函数体的第一行,则整个函数以”严格模式”运行。

1
2
3
4
5
6
7
8
9
10
function strict(){        // 严格模式
'use strict';
// doSomething
return obj;
}
------------------------
function notStrict() { // 正常模式
// doSomething
return obj;
}

脚本文件的变通写法

因为第一种调用方法不利于文件合并,所以更好的做法是,借用第二种方法,将整个脚本文件放在一个立即执行的匿名函数之中。

1
2
3
4
(function() {
'use strict';
// doSomething
})();

语法和行为改变

全局变量显式声明

在正常模式中,如果一个变量没有声明就赋值,默认是全局变量。严格模式禁止这种用法,全局变量必须显式声明。

1
2
3
4
5
'use strict';
a = 1; // error,v=a未声明
for(i = 0; i < 10; i++) { // error,i未声明
// doSomething
}

如果给一个未声明的变量、函数、函数参数、catch从句参数或全局对象的属性赋值,将会抛出一个引用错误异常。在非严格模式中,这种隐式声明的全局变量的方法是给全局对象新添加一个新属性。

静态绑定

Javascript语言的一个特点,就是允许”动态绑定”,即某些属性和方法到底属于哪一个对象,不是在编译时确定的,而是在运行时确定的。

严格模式对动态绑定做了一些限制。某些情况下,只允许静态绑定。也就是说,属性和方法到底归属哪个对象,在编译阶段就确定。这样做有利于编译效率的提高,也使得代码更容易阅读,更少出现意外。

禁止使用with语句

with所引起的问题是块内的任何名称可以映射(map)到with传进来的对象的属性,也可以映射到包围这个块的作用域内的变量(甚至是全局变量),这一切都是在运行时决定的,在代码运行之前是无法得知的。

严格模式下,使用 with 会引起语法错误,所以就不会存在 with 块内的变量在运行是才决定引用到哪里的情况了

1
2
3
4
5
6
'use strict';
var obj = {a: 1};
with (obj) {
a = 2;
}
console.log(obj.a); // Uncaught SyntaxError

一种取代with的简单方法是,将目标对象赋给一个短命名变量,然后访问这个变量上的相应属性。

创设eval作用域

正常模式下,Javascript语言有两种变量作用域:全局作用域和函数作用域。严格模式创设了第三种作用域:eval作用域。

正常模式下,eval语句的作用域,取决于它处于全局作用域,还是处于函数作用域。严格模式下,eval语句本身就是一个作用域,传入eval()的代码不能在调用程序所在的上下文中声明变量或定义函数,而在非严格模式中是可以这样做的;相反,变量和函数的定义是在eval()创建的新作用域中,这个作用域在eval()返回时就弃用了。

1
2
3
4
5
6
7
8
'use strict';
var i = 1;
eval("var i = 2;console.log(i);"); // => 2
console.log(i); // => 1
-----------------------------------
var i = 1;
eval("var i = 2;console.log(i);"); // => 2
console.log(i); // => 2

相应的,如果函数eval被在严格模式下的eval(…)以表达式的形式调用时,其代码会被当做严格模式下的代码执行。当然也可以在代码中显式开启严格模式,但这样做并不是必须的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function strict1(str) {
'use strict';
return eval(str); // str中的代码在严格模式下运行
}
function strict2(f, str) {
"use strict";
return f(str); // 没有直接调用eval(...): 当且仅当str中的代码开启了严格模式时才会在严格模式下运行
}
function nonstrict(str) {
return eval(str); // 当且仅当str中的代码开启了"use strict",str中的代码才会在严格模式下运行
}
strict1("'Strict mode code!'");
strict1("'use strict'; 'Strict mode code!'");
strict2(eval, "'Non-strict code.'");
strict2(eval, "'use strict'; 'Strict mode code!'");
nonstrict("'Non-strict code.'");
nonstrict("'use strict'; 'Strict mode code!'");

增强的安全措施

  • 禁止this关键字指向全局对象

    在严格模式中,调用的函数(不是方法)中的一个this值是undefined;在非严格模式中,调用的函数中的this值总是指向全局变量。

    可以根据这种特性判断JavaScript实现是否支持严格模式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    var hasStrictMode = (function() {
    'use strict';
    return this === undefined;
    }());
    console.log(hasStrictMode); // => true
    -----------------------------------
    var hasStrictMode = (function() {
    return this === undefined;
    }());
    console.log(hasStrictMode); // => false

    使用构造函数时,如果忘了加new,this不再指向全局对象,而是报错。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    (function() {
    this.a = 1;
    }());
    console.log(a); // => 1
    -----------------------
    (function() {
    'use strict';
    this.a = 1;
    }());
    console.log(a); // => error
    -----------------------
    function f() {
    'use strict';
    this.a = 1;
    };
    console.log(new f().a); // => 1

    在严格模式中,通过call()apply()来调用函数时,其中的this值就是通过call()apply()传入的第一个参数;在非严格模式中,null和undefined值被全局对象和转换为对象的非对象值所替代。

  • 禁止在函数内部遍历调用栈

    在严格模式的函数中,arguments.callerarguments.callee都会抛出一个类型异常错误。

    严格模式的函数同样具有callerarguments属性,当访问这两个属性时将抛出类型错误异常。

    1
    2
    3
    4
    5
    6
    function f1(){
    'use strict';
    f1.caller; // error
    f1.arguments; // error
    }
    f1();

禁止删除变量

严格模式下,当delete运算符无法删除变量

只有configurable设置为true的对象属性,才能被删除,试图删除不可配置的属性将抛出一个类型错误异常;在非严格模式中,返回false。

1
2
3
4
5
6
7
8
9
10
'use strict';
var x;
// delete x; -> 语法错误
var obj = Object.create(null, {'x': {
value: 1,   
configurable: true
}});
console.log(obj.x); // => 1
delete obj.x;
console.log(obj.x); // => undefined

显式报错

  • 正常模式下,对一个对象的只读属性进行赋值,不会报错,只会默默地失败。严格模式下,将抛出一个类型错误异常。

    1
    2
    3
    4
    5
    6
    7
    'use strict';
    var obj = {};
    Object.defineProperty(obj, 'v', { value: 1, writable: false });
    obj.v = 2;
    /*
    Uncaught TypeError: Cannot assign to read only property 'v' of object '#<Object>'
    */
  • 严格模式下,对一个使用getter方法读取的属性进行赋值,会报错。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    'use strict';
    var obj = {
    get v() {
    return 1;
    }};
    obj.v = 2;
    /*
    Uncaught TypeError: Cannot set property v of #<Object> which has only a getter
    */
  • 严格模式下,对禁止扩展的对象添加新属性,会抛出类型错误异常。

    1
    2
    3
    4
    5
    6
    7
    'use strict';
    var obj = {};
    Object.preventExtensions(obj);
    obj.v = 1;
    /*
    Uncaught TypeError: Cannot add property v, object is not extensible
    */
  • 严格模式下,删除一个不可删除的属性,会抛出类型错误异常。

    1
    2
    3
    4
    5
    'use strict';
    delete Object.prototype;
    /*
    Uncaught TypeError: Cannot delete property 'prototype' of function Object() { [native code] }
    */

重名错误

函数不能有重名的参数

正常模式下,如果函数有多个重名的参数,可以用arguments[i]读取。严格模式下,这属于语法错误。

1
2
3
4
5
6
7
'use strict';
function f(a, a, b) { // 语法错误
return ;
}
/*
Uncaught SyntaxError: Duplicate parameter name not allowed in this context
*/

禁止八进制表示法

正常模式下,整数的第一位如果是0,表示这是八进制数,比如0100等于十进制的64。严格模式禁止这种表示法,整数第一位为0,将报错。

1
2
3
4
5
'use strict';
var n = 0100;
/*
Uncaught SyntaxError: Octal literals are not allowed in strict mode.
*/

arguments对象的限制

  • arguments 不能通过程序语法被绑定(be bound)或赋值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    'use strict';
    /*
    Uncaught SyntaxError: Unexpected eval or arguments in strict mode
    */
    arguments++;
    --------------------------------
    var obj = { set p(arguments) {
    // code
    }};
    --------------------------------
    try {
    // code
    } catch(arguments) {
    // code
    }
    --------------------------------
    function arguments() {
    // code
    }
    --------------------------------
    var f = new Function("arguments", "'use strict'; return 17;");
  • arguments不再追踪参数的变化

    严格模式下,参数的值不会随arguments对象的值的改变而变化。

    在正常模式下,对于第一个参数是arg的函数,对arg赋值时会同时赋值给arguments[0],反之亦然(除非没有参数,或者arguments[0] 被删除)。

    严格模式下,函数的arguments对象会保存函数被调用时的原始参数。arguments[i]的值不会随与之相应的参数的值的改变而变化,同名参数的值也不会随与之相arguments[i]的值的改变而变化。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    function f(a) {
    a = 2;
    return [a, arguments[0]];
    }
    console.log(f(1)); // => [2,2]
    -----------------------------------------------
    function f(a) {
    'use strict';
    a = 2;
    return [a, arguments[0]];
    }
    console.log(f(1)); // => [2,1]
  • 禁止使用arguments.callee

    正常模式下,arguments.callee 指向当前正在执行的函数;在严格模式下,arguments.callee 是一个不可删除属性,而且赋值和读取时都会抛出异常

    1
    2
    3
    4
    5
    6
    7
    8
    'use strict';
    var f = function() {
    return arguments.callee;
    };
    f();
    /*
    Uncaught TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them
    */

保留字

  • 在正常模式中合法,严格模式中是保留字

    implements    let    private    public    yield</br>interface    package    protected    static

  • 严格限制,并不完全是保留字,但不能用作变量名、函数名和参数名

    arguments    eval