一:简单介绍
严格模式是在ES5中引入的,它可以视为JS的一个子集,在严格模式下,限制了JS的标准使用下一些行为。
- 严格模式消除了一些 JavaScript的静默错误,通过改变它们来抛出错误。
- 严格的模式修复了 JavaScript引擎难以执行优化的错误:有时候,严格模式代码可以比非严格模式的相同的代码运行得更快。
- 严格模式禁用了在ECMAScript的未来版本中可能会定义的一些语法。
但是需要注意的是:不支持严格模式的浏览器将会执行与支持严格模式的浏览器不同行为的严格模式代码。所以不要依靠严格模式,而是应当加强自己代码的鲁棒性
二:使用方法
严格模式可以应用于整个脚本或单个函数中。其中的脚本不仅仅包括了单个文件,还包括了dom中事件处理,eval(),Function(),以及window.setTimeOut()中的字符串。
1:为某个脚本使用严格模式
需要在所有代码前,声明
"use strict";
PS:注意,必须为"use strict";或者'use strict';,且必须带分号结束。
但这种使用方式在存在代码引用和合并的时候,会无法正常激活严格模式。因为,当一个声明了严格模式的脚本,被引入合并到一个新的未使用严格模式的脚本中的时候,由于声明并没有在所有代码前使用,从而导致严格模式声明失败。
为了保证严格模式的正常使用,一般的做法是用一个外部匿名函数将使用严格模式的脚本封装,然后执行。代码一般如下
(function () {
'use strict';
/**** 原有脚本代码 ****/
})()
但需要注意的是,这种方法,会将原有的代码封装到一个全局环境的函数中,原有脚本代码的作用域由全局变为了函数内部作用域,因此在使用的时候,务必注意。
PS:一个变通的方法是,将内部脚本需要全局的变量,直接声明为全局变量,即不用var或者let定义,比如a,不适用var a;或者let a;,而是直接 a = xxx
2:单函数使用严格模式
单函数使用严格模式,只需要在函数代码开头声明
"use strict";
即可
三:严格模式带来的具体差异
1:消除了静默错误,改为抛出错误
- 无法静默声明全局变量
全局变量必须显式声明,标准模式下,如果一个变量没有声明就赋值,默认是全局变量。严格模式禁止这种用法,全局变量必须显式声明。
"use strict";
v = 1; // 报错,v未声明
for(i = 0; i < 2; i++) { // 报错,i未声明
}
- 函数的参数必须命名唯一
在正常模式中,如果函数的参数中出现重名,则最后出现的重名参数替换之前出现的参数,但之前出现的参数依然可以通过arguments属性读取。在严格模式下,禁止出现这种情况,此时会跳出错误。
function sum(a, a, c){ // !!! 语法错误
"use strict";
return a + a + c; // 代码运行到这里会出错
}
- 对象的属性名必须唯一
严格模式要求一个对象内的所有属性名在对象内必须唯一。正常模式下重名属性是允许的,最后一个重名的属性决定其属性值。
"use strict";
var o = { p: 1, p: 2 }; // !!! 语法错误
- 禁止出现八进制数字数据
在ES标准中,并没有八进制数据,但现在的浏览器都支持以0开头作为八进制数据。严格模式下禁止了这种数据类型,但在ES6中,新引入了'0o'前缀来表示八进制数据
var a = 015; // 错误
var a = 0o10; // ES6: 八进制
- 任何在正常模式下引起静默失败的赋值操作 (给不可赋值的全局变量赋值,给不可写属性赋值, 给只读属性(getter-only)赋值赋值, 给不可扩展对象(non-extensible object)的新属性赋值) 都会抛出异常
"use strict";
NaN = 1; // 不可复制的全局变量
var o = {
get v() {
return this.v
}
};
Object.defineProperty(o, "v", { value: 1, writable: false }); // 不可写的属性
o.v = 2; // 报错
var o1 = {
get v() { return 1; } // 只读属性
};
o1.v = 2; // 报错
// 给不可扩展对象的新属性赋值
var fixed = {};
Object.preventExtensions(fixed);
fixed.newProp = "ohai"; // 抛出TypeError错误
- 试图删除不可删除的属性时会抛出异常
"use strict";
delete Object.prototype; // 抛出TypeError错误
- ECMAScript 6中的严格模式禁止设置primitive值的属性.
2:修复了JS的随意性,提升了优化能力
在标准模式下,JS的随意性,或者灵活性,是的很多变量,只有在运行时才能确切知道具体指向,这就使得变量名到内存的映射也只有到运行时才能完成。严格模式修复了大部分这种行为,使得所有的变量名在编译的时候,就已经可以一起进行优化,从而提升了执行速度。
- 禁止使用with
在标准模式中,使用with的时候,with代码块内的变量,只有在运行时,才能根据with引入的Obj是否存在相应的属性,来确定具体的指向。因此,严格模式禁止使用with
"use strict";
var x = 17;
with (obj) // !!! 语法错误
{
// 如果没有开启严格模式,with中的这个x会指向with上面的那个x,还是obj.x?
// 如果不运行代码,我们无法知道,因此,这种代码让引擎无法进行优化,速度也就会变慢。
x;
}
- eval不在为上层作用域引入新变量
在标准模式下,如果某个函数内部引入了eval代码。那么在函数内部,所有出现的名称(也就是所有的变量名)应当映射到的变量,除了引用到函数的参数,以及函数的内部变量,可以在编译的时候确定外,其他所有的名称只有在运行的时候,执行完eval代码后才能映射到相应的变量。因为有些变量可能是由eval代码引入到函数作用域的。
var x = 17;
var evalX = eval("'use strict'; var x = 42; x");
console.log(x === 17); // true 未能引入x,所以还是原来的x,但如果去掉 use strict,那么为false
console.log(evalX === 42); // true
但在严格格式下,eval不在为上层作用域引入新的局部变量和全局变量。所有的eval中出现的变量,只在eval的字符串代码块中有效。
另外如果判定eval是否为严格模式,也存在各种复杂的情况。
-- 如果直接调用eval(...)代码的代码块显式使用了严格格式,那么eval()也执行严格格式。如下面代码
function test () {
'use strict';
eval(...) // 无论eval中的字符串是否包含 use strict; 都会进入严格格式
}
-- 如果直接调用eval的代码块未使用严格模式,而是更上级别的代码块使用了严格模式,则eval代码按照标准模式执行,如下面代码
function test () {
'use strict';
f(){} // 无论eval中的字符串是否包含 use strict; 都会进入严格格式
}
-- 如果eval(...)的字符串中显式使用了'use strict'; 则必然进入严格模式
- 严格模式禁止删除声明变量
"use strict";
var x;
delete x; // !!! 语法错误
eval("var y; delete y;"); // !!! 语法错误
3:让eval和arguments变的更加简单
- 严格模式下,eval和arguments作为保留字
在严格模式下,不允许对eval和arguments进行赋值或者绑定,以下代码全部为错误语法
"use strict";
eval = 17;
arguments++;
++eval;
var obj = { set p(arguments) { } };
var eval;
try { } catch (arguments) { }
function x(eval) { }
function arguments() { }
var y = function eval() { };
var f = new Function("arguments", "'use strict'; return 17;");
经过测试,这里的不得对arguments的复制,指的是arguments这个变量,而不是指的其中的元素,比如
function test (a, b) {
'use strict';
a = 42;
arguments[1] = 17; // 可以通过语法检测,但并不会修改b的值
}
依然是允许的,也可以通过语法检测。
- 严格模式下,参数的值,不会随arguments对象的值的变化而修改。arguments对象的值的修改也不会影响同名函数。
在标准模式下,比如一个函数第一个参数为arg,那么在函数内部,修改arg的时候,也会同步修改arguments[0],反之亦然。但在严格模式下,两者进行了隔离。函数的 arguments 对象会保存函数被调用时的原始参数。arguments[i] 的值不会随与之相应的参数的值的改变而变化,同名参数的值也不会随与之相应的 arguments[i] 的值的改变而变化。 - 不再支持arguments. callee
4:更加安全的JS
- 对this的严格限制
在普通模式下,无论任何情况下,this都是一个对象。需要注意:this的指向不在函数定义的时候是确定不了的,只有函数执行的时候才能确定this到底指向谁,在标准模式下,只想调用函数的对象。但如果是箭头函数,则this为定义时上下文的this。
-使用new新建对象
function test () {
console.log(typeof this);
return this;
}
var t = new test()
console.log(t) // this 为新建的对象
-使用call,apply,bind传入this,如果传入的是数字,字符串,布尔值等,那么就会将这些基本数据的this转换为Number,String,Boolean对象类型。如果传入的是null和undefined,则为全局变量window,默认调用下,this也为window
function fun() {
console.log(typeof this)
return this;
}
console.log(fun()); // window 需要注意,单独的此类 fun(),等价于window.fun()。所以为window
console.log(fun.call(2)); // Number
console.log(fun.call('2018-01-01 10:00:00')); // String
console.log(fun.apply(null)); // window
console.log(fun.call(undefined)); // window
console.log(fun.bind(true)()); // Boolean
上面的这种模式既增加了转换为对象的对象的开销,又因为将全局对象window暴露出来造成安全性问题。
因此在严格模式下指定的this不再被封装为对象,而且如果没有指定this的话它值是undefined,上面的结果如下:
'use strict';
function fun() {
console.log(typeof this)
return this;
}
console.log(fun()); // undefined
console.log(fun.call(2)); // 2
console.log(fun.call('2018-01-01 10:00:00')); // '2018-01-01 10:00:00'
console.log(fun.apply(null)); // null
console.log(fun.call(undefined)); // undefined
console.log(fun.bind(true)()); // true
- 禁止对函数扩展,fun.caller和fun.arguments进行读取和复制
在普通模式下用这些扩展的话,当一个叫fun的函数正在被调用的时候,fun.caller是最后一个调用fun的函数,而且fun.arguments包含调用fun时用的形参。通过这些扩展,可以让不安全的用户操作到危险的属性。
因此在严格模式下,fun.caller和fun.arguments都是不可删除的属性而且在存值、取值时都会报错
5:对未来的兼容
- 增加了一些保留字
在严格模式中一部分字符变成了保留的关键字。这些字符包括implements, interface, let, package, private, protected, public, static和yield - 禁止不在脚本或者函数层面声明函数
所谓的脚本层面,指的是文件的全局作用域。而函数层面,指的是函数的直接作用域,这里并不包括了脚本中的块作用域,以及函数中嵌套的块作用域
"use strict";
if (true){
function f() { } // !!! 语法错误
f();
}
for (var i = 0; i < 5; i++){
function f2() { } // !!! 语法错误
f2();
}
function baz() { // 合法
function eit() { } // 同样合法
}