ECMAscript 5新增严格运行模式。顾名思义,严格模式就是使Javascript在更严格的条件下运行。包括IE 10在内的主流浏览器都已经支持它,许多大项目已经开始全面拥抱它。定义严格模式的目的:
- 消除Javascript语法的一些不合理、不严谨之处,减少一些怪异行为。
- 消除代码运行的一些不安全之处,保证代码运行的安全。
- 提高编译器效率,增加运行速度。
- 为未来新版本的Javascript做好铺垫。
【提示】
同样的代码,在严格模式中,可能会有不一样的运行结果。一些在正常模式下可以运行的语句,在严格模式下将不能运行。掌握这些内容,有助于更细致深入地理解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代码。以下都是无效的,将不会触发严格模式。
- "use strict"前有代码
var width = 10; 'use strict'; globalVar = 100 ;
- "use strict"前有空语句
; '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语句
with语句无法在编译时确定属性到底归属哪个对象。例如:
"use strict"; //开启严格模式 var v = 1; with (o){ //语法错误 v = 2; }
- 创设eval作用域
在正常模式下,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') //非法的实参
增强的安全措施
- 禁止this指代全局对象
【示例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是函数的参数对象,严格模式对它的使用进行限制。
- 不允许对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;"); //语法错误:作为参数使用
- arguments不再追踪参数的变化
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]
- 禁止使用arguments.callee
这意味着无法在匿名函数内部调用函数自身了。
"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保留字,都不再允许用户使用。
动态绑定
- call、apply的第一个参数直接传入不包装为对象
【示例11】在下面代码中,输出依次为"string"、"number"。而在非严格模式中call、apply将对值类型的"abcd"包装为对象后传入,即两次输出都为"object"。
'use strict' //开启严格模式 function func() { console.log(typeof this) } func.call('abcd') //string func.apply(1) //number
- call、apply的第一个参数为null、undefined时,this为null、undefined
【示例12】下面代码输出依次是undefined、null,而在正常模式中则是宿主对象(浏览器里是window,node.js环境则是global)。
'use strict' //开启严格模式 function func() { console.log(this) } func.call(undefined) //undefined func.call(null) //null
- bind的第一个参数为null、undefined时,this为null、undefined
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