跳到主要内容

严格模式

严格模式介绍


ECMAScript 5严格模式是采用具有限制性JavaScript变体的一种方式,从而使代码隐式地脱离“马虎模式/稀松模式/懒散模式“(sloppy)模式。支持严格模式的浏览器在检测到代码中有严格模式时,会以更加严格的方式对带啊吗进行检测和执行。

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

  • 严格模式通过抛出错误来消除了一些原有静默错误。
  • 严格模式修复了一些导致 JavaScript引擎难以执行优化的缺陷:有时候,相同的代码,严格模式可以比非严格模式下运行得更快。
  • 严格模式禁用了在ECMAScript的未来版本中可能会定义的一些语法。

调用严格模式


严格模式可以应用到整个脚本或个别函数中。不要在封闭大括弧 内这样做,在这样的上下文中这么做是没有效果的。在 eval 、Function 、内联事件处理属性、 WindowTimers.setTimeout() (en-US) 方法中传入的脚本字符串,其行为类似于开启了严格模式的一个单独脚本,它们会如预期一样工作。

为脚本开启严格模式

为整个脚本文件开启严格模式,需要在所有语句之前放一个特定语句 "use strict"; (或 'use strict';)

// 整个脚本都开启严格模式的语法
"use strict";
var v = "Hi! I'm a strict mode script!";

这种语法存在陷阱,有一个大型网站已经被它坑倒了:不能盲目的合并冲突代码。试想合并一个严格模式的脚本和一个非严格模式的脚本:合并后的脚本代码看起来是严格模式。反之亦然:非严格合并严格看起来是非严格的。合并均为严格模式的脚本或均为非严格模式的都没问题,只有在合并严格模式与非严格模式有可能有问题。建议按一个个函数去开启严格模式(至少在学习的过渡期要这样做).

为函数开启严格模式

要给某个函数开启严格模式,得把 "use strict"; (或 'use strict'; )声明一字不漏地放在函数体所有语句之前。

function strict() {
// 函数级别严格模式语法
'use strict';
function nested() {
return "And so am I!";
}
return "Hi! I'm a strict mode function! " + nested();
}

function notStrict() {
return "I'm not strict.";
}

严格模式中的变化


将过失错误转成异常

在严格模式下, 某些先前被接受的过失错误将会被认为是异常

  • 第一 严格模式下无法再意外创建全局变量

    在普通的JavaScript里面给一个错误命名的变量名赋值会使全局对象新增一个属性并继续“工作”(尽管将来可能会失败:在现代的JavaScript中有可能)。严格模式中意外创建全局变量被抛出错误替代:

    // 非严格模式下:
    message = "hello";
    function foo(){
    code = 200;
    }
    foo();

    console.log(window.message); //省略变量声明字符,则默认创建全局变量 hello
    console.log(window.code); //省略变量申明字符,则默认创建全局变量 200

    // 严格模式下:
    'use strict'
    message = "hello";
    function foo(){
    code = 200;
    }
    foo();

    console.log(window.message); //报错 message 未定义
    console.log(window.code); //报错 code 未定义
  • 第二 严格模式会使引起静默失败

    注: 静默失败就是(不报错也没有任何效果)的赋值操作抛出异常。例如, NaN 是一个不可写的全局变量. 在正常模式下, 给 NaN 赋值不会产生任何作用; 开发者也不会受到任何错误反馈. 但在严格模式下, 给 NaN 赋值会抛出一个异常. 任何在正常模式下引起静默失败的赋值操作 (给不可写属性赋值, 给只读属性(getter-only)赋值, 给不可扩展对象(non-extensible object)的新属性赋值) 都会抛出异常:

    "use strict";

    // 给不可写属性赋值
    var obj1 = {};
    Object.defineProperty(obj1, "x", { value: 42, writable: false });
    obj1.x = 9; // 抛出TypeError错误

    // 给只读属性赋值
    var obj2 = { get x() { return 17; } };
    obj2.x = 5; // 抛出TypeError错误

    // 给不可扩展对象的新属性赋值
    var fixed = {};
    Object.preventExtensions(fixed);
    fixed.newProp = "ohai"; // 抛出TypeError错误
  • 第三 在严格模式下, 试图删除不可删除的属性时会抛出异常(之前这种操作不会产生任何效果)

    "use strict";
    delete Object.prototype; // 抛出TypeError错误
  • 第四 严格模式要求一个对象内的所有属性名在对象内必须唯一

    正常模式下重名属性是允许的,最后一个重名的属性决定其属性值。因为只有最后一个属性起作用,当代码要去改变属性值而不是修改最后一个重名属性的时候,复制这个对象就产生一连串的bug。在严格模式下,重名属性被认为是语法错误:

    "use strict";
    var o = { p: 1, p: 2 }; // !!! 语法错误
  • 第五 严格模式要求函数的参数名唯一

    在正常模式下, 最后一个重名参数名会掩盖之前的重名参数. 之前的参数仍然可以通过 arguments[i] 来访问, 还不是完全无法访问. 然而, 这种隐藏毫无意义而且可能是意料之外的 (比如它可能本来是打错了), 所以在严格模式下重名参数被认为是语法错误:

    // 非严格模式下 
    function foo(x,y,z,x){
    console.log(x,y,z,x); //结果 40 20 30 40 。后面的同名参数会覆盖之前的同名参数
    }
    foo(10,20,30,40);

    // 严格模式下
    function foo(x,y,z,x){
    'use strict'
    console.log(x,y,z,x); //报错 Duplicate parameter name not allowed in this context
    }
    foo(10,20,30,40);
  • 第六 严格模式禁止八进制数字语法

    ECMAScript并不包含八进制语法, 但所有的浏览器都支持这种以零(0)开头的八进制语法: 0644 === 420 还有 "\045" === "%".在ECMAScript 6中支持为一个数字加"0o"的前缀来表示八进制数.

    // 之前进制写法:
    var a = 010; // 八进制 严格模式下不被支持
    var b = 0123; // 十六进制 严格模式下不被支持

    // ES6 进制写法:
    var a = 0o10; // ES6: 八进制
    var b = 0x123; // ES6: 十六进制
    var c = 0b100; // ES6: 二进制

