严格模式

课后整理 2020-12-20

【拓展】

严格模式是限制性更强的JavaScript变体,旨在改善错误检查功能,并且标识可能不会延续到未来JavaScript版本的脚本。与常规的JavaScript语义不同,其分析更为严格,下面介绍严格模式对Javascript语法和行为限制性规定。

显式声明变量

Javascript是弱类型语言,不使用var声明的变量默认会转为全局变量。但在严格模式中将不允许,会报语法错误。

【示例1】执行下面代码,将会提示语法错误。

"use strict";
v = 1;                      // 报错,v未声明
for(i = 0; i < 2;  i++) {    // 报错,i未声明
}

因此,严格模式下,变量都必须先用var命令声明,然后再使用。

静态绑定

在正常模式下,Javascript允许动态绑定,即某些属性和方法到底属于哪一个对象,不是在编译时确定的,而是在运行时确定的。

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

因为with语句无法在编译时就确定,属性到底归属哪个对象。例如:

  "use  strict";
  var  v = 1;
  with  (o){               // 语法错误
     v  = 2;
  }

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

在正常模式下,eval语句的作用域,取决于它处于全局作用域,还是处于函数作用域。而在严格模式下,eval语句本身就是一个作用域,不再能够生成全局变量了,它所生成的变量只能用于eval内部。例如:

  "use  strict";
  var  x = 2;
  console.info(eval("var  x = 5; x"));  // 5
  console.info(x);                         // 2

另外,任何使用'eval'的操作都会被禁止,例如,下面用法都是非法的。

'use strict'
var obj = {}
var eval = 3
obj.eval = 1
obj.a = eval
for (var eval in obj) {}
function eval() {}
function func(eval) {}
var func = new  Function('eval')

增强的安全措施

【示例2】执行下面代码,比较正常模式和严格模式下this的值。

  function  f(){
    return  !this;
  }   // 返回false,因为"this"指向全局对象,"!this"就是false
  function  f(){ 
    "use  strict";
    return  !this;
  }  // 返回true,因为在严格模式下,this的值为undefined,所以"!this"为true。

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

  function  f(){ 
    "use  strict";
    this.a  = 1;
  };
  f();                            // 报错,this未定义

【示例4】caller、callee和arguments的调用行为都被禁用。

  function  f1(){
    "use  strict";
    f1.caller;                    // 报错
    f1.arguments;                 // 报错
  }
  f1();

禁止删除变量

在严格模式下无法删除变量。只有configurable设置为true的对象属性,才能被删除。

【示例5】错误的删除操作。

  "use  strict";
  var  x;
  delete  x;                       // 语法错误
  var  o = Object.create(null, 'x', {
      value: 1,
      configurable: true
  });
  delete  o.x;                 // 删除成功

显式报错

在正常模式下,对一个对象的只读属性进行赋值,不会报错,只会默默地失败。严格模式下,将报错。

【示例6】提示错误信息。

  "use  strict";
  var  o = {};
  Object.defineProperty(o,  "v", { value: 1, writable: false });
  o.v  = 2;                        // 报错

严格模式下,对一个使用getter方法读取的属性进行赋值,会报错。

  "use  strict";
  var  o = {
    get  v() { return 1; } 
  };
  o.v  = 2;                        // 报错

严格模式下,对禁止扩展的对象添加新属性,会报错。

  "use  strict";
  var  o = {};
  Object.preventExtensions(o);
  o.v  = 1; // 报错

严格模式下,删除一个不可删除的属性,会报错。

  "use  strict";
  delete  Object.prototype;        // 报错

重名错误

严格模式新增了一些语法错误。

在正常模式下,如果对象有多个重名属性,最后赋值的那个属性会覆盖前面的值。在严格模式下,这属于语法错误。例如:

  "use  strict";
  var  o = {
    p:  1,
    p:  2
  };                               // 语法错误

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

  "use  strict";
  function  f(a, a, b) {           // 语法错误
    return  ; 
  }

禁止八进制表示法

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

  "use  strict";
  var  n = 0100;                   // 语法错误

arguments对象的限制

arguments是函数的参数对象,严格模式对它的使用进行限制。

【示例7】下面代码演示了无法对arguments对象写操作。

  "use  strict";
  arguments++;  // 语法错误
  var  obj = { set p(arguments) { }};  // 语法错误
  try  { } catch (arguments) { }       // 语法错误
  function  arguments() { }            // 语法错误
  var  f = new Function("arguments", "'use strict'; return 17;");  // 语法错误
  function  f(a) {
    a  = 2;
    return  [a, arguments[0]];
  }
  f(1);                            // 正常模式为[2,2]
  function  f(a) {
    "use  strict"; 
    a  = 2;
    return  [a, arguments[0]];
  }
  f(1);                            // 严格模式为[2,1]

这意味着,无法在匿名函数内部调用自身了。例如:

  "use  strict";
  var  f = function() { return arguments.callee; };
  f();                             // 报错

函数必须声明在顶层

将来Javascript的新版本会引入块级作用域。为了与新版本接轨,严格模式只允许在全局作用域或函数作用域的顶层声明函数。也就是说,不允许在非函数的代码块内声明函数。

【示例8】下面代码演示了函数不能够用在条件和循环语句中。

  "use  strict";
  if  (true) {
    function  f() { }             // 语法错误
  }
  for  (var i = 0; i < 5; i++) {
    function  f2() { }            // 语法错误
  }

保留字

为了向未来Javascript新版本过渡,严格模式新增了一些保留字:implements、interface、 let、package、private、protected、public、static、yield。使用这些词作为变量名将会报错。

【示例9】下面代码显示implements是保留字,并禁止使用。

  function  package(protected) {   // 语法错误
    "use  strict";
    var  implements;              // 语法错误
  }

此外,ECMAscript 5本身还规定了另一些保留字,如class、enum、export、extends、import、super,以及各大浏览器自行增加的const保留字,也是不能作为变量名的。

动态绑定

【示例10】在下面代码中,输出依次为"string"、"number"。而在非严格模式中call、apply将对值类型的"abcd"包装为对象后传入,即两次输出都为"object"。

'use strict'
function func() {
    console.log(typeof this)
}
func.call('abcd')                   // string
func.apply(1)                       //  number 

【示例11】下面代码输出依次是undefined、null,而在正常模式中则是宿主对象(浏览器里是window,node.js环境则是global)。

'use strict'
function func() {
    console.log(this)
}
func.call(undefined)                // undefined
func.call(null)                     //  null 

bind是ECMAScript 5给Function.prototype新增的一个方法,它和call、apply一样在function上直接调用。它返回一个指定了上下文和参数的函数。当它的第一个参数为null、undefined时,情形和call、apply一样,this也为null、undefined。

【示例12】下面代码在非严格模式中输出都是window(或global)。

'use strict'
function func() {
    console.log(this)
}
var f1 = func.bind(null)
var f2 =  func.bind(undefined)
f1()                                // null
f2()                                // undefined