严格模式是 ECMAScript5
(ES5
)发布的语言新特性。使用严格模式可以限制 JavaScript 的一些语言特性,使用严格模式可以去除在书写代码时的一些“骚操作”(有些特性在严格模式下是不可用的),使代码更严谨整洁。
严格模式对正常的 JavaScript
语义做了一些更改:
使用严格模式也很简单,只要在 js 文件中写入 "use strict"
这一行文字即可。写了之后,该字符串所在的作用域中的代码就会遵循严格模式。
比如:
"use strict"
function aa(){
// ...
}
// ....
use strict
可以写在文件的许多位置,下面说一下在不同位置它起到的作用与作用范围。
function strict(a,b){
"use strict"; // 给 strict 开启严格模式
return a + b;
}
use strict
单独放在 {}
中,在这样的上下文中这么做是没有效果的。比如下面的代码,其实并没有开启严格模式:
{ "use strict" };
// some code ...
这是因为 {}
相当于一个作用域,上面相当于在一个作用域中使用严格模式,作用域外面的代码是不受约束的。因此可以看出,严格模式只对它所在的作用域中的代码有效(而且是它下方的代码)。
一个函数中的内容({}
里的)就是一个作用域,ES6
类里的内容也是一个作用域;for
循环中也是一个作用域。
首先看几个例子,在不使用严格模式下运行代码是什么结果,而使用了严格模式又是什么结果。
考虑下面代码,运行后会出现什么结果:
"use strict";
a = 123;
console.log(a);
运行后会发现报错(a is not defined
),这是因为 在严格模式下你必须声明变量,然后再赋值。
这样做的好处是,当你想使用一个局部变量时,却没有声明它,这时如果不使用严格模式,改变量默认会挂载到上层作用域中。有了严格模式可以帮助你检查这样的错误,同时也可以提高编码规范。
考虑下面的代码:
function loose(a,a){
return a + a;
}
console.log(loose(2,4));
结果可能出乎意料,并没有报错,而且输出的是 8。只是因为在正常模式下,最后一个重名参数名会掩盖之前的重名参数。因此这个函数其实只有一个形参 a
,4 才是它真正的实参。
在严格模式下这是会报错的。
arguments
是存储函数形参的类数组,还有一个特殊的函数: arguments.callee
,它可以用于引用该函数的函数体内当前正在执行的函数。arguments.callee()
相当于执行函数体本身,当你想对匿名函数进行递归调用时就可以使用 callee
函数。
比如下面的代码:
[1,2,3,4,5].map(function (n) {
// 使用 callee 实现递归
return !(n > 1) ? 1 : arguments.callee(n - 1) * n;
// [1, 2, 6, 24, 120]
});
而在严格模式下,arguments.callee
是不能使用的(会报错)。那又如何实现上面的递归呢?可以使用具名回调函数:
"use strict";
// 使用具名函数
var arr = [1, 2, 3, 4, 5].map(function fn(n) {
return !(n > 1) ? 1 : fn(n - 1) * n;
});
当然你也可以直接将 fn
函数直接写成一个独立的函数,然后传入 map
中即可:[1,2,3,4,5].map(fn);
。
考虑下面的代码:
function loose(a,b,c){
a = 100;
console.log(arguments);
}
loose("a","b");
打印后会发现,a 的值已经变成 100。在严格模式下函数形参是只读的,修改了形参的值并不对报错,当然值还是原来的值,并没有被你修改掉。如果你真的有这个需求,可以声明一个变量,然后将形参赋给改变量。或者使用 arguments[0] = 100
的方式去修改,在严格模式下通过 arguments 下标的方式是可以修改传入的值的。
考虑下面两个函数打印的 this
值:
function fn1(){
console.log(this);
}
function fn2(){
"use strict"; // 启用严格模式
console.log(this);
}
fn1(); fn2();
运行后会发现,fn2
的 this 是 undefined
。这是因为在严格模式下通过 this 传递给一个函数的值不会被强制转换为一个对象;一个开启严格模式的函数,指定的 this
不再被封装为对象,而且如果没有指定 this 的话它值是 undefined
,可以使用 call
, apply
或者 bind
方法来指定一个确定的 this
。
比如下面的代码会全部返回 true
。而如果是非严格模式,除了最后 obj
的那一个,其余都返回 false
。这是因为非严格模式下,this 会被包装成一个对象。
"use strict";
function fun() { return this; }
console.log(fun() === undefined);
console.log(fun.call(2) === 2);
console.log(fun.apply(null) === null);
console.log(fun.call(undefined) === undefined);
console.log(fun.bind(true)() === true);
"use strict";
var obj = {
// fn1 就在一个对象里,因此 this 指向 obj
fn1(){
return this;
}
}
console.log(obj.fn1() === obj); // true
箭头函数与普通函数略有不同,箭头函数不会创建自己的 this,它只会从自己的作用域链的上一层继承 this。而且箭头函数还有以下特点:
call()
apply()
或 bind()
方法调用一个函数时,只能传递参数(不能绑定 this),他们的第一个参数会被忽略。window
。arguments
。
比如下面的代码:
const fn = (a,b,c,d) =>
arguments;
// 会报错:arguments is not defined
console.log(fn(1,2,3,4));
因此,使用箭头函数可以接收上层普通函数的 arguments
参数。
function
fn(a,b,c){
let func = () => {
console.log(arguments);
}
func();
}
fn(1,2,3); // 1,2,3
new
一起用会抛出错误,箭头函数也没有 prototype
属性。yield
关键字通常不能在箭头函数中使用(除非是嵌套在允许使用的函数内)。因此,箭头函数不能用作函数生成器。使用严格模式会引起静默失败,静默就是有些操作是不能完成的,但运行代码不报错也没有任何效果。例如 Object.defineProperty
方法可以给对象的键设置一些属性,比如设置不可写属性:
var obj1 = {};
Object.defineProperty(obj1, "x", { value: 42, writable: false });
obj1.x = 9;
如果是非严格模式,运行代码并不会报错,而且 obj.x
的值也不能被改变。而如果开启了严格模式,就会出现错误。
类似的静默失败还有这么几个方法:
Object.preventExtensions(object)
给不可扩展对象的新属性赋值(例如:object.aa = 123);Object.defineProperties()
在一个对象上定义新的属性或修改现有属性的状态(可以一次修改多个属性);delete Object.prototype
;eval
或者 arguments
,严格模式下会报错;021
这种表示八进制(普通模式下表示八进制),可以使用 0o21
表示八进制;true.a = 1
、"aa".a = 1
、(123).b = "aa"
,这些都会报错在严格模式下。var a; delete a;
会报错;eval 函数可以将字符串解析成 js 代码然后执行,因此 eval 很强大,严格模式对 eval
函数做了一些限制。
eval 会将传入的字符串执行,然后将返回(或者赋值)的变量返回。而且 "use strict"
严格模式标志可以写进 eval 函数中执行。
比如下面的代码,如果不使用严格模式,变量 a 和 b 的值会相同。
var a = 123;
// var a = 12 然后将 a 赋给 b
var b = eval("var a = 12;a");
console.log(a,b); // 12 12
如果使用了严格模式(var b = eval("'use strict';var a = 12;a");
),则打印的变量 a 仍然是 123。
还有一种的情况:
strict2(eval,"Non-strict code.");
function strict2(f, str) {
"use strict";
// 没有直接调用 eval(...): 当且仅当 str 中的代码开启了严格模式时
// 才会在严格模式下运行
return f(str);
}
以上就是 JavaScript 的严格模式。