简化变量的使用

严格模式简化了代码中变量名字映射到变量定义的方式. 很多编译器的优化是依赖存储变量X位置的能力:这对全面优化JavaScript代码至关重要. JavaScript有些情况会使得代码中名字到变量定义的基本映射只在运行时才产生. 严格模式移除了大多数这种情况的发生, 所以编译器可以更好的优化严格模式的代码.

  • 第一 严格模式禁用 with

    with所引起的问题是块内的任何名称可以映射(map)到with传进来的对象的属性, 也可以映射到包围这个块的作用域内的变量(甚至是全局变量), 这一切都是在运行时决定的: 在代码运行之前是无法得知的. 严格模式下, 使用 with 会引起语法错误, 所以就不会存在 with 块内的变量在运行时才决定引用到哪里的情况了:

    "use strict";
    var x = 17;
    with (obj) { // !!! 语法错误
    // 如果没有开启严格模式,with中的这个x会指向with上面的那个x,还是obj.x?
    // 如果不运行代码,我们无法知道,因此,这种代码让引擎无法进行优化,速度也就会变慢。
    x;
    }

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

  • 第二 严格模式下的 eval 不再为上层范围引入新变量

    在正常模式下, 代码 eval("var x;") 会给上层函数(surrounding function)或者全局引入一个新的变量 x . 这意味着, 一般情况下, 在一个包含 eval 调用的函数内所有没有引用到参数或者局部变量的名称都必须在运行时才能被映射到特定的定义 (因为 eval 可能引入的新变量会覆盖它的外层变量). 在严格模式下 eval 仅仅为被运行的代码创建变量, 所以 eval 不会使得名称映射到外部变量或者其他局部变量:

  • 第三 严格模式禁止删除声明变量。delete name 在严格模式下会引起语法错误

让eval和arguments变的简单

严格模式argumentseval少了一些奇怪的行为。两者在通常的代码中都包含了很多奇怪的行为: eval会添加删除绑定,改变绑定好的值,还会通过用它索引过的属性给形参取别名的方式修改形参. 虽然在未来的ECMAScript版本解决这个问题之前,是不会有补丁来完全修复这个问题,但严格模式下将eval和arguments作为关键字对于此问题的解决是很有帮助的。

  • 第一 名称evalarguments不能通过程序语法被绑定或赋值

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

    在正常模式下,对于第一个参数是 arg 的函数,对 arg 赋值时会同时赋值给 arguments[0],反之亦然(除非没有参数,或者 arguments[0] 被删除)。严格模式下,函数的 arguments 对象会保存函数被调用时的原始参数。arguments[i] 的值不会随与之相应的参数的值的改变而变化,同名参数的值也不会随与之相应的 arguments[i] 的值的改变而变化。

    function f(a) {
    "use strict";
    a = 42;
    return [a, arguments[0]];
    }
    var pair = f(17);
    console.assert(pair[0] === 42);
    console.assert(pair[1] === 17);
  • 第三 不再支持arguments.callee

    正常模式下,arguments.callee 指向当前正在执行的函数。这个作用很小:直接给执行函数命名就可以了!此外,arguments.callee 十分不利于优化,例如内联函数,因为 arguments.callee 会依赖对非内联函数的引用。在严格模式下,arguments.callee 是一个不可删除属性,而且赋值和读取时都会抛出异常:

    "use strict";
    var f = function() { return arguments.callee; };
    f(); // 抛出类型错误

