JavaScript严格模式

课后整理 2020-12-14

ECMAscript 5新增严格运行模式。顾名思义,严格模式就是使Javascript在更严格的条件下运行。包括IE 10在内的主流浏览器都已经支持它,许多大项目已经开始全面拥抱它。定义严格模式的目的:

【提示】

同样的代码,在严格模式中,可能会有不一样的运行结果。一些在正常模式下可以运行的语句,在严格模式下将不能运行。掌握这些内容,有助于更细致深入地理解Javascript。

启用严格模式

启用严格模式很简单,只要在代码首部加入如下注释字符串即可:

"use strict"

不支持该模式的浏览器会把它当作一行普通字符串,加以忽略。

严格模式有两种应用场景,一种是全局模式,一种是局部模式。

全局模式

将"use strict"放在脚本文件的第一行,则整个脚本都将以严格模式运行。如果这行语句不在第一行,则无效,整个脚本将以正常模式运行。如果不同模式的代码文件合并成一个文件,这一点需要特别注意。

严格地说,只要前面不是产生实际运行结果的语句,"use strict"可以不在第一行。

【示例1】下面代码表示,一个网页中依次有两段Javascript代码。前一个script标签是严格模式,后一个不是。

  <script>
    "use  strict";
    console.log("这是严格模式。");
  </script>
  <script>
    console.log("这是正常模式。");
  </script>

局部模式

【示例2】将"use strict"放到函数内的第一行,则整个函数将以严格模式运行。

  function  strict(){
    "use  strict";
    return  "这是严格模式。";
  }
  function  notStrict(){
    return  "这是正常模式。";
  } 

模块模式

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

【示例3】如果定义一个模块或者一个库,可以采用一个匿名函数自执行的方式进行设计:

(function (){ 
    "use strict";
    //在这里编写JavaScript代码
})(); 

【提示】

"use strict"的位置比较讲究,它必须在首部。首部是指其前面没有任何有效Javascript代码。以下都是无效的,将不会触发严格模式。

var width = 10;
'use strict';
globalVar = 100 ;
;
'use strict';
globalVar = 100;

function func() {
    ;
    'use strict';
    localVar = 200;
}

function func() {
    ;'use strict'
    localVar = 200;
} 

当然,"use strict"前加注释是可以的:

//严格模式
'use strict';
globalVar = 100;

function func() {
    // 严格模式
    'use strict';
    localVar = 200;
}

严格模式的执行限制

下面简单介绍严格模式对JavaScript语法和行为限制性规定。

【提示】

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

显式声明变量

在严格模式下,变量必须是先声明,后使用,否则会抛出语法错误。

【示例1】执行下面代码,将会提示语法错误。因此必须先用var语句声明,然后再使用。

"use strict";                                               //开启严格模式 
v = 1;                                                       //报错,v未声明 

静态绑定

在正常模式下,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

【示例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')                  //非法的实参 

增强的安全措施

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

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

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

(function f(){ 
    "use  strict";                                        //开启严格模式 
    this.a  = 1;                                          //报错,this未定义 
}) ();

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

(function f(){
  "use  strict";                                        //开启严格模式 
  f.caller;                                             //报错 
  f.arguments;                                      //报错 
}) ();

禁止删除变量

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

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

"use strict";                                               //开启严格模式 
var x;
delete x;                                                   //语法错误 
var o = Object.create(null, 'x', {                  //创建对象 
    value:  1,                                            //定义属性值 
    configurable:  true                               //设置属性为可修改 
});
delete o.x;                                                //删除成功 

显式报错

在正常模式下,为对象的只读属性赋值,不会报错,只会失败。在严格模式下将报错。

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

"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是函数的参数对象,严格模式对它的使用进行限制。

【示例8】下面代码演示了无法对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新版本将引入块级作用域。为了接轨,严格模式只允许在全局作用域或函数作用域的顶层声明函数。也就是说,不允许在非函数的代码块内声明函数。

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

"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。使用这些词作为变量名将会报错。

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

function package(protected) {                     //语法错误 
    "use  strict";                                        //开启严格模式 
    var  implements;                                 //语法错误 
}

此外,ECMAscript 5也规定了一些保留字,如class、enum、export、extends、import、super,以及各大浏览器自行增加的const保留字,都不再允许用户使用。

动态绑定

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

'use strict'                                                  //开启严格模式 
function func() {
     console.log(typeof this)
}
func.call('abcd')                                         //string
func.apply(1)                                            //number 

【示例12】下面代码输出依次是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上直接调用。bind返回一个指定了上下文和参数的函数。当它的第一个参数为null、undefined时,情形和call、apply一样,this也为null、undefined。

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

'use strict'                                                  //开启严格模式 
function func() {
     console.log(this)
}
var f1 = func.bind(null)
var f2 = func.bind(undefined)
f1()                                                          //null
f2()                                                          //undefined