目录
一、严格模式概述
1.严格模式是什么
严格模式是采用具有限制性JavaScript变体的一种方式,从而使代码隐式地脱离“马虎模式/稀松模式/懒散模式“(sloppy)模式。
严格模式不仅仅是一个子集:它的产生是为了形成与正常代码不同的语义。
不支持严格模式与支持严格模式的浏览器在执行严格模式代码时会采用不同行为。
所以在没有对运行环境展开特性测试来验证对于严格模式相关方面支持的情况下,就算采用了严格模式也不一定会取得预期效果。严格模式代码和非严格模式代码可以共存,因此项目脚本可以渐进式地采用严格模式。
严格模式对正常的 JavaScript语义做了一些更改。
注:
- ES6 的模块自动采用严格模式,不管你有没有在模块头部加上"use strict";。
2.严格模式的特点
- 严格模式通过抛出错误来消除了一些原有静默错误。
- 严格模式修复了一些导致 JavaScript引擎难以执行优化的缺陷:有时候,相同的代码,严格模式可以比非严格模式下运行得更快。
- 严格模式禁用了在ECMAScript的未来版本中可能会定义的一些语法。
3.严格模式的限制
严格模式主要有以下限制:
- 变量必须声明后再使用
- 函数的参数不能有同名属性,否则报错
- 不能使用with语句
- 不能对只读属性赋值,否则报错
- 不能使用前缀 0 表示八进制数,否则报错
- 不能删除不可删除的属性,否则报错
- 不能删除变量delete prop,会报错,只能删除属性delete global[prop]
- eval不会在它的外层作用域引入变量
- eval和arguments不能被重新赋值
- arguments不会自动反映函数参数的变化
- 不能使用arguments.callee
- 不能使用arguments.caller
- 禁止this指向全局对象
- 不能使用fn.caller和fn.arguments获取函数调用的堆栈
- 增加了保留字(比如protected、static和interface)
要注意this的限制。ES6 模块之中,顶层的this指向undefined,即不应该在顶层代码使用this。
二、严格模式的使用
1.调用严格模式
为整个脚本文件开启严格模式
需要在所有语句之前放一个特定语句
"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.";
}
2.严格模式的变化
(1)、将过失转化成异常
在严格模式下, 某些先前被接受的过失错误将会被认为是异常. JavaScript被设计为能使新人开发者更易于上手, 所以有时候会给本来错误操作赋予新的不报错误的语义(non-error semantics). 有时候这可以解决当前的问题, 但有时候却会给以后留下更大的问题. 严格模式则把这些失误当成错误, 以便可以发现并立即将其改正.
- 严格模式下无法再意外创建全局变量
普通的JavaScript里面给一个错误命名的变量名赋值会使全局对象新增一个属性并继续“工作”(尽管将来可能会失败:在现代的JavaScript中有可能)。严格模式中意外创建全局变量被抛出错误替代:
"use strict"; // 假如有一个全局变量叫做mistypedVariable mistypedVaraible = 17; // 因为变量名拼写错误 // 这一行代码就会抛出 ReferenceError
- 严格模式会使引起静默失败的赋值操作抛出异常
静默失败是指不报错也没有任何效果
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错误
- 要求一个对象内的所有属性名在对象内必须唯一(Gecko版本34之前)
正常模式下重名属性是允许的,最后一个重名的属性决定其属性值。因为只有最后一个属性起作用,当代码要去改变属性值而不是修改最后一个重名属性的时候,复制这个对象就产生一连串的bug。在严格模式下,重名属性被认为是语法错误:
"use strict"; var o = { p: 1, p: 2 }; // !!! 语法错误
- 要求函数的参数名唯一
正常模式下, 最后一个重名参数名会掩盖之前的重名参数. 之前的参数仍然可以通过
arguments[i] 来访问
, 还不是完全无法访问. 然而, 这种隐藏毫无意义而且可能是意料之外的 (比如它可能本来是打错了), 所以在严格模式下重名参数被认为是语法错误:function sum(a, a, c) { // !!! 语法错误 "use strict"; return a + a + c; // 代码运行到这里会出错 }
- 严格模式禁止八进制数字语法
ECMAScript并不包含八进制语法, 但所有的浏览器都支持这种以零(
0
)开头的八进制语法:0644 === 420
还有"\045" === "%"
.在ECMAScript 6中支持为一个数字加"0
o"的前缀来表示八进制数.var a = 0o10; // ES6: 八进制
- 补充
ECMAScript 6中的严格模式禁止设置primitive值的属性.不采用严格模式,设置属性将会简单忽略(no-op),采用严格模式,将抛出TypeError错误
(function() { "use strict"; false.true = ""; //TypeError (14).sailing = "home"; //TypeError "with".you = "far away"; //TypeError })();
(2)、严格模式简化变量的使用
严格模式简化了代码中变量名字映射到变量定义的方式. 很多编译器的优化是依赖存储变量X位置的能力:这对全面优化JavaScript代码至关重要. JavaScript有些情况会使得代码中名字到变量定义的基本映射只在运行时才产生. 严格模式移除了大多数这种情况的发生, 所以编译器可以更好的优化严格模式的代码.
- 严格模式禁用
with
with
所引起的问题是块内的任何名称可以映射(map)到with传进来的对象的属性, 也可以映射到包围这个块的作用域内的变量(甚至是全局变量), 这一切都是在运行时决定的: 在代码运行之前是无法得知的. 严格模式下, 使用with
会引起语法错误, 所以就不会存在 with 块内的变量在运行时才决定引用到哪里的情况了:!function () { with({ x: 1 }) { console.log(x); //1 } }() !function () { 'use strict'; with({ x: 1 }) { console.log(x); //SyntaxError !!! 语法错误 // 如果没有开启严格模式,with中的这个x会指向with上面的那个x,还是obj.x? // 如果不运行代码,我们无法知道,因此,这种代码让引擎无法进行优化,速度也就会变慢。 } }()
一种取代
with
的简单方法是,将目标对象赋给一个短命名变量,然后访问这个变量上的相应属性注:
解释器在解释一个语句时,如果以function开头,就会理解为函数声明。
而前面加一个"!"
可以让解释器理解为函数表达式,这样就可以立即调用
了
- 禁止删除声明变量
delete name
在严格模式下会引起语法错误:"use strict"; var x; delete x; // !!! 语法错误 eval("var y; delete y;"); // !!! 语法错误
- arguments变为参数的静态副本
非严格模式下,arguments对象里的元素和对应的参数是指向同一个值的
引用
!function(a) { arguments[0] = 100; console.log(a); //100 }(1); !function(a) { 'use strict'; arguments[0] = 100; console.log(a); //1 }(1);
但是:传的
参数
是对象
除外。arguments和形参共享传递。!function(a) { 'use strict'; console.log(a.x); //1 arguments[0].x = 100; console.log(a.x); //100 }({x: 1});
eval
变成了独立作用域
在正常模式下, 代码
eval("var x;")
会给上层函数(surrounding function)或者全局引入一个新的变量x
. 这意味着, 一般情况下, 在一个包含eval
调用的函数内所有没有引用到参数或者局部变量的名称都必须在运行时才能被映射到特定的定义 (因为eval
可能引入的新变量会覆盖它的外层变量). 在严格模式下eval
仅仅为被运行的代码创建变量, 所以eval
不会使得名称映射到外部变量或者其他局部变量:!function() { eval('var evalVal = 2;'); console.log(typeof evalVal); //number }(); !function() { 'use strict'; eval('var evalVal = 2;'); console.log(typeof evalVal); //undefined }();
- eval,arguments成为关键字,不能用作变量,函数名
- 不再支持
arguments.callee
正常模式下,
arguments.callee
指向当前正在执行的函数。这个作用很小:直接给执行函数命名就可以了!此外,arguments.callee
十分不利于优化,例如内联函数,因为arguments.callee
会依赖对非内联函数的引用。在严格模式下,arguments.callee
是一个不可删除属性,而且赋值和读取时都会抛出异常:"use strict"; var f = function() { return arguments.callee; }; f(); // 抛出类型错误
- this指向undefined
一般函数调用(不是对象的方法调用,也不使用apply/call/bind等修改this),this指向undefined,而不是全局对象
!function () { function fun() { return this; } console.log( fun() ); //Window }(); !function () { 'use strict'; function fun() { return this; } console.log( fun() ); //undefined }();
- 使用apply/call/bind时,this指向改变
使用apply/call/bind,当传入参数是null/undefined时,this指向null/undefined,而不是全局对象
!function () { function fun() { return this; } console.log( fun.apply(null) ); //Window console.log( fun.apply(undefined) ); //Window console.log( fun.call(null) ); //Window console.log( fun.call(undefined) ); //Window console.log( fun.bind(null)() ); //Window console.log( fun.bind(undefined)() ); //Window }(); !function () { 'use strict'; function fun() { return this; } console.log( fun.apply(null) ); //null console.log( fun.apply(undefined) ); //undefined console.log( fun.call(null) ); //null console.log( fun.call(undefined) ); //undefined console.log( fun.bind(null)() ); //null console.log( fun.bind(undefined)() ); //undefined }();
fun.caller
和fun.arguments
都是不可删除的属性而且在存值、取值时都会报错:
当一个叫
fun
的函数正在被调用的时候,fun.caller
是最后一个调用fun
的函数,而且fun.arguments
包含调用fun
时用的形参。这两个扩展接口对于“安全”JavaScript而言都是有问题的,因为他们允许“安全的”代码访问"专有"函数和他们的(通常是没有经过保护的)形参function restricted() { "use strict"; restricted.caller; // 抛出类型错误 restricted.arguments; // 抛出类型错误 } function privilegedInvoker() { return restricted(); } privilegedInvoker();
- arguments.caller是不可删除的属性而且在存值、取值时都会报错:
在一些旧时的ECMAScript实现中arguments.caller曾经是一个对象,里面存储的属性指向那个函数的变量。这是一个安全隐患,因为它通过函数抽象打破了本来被隐藏起来的保留值;它同时也是引起大量优化工作的原因。出于这些原因,现在的浏览器没有实现它。但是因为它这种历史遗留的功能,arguments.caller在严格模式下同样是一个不可被删除的属性,在赋值或者取值时会报错:
!function () { 'use strict'; var fun = function () { return arguments.callee; }; fun(); //TypeError }();
- 保留了一些关键字
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'; } // !!!
- 禁止了不在脚本或者函数层面上的函数声明
!function () {
"use strict";
if (true) {
function fun() {}
console.log( fun() ); //IE10报错。IE11、IE7~9、Chrome、FF不报错。
}
for (var i = 0; i < 5; i++) {
function fun2() {}
console.log( fun2() ); //IE10报错。IE11、IE7~9、Chrome、FF不报错。
}
function fun3() { // 合法
function fun4() {} //同样合法
}
}();