"安全的" JavaScript

  • 第一 在严格模式下通过this传递给一个函数的值不会被强制转换为一个对象

    对一个普通的函数来说,this总会是一个对象:不管调用时this它本来就是一个对象;还是用布尔值,字符串或者数字调用函数时函数里面被封装成对象的this;还是使用undefined或者null调用函数式this代表的全局对象(使用call, apply或者bind方法来指定一个确定的this)。这种自动转化为对象的过程不仅是一种性能上的损耗,同时在浏览器中暴露出全局对象也会成为安全隐患,因为全局对象提供了访问那些所谓安全的JavaScript环境必须限制的功能的途径。所以对于一个开启严格模式的函数,指定的this不再被封装为对象,而且如果没有指定this的话它值是undefined:

    // 对于 setTimeout 而言,严格模式和非严格模式 this 指向一致

    'use strict'
    setTimeout(()=>{
    console.log(this); // 指向 undefined,且指向一致
    },200);

    'use strict'
    setTimeout(function(){
    console.log(this); // 指向 window,且指向一致
    },200);

    // 对于普通函数而言,严格模式下和非严格模式下:
    // 普通函数非严格模式下:
    function foo(){
    console.log(this); // 指向 window
    }
    foo();

    // 普通函数严格模式下:
    'use strict'
    function foo(){
    console.log(this); // 指向 undefined
    }
    foo();

    // 对于普通函数的 call 指向:
    "use strict";
    function fun() { return this; }
    console.assert(fun() === undefined);
    console.assert(fun.call(2) === 2);
    console.assert(fun.apply(null) === null);
    console.assert(fun.call(undefined) === undefined);
    console.assert(fun.bind(true)() === true);
  • 第二 在严格模式中再也不能通过广泛实现的ECMAScript扩展“游走于”JavaScript的栈中

    在普通模式下用这些扩展的话,当一个叫fun的函数正在被调用的时候,fun.caller是最后一个调用fun的函数,而且fun.arguments包含调用fun时用的形参。这两个扩展接口对于“安全”JavaScript而言都是有问题的,因为他们允许“安全的”代码访问"专有"函数和他们的(通常是没有经过保护的)形参。如果fun在严格模式下,那么fun.caller和fun.arguments都是不可删除的属性而且在存值、取值时都会报错:

    function restricted() {
    "use strict";
    restricted.caller; // 抛出类型错误
    restricted.arguments; // 抛出类型错误
    }

    function privilegedInvoker() {
    return restricted();
    }

    privilegedInvoker();
  • 第三 严格模式下的arguments不会再提供访问与调用这个函数相关的变量的途径

    在一些旧时的ECMAScript实现中arguments.caller曾经是一个对象,里面存储的属性指向那个函数的变量。这是一个安全隐患,因为它通过函数抽象打破了本来被隐藏起来的保留值;它同时也是引起大量优化工作的原因。出于这些原因,现在的浏览器没有实现它。但是因为它这种历史遗留的功能,arguments.caller在严格模式下同样是一个不可被删除的属性,在赋值或者取值时会报错:

    "use strict";
    function fun(a, b) {
    "use strict";
    var v = 12;
    return arguments.caller; // 抛出类型错误
    }
    fun(1, 2); // 不会暴露v(或者a,或者b)

为未来的ECMAScript版本铺平道路

  • 第一 在严格模式中一部分字符变成了保留的关键字

    这些字符包括implements, interface, let, package, private, protected, public, static和yield。在严格模式下,你不能再用这些名字作为变量名或者形参名。

    function package(protected) { // !!!
    "use strict";
    var implements; // !!!

    interface: // !!!
    while (true) {
    break interface; // !!!
    }

    function private() { } // !!!
    }
    function fun(static) { 'use strict'; } // !!!
  • 第二 严格模式禁止了不在脚本或者函数层面上的函数声明

    在浏览器的普通代码中,在“所有地方”的函数声明都是合法的。这并不在ES5规范中(甚至是ES3)!这是一种针对不同浏览器中不同语义的一种延伸。未来的ECMAScript版本很有希望制定一个新的,针对不在脚本或者函数层面进行函数声明的语法。在严格模式下禁止这样的函数声明对于将来ECMAScript版本的推出扫清了障碍:

    "use strict";
    if (true) {
    function f() { } // !!! 语法错误
    f();
    }

    for (var i = 0; i < 5; i++) {
    function f2() { } // !!! 语法错误
    f2();
    }

    function baz() { // 合法
    function eit() { } // 同样合法
    }