严格模式到底有多严格

554 阅读7分钟

严格模式具有一些优点,我们在很多时候也经常发现他与非严格模式的不同。

结合诸多资料简单总结一下严格模式

它相对未加限制的JavaScript的语义进行了一些修改:

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

使用严格模式,需要使用严格模式编译指示

"use strict"

支持严格模式的JS引擎会识别并启用严格模式。而不支持的仅把他当作一个字符串

也可以单独在某个函数内开启严格模式

function a (){
    "use strict";
    //函数体
}

因此引出一个问题,"use strict"并不严格限制一定要写在第一行。前边无产生实际运行结果的语句,"use strict"可以不在第一行

function(){
    "asd";
    "use strict";
    a = 1;
}

在mdn中这么说

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

简单来讲,对全局使用严格模式,在有的时候会出现一些意料之外的问题(亚马逊网站上出现的错误627531 - Lighnting deals on Amazon shows only closed teasers (mozilla.org)

因此,在学习时期建议在函数内单独开启严格模式

那么严格模式到底有多严格呢?

1、变量

  • 严格模式下,不允许意外创建全局变量

    //变量未声明
    //非严格模式下:创建一个全局变量并初始化为1
    //严格模式下:抛出错误
    a = 1;
    
  • 严格模式下,不允许使用delete删除变量,否则会抛出错误

    // delete 操作符用于删除对象的某个属性;如果没有指向这个属性的引用,那它最终会被释放。
    //删除变量
    //非严格模式下:允许这样,但是可能会静默失败,返回false
    //严格模式下:抛出错误 
    let color = 'red';
    delete color;
    

    何谓静默失败呢?就是在很多时候,你进行了操作,但是没有任何效果,当然也没有报错,就像下边这样

    /*
    Object.freeze() 方法可以冻结一个对象。一个被冻结的对象再也不能被修改;冻结了一个对象则不能向这个对象添加新的属性,不能删除已有属性,不能修改该对象已有属性的可枚举性、可配置性、可写性,以及不能修改已有属性的值。此外,冻结一个对象后该对象的原型也不能被修改。
    */
    const obj={name:"Blue"};
    Object.freeze(obj);
    obj.name="Bluemiao";
    //非严格模式下不报错,但是输出是Blue
    //严格模式下,抛出错误
    console.log(obj.name); 
    
  • 严格模式下,不能使用保留字作为变量名

    //抛出错误 
    let this = 'Bluemiao';
    

    ES6中的保留关键字(词法文法 - JavaScript | MDN (mozilla.org)

    2、对象

  • 严格模式下,给只读属性的赋值会抛出错误。

    //get语法将对象属性绑定到查询该属性时将被调用的函数。详见mdn
    "use strict";
    const obj = {
        log:['a','b','c'],
        latest:get x (){
            if(log.length === 1) return undefined
            return this.log(this.log.length - 1)
        }
    };
    ​
    console.loh(obj.latest);
    obj.latest = 5;    //会抛出错误
    

    以上代码在node.js环境下测试,但是放在浏览器控制台发现并没有什么效果

    据说是因为在console中的js代码是通过eval()来执行的

    也没找到太好的解释,不过我们可以使用匿名函数来避免这问题

    (function() {
      "use strict";
      const obj = {
        log: ['a', 'b', 'c'],
        get latest() {
          if (this.log.length === 1) return undefined
          return this.log[this.log.length - 1]
        }
      };
      console.log(obj.latest);
      obj.latest = 5;
    })()
    
  • 严格模式下,在不可配置属性上使用delete会抛出错误错误。

    "use strict";
    delete Object.prototype;
    
  • 严格模式下,给不可写属性赋值会抛出 错误。

    //Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
    //默认情况下,使用 Object.defineProperty() 添加的属性值是不可修改(immutable)的,非默认情况就是writable:true
    "use strict";
    var obj1 = {};
    Object.defineProperty(obj1, "x", { value: 42, writable: false });
    obj1.x = 5; 
    
  • 严格模式下,给不可扩展对象的新属性赋值会抛出 错误。

    //Object.preventExtensions()方法让一个对象变的不可扩展,也就是永远不能再添加新的属性。
    "use strict";
    var obj2 = {};
    Object.preventExtensions(obj2);
    obj2.newProp = "no"; 
    

3、函数

3.1

  • 严格模式下,函数参数名不能重复

    //在非严格模式下,这种用法不算错误,函数内使用的参数以最后一个出现的为准,想访问第一个需要借助arguments访问
    //抛出错误
    function func (a,a,a,a){
        "use strict";
        console.log(a); 
        console.log(arguments[0]);  
    };
    ​
    func(10,20,30,40);
    

    但在过程中我还发现

    //只有借助arguments[0]才能访问第一个,其他方式都不好整。不过建议大家不要这么起名字
    function func(a, a, a, a) {
      console.log(a); //40
      console.log(arguments[[...arguments].indexOf(a)]); //40
    };
    ​
    func(10, 20, 30, 40);
    
  • 严格模式下,像默认参数、解构操作等都会报错

    function foo(a, b, c) { 
     "use strict"; 
    } 
    //报错
    function bar(a, b, c='d') { 
     "use strict"; 
    } 
    //报错
    function qux(a, b, ...c) { 
     "use strict"; 
    } 
    

3.2 arguments

  • 严格模式下,函数的 arguments 对象会保存函数被调用时的原始参数,arguments[i] 的值不会随与之相应的参数的值的改变而变化;同名参数的值也不会随与之相应的 arguments[i] 的值的改变而变化。简而言之,他们两个是分离的,是独立的

    function sayHello (value){
        "use strict";
        value = "I don't want to say hello!"; 
        //严格模式下,输出Hello world
        //非严格模式下,输出I don't want to say hello!
        console.log(arguments[0]);
        return [value,arguments[0]];
    };
    ​
    let zzr = sayHello("Hello world");  //Hello world
    console.log(zzr[0] === "I don't want to say hello!");  //true
    
  • 严格模式下,不再支持 arguments.callee

    arguments.callee 指向当前正在执行的函数,但就目前我学的来说,感觉没啥用,因为我的函数是声明的,我在内部的调用完全可以使用函数名来实现。

    或许对于箭头函数或是匿名函数有用?望大家不吝赐教。

  • 严格模式下,不能操作arguments 的值

    //抛出错误
    "use strict";
    arguments++; 
    

    以下操作都不行:

    使用 let 声明;

    赋予其他值;

    修改其包含的值,如使用++;

    用作函数名;

    用作函数参数名

    在 try/catch 语句中用作异常名称。

    3.3 eval

  • 严格模式下的 eval 不再为上层范围(surrounding scope,注:包围eval代码块的范围)引入新变量。 在正常模式下, 代码 eval("var x;") 会给上层函数(surrounding function)或者全局引入一个新的变量 x 。 这意味着,eval 可能引入的新变量会覆盖它的外层变量。 在严格模式下 eval 仅仅为被运行的代码创建变量(就是说原先是个大家长,谁家的事都要掺一脚,现在只能自己家的事管一管了), 所以 eval 不会使得名称映射到外部变量或者其他局部变量:

    // 使用 eval()创建变量
    // 非严格模式:输出 10 
    // 严格模式:抛出 ReferenceError 
    function doSomething(){ 
     eval("var x = 10"); 
     console.log(x); 
    };
    ​
    doSomething()
    
  • 严格模式下,不能操作eval 的值,与arguments相类似

4、this

  • 使用函数的 apply()或 call()方法时,在非严格模式下 null 或 undefined 值会被强制转型为全局对象(浏览器中时window)。在严格模式下,则始终以指定值作为函数 this 的值,无论指定的是什么值

    // 访问属性
    // 非严格模式:访问全局属性,在浏览器中测试是red
    // 严格模式:抛出错误,因为 this 值为 null 
    var color = "red";
    function displayColor() {
      "use strict"
      console.log(this.color);
    }
    displayColor.call(null);
    

5、其他变化

  • 严格模式下,没有with这种语法
  • 严格模式下,取消了八进制字面量

严格模式下,不允许函数声明,除非它们位于脚本或函数的顶级,也就是说,严格模式下,在if或者for里不能声明函数了(手动增加删除线。这个并没有测试出来)

"use strict";
if(true){
    function sum(){
        console.log(10 + 10);
    };
};

容我再去看看

参考资料

  1. 《JavaScript高级程序设计》
  2. 严格模式 mdn(严格模式 - JavaScript | MDN (mozilla.org))
  3. Javascript 严格模式详解 - 阮一峰的网络日志 (ruanyifeng.com)