Javascript的严格模式
ECMAScript5定义了一种严格模式的语法,它禁用了一些不够安全的用法,同时,对一些原本静默失败的异常情况会抛出错误。使用严格模式可以帮助我们避免因为失误写出不安全的代码,我们应该尽量使用它。
启用严格模式
使用“use strict”; 或者 ‘use strict’; 可以指定特定的代码使用严格模式,如我们可以指定对单个函数启用严格模式。如果我们在全局使用严格模式,那么会影响到所有的代码,这可能导致一些旧代码执行出错,所以我们应该把严格模式的代码控制在我们所掌握的范围内。
要注意的是,“use strict”; 应该放在代码的顶端才能生效,放在中间是不生效的。请看下面的例子:
// OK, non-strict mode (function foo() { arguments = 10; "use strict"; })(); // SyntaxError, strict-mode (function foo() { "use strict"; arguments = 10; })();
当然,这个顶端指的是可执行代码之前,倒不必非是第一行代码:
// also strict mode "use restrict"; "use strict"; a = 10; // ReferenceError
严格模式的作用域
严格模式的作用域延伸到其所有内部代码,定义了严格模式的代码,其内部嵌套的代码都处于严格模式中:
// define strict mode in the global context, // i.e. for the whole program "use strict"; (function foo() { // strictness is "inherited" from // the global context eval = 10; // SyntaxError (function bar() { // the same - from the global context arguments = 10; // SyntaxError })(); })();
需要明确一点是,严格模式作用于词法上下文(静态上下文)而非运行时环境,即在定义的时候其作用域就已经确定了,而非运行时才确定,这一点和闭包一样:
// globally use a non-strict mode var foo = (function () { "use strict"; return function () { alert(this); }; })(); function bar() { alert(this); } // for both functions, a caller is the global context // but "foo" is evaluated in the strict mode foo(); // undefined // meanwhile "bar" is not bar(); // object
另外提及一点,使用Function构造函数创造的函数不会继承定义时的环境的严格模式:
"use strict"; var f = Function("eval", "arguments", " \ eval = 10; arguments = 20; \ with ({a: 30}) { \ alert(a + eval + arguments); \ }" ); f(); // OK, 60
除非把 “use strict”; 放在函数定义中:
// non-strict globally var f = Function("eval", "'use strict'; alert(eval);"); // SyntaxError
严格模式下的代码要求和限制
我们看看在严格模式下有哪些代码的要求和限制
为未来预留的关键字
这些关键字是为未来预留的,所以不能用于变量名和函数名中:implements, interface, let, package, private, protected, public, static, yield 。
"use strict"; var let = 10; // SyntaxError
不支持八进制字面量
在非严格模式中,数字第一位是0会被解析成八进制数字:
var x = 010; // octal number print(x); // 8 - in octal radix
这样的做法仅仅是为了向后兼容,但在严格模式下,这样的写法不被支持了,这么写将会报错:
"use strict"; var x = 010; // SyntaxError
另外提一点,在ES3中使用 parseInt(‘010′) 会默认转成八进制数字,不过这在ES5中已经改为转换成十进制数字了,不管有没有严格模式。
给未声明的变量赋值
众所周知,给未声明的变量赋值会自动创建一个全局作用域的变量,这是一种非常不安全的行为:
// non-strict mode (function foo() { // local vars var x = y = 20; })(); // unfortunately, "y" wasn't local // for "foo" function alert(y); // 20 alert(x); // "x" is not defined
但在严格模式下,这么做将会报错:
"use strict"; a = 10; // ReferenceError var x = y = 20; // also a ReferenceError
eval 和 arguments
在严格模式下,eval和arguments被当作关键词。它们不能用作变量名、函数名、函数的参数名,不能给它们赋值,不能进行自增、自减操作:
"use strict"; // SyntaxError in both cases var arguments; var eval; // also SyntaxError function eval() {} var foo = function arguments() {}; // SyntaxError function foo(eval, arguments) {} (function (x) { alert(arguments[0]); // 30 arguments = 40; // TypeError })(30); // SyntaxError ++eval; arguments--; try { throw Error("..."); } catch (arguments) {} // SyntaxError, the same for "eval" name
它们可以作为对象的属性名,不过不能作为函数的属性名
"use strict"; // OK var foo = { eval: 10, arguments: 20 }; // OK foo.eval = 10; foo.arguments = 20;
"use strict"; function foo() { alert(foo.arguments); // SyntaxError alert(foo.caller); // SyntaxError } foo();
arguments和函数参数之间的绑定将会断开,即更改一个不会影响到另一个
"use strict"; (function foo(x) { alert(arguments[0]); // 10 arguments[0] = 20; alert(x); // 10, but not 20 x = 30; alert(arguments[0]); // 20, but not 30 })(10);
eval执行在一个沙盒环境中,在其中声明的变量不会影响到全局的作用域,在eval结束后,沙盒环境也相应地消失。
"use strict"; eval("var x = 10; alert(x);"); // 10 alert(x); // "x" is not defined
不过我们可以通过 indirect eval 来让eval创建全局变量。
"use strict"; ("indirect", eval)("var x = 10; alert(x);"); // 10 alert(x); // 10
callee 和 caller
在严格模式下,callee和caller都被禁止访问。我们无法使用arguments.callee来获取匿名函数了
"use strict"; (function foo(bar) { if (!bar) { arguments.callee(true); // SyntaxError foo(true); // OK } })();
这是处于安全性的考虑,如果没有这样的限制,被调用函数有权限修改调用方的值,比如:
// non-strict mode function foo(x, y) { alert(x); // 10 bar(); alert(x); // 100 } function bar() { console.dir(bar.caller.arguments); // 10, 20 bar.caller.arguments[0] = 100; } foo(10, 20);
禁止访问arguments.callee后会引发一些不便,比如在非全局作用域下,用Function创建函数,且这个函数存在递归调用的时候:
(function () { // outer name is not available, // regardless strictness var foo = Function("alert(foo);'"); foo(); // "foo" is not defined (no such name in the global context) // error in strict mode for arguments.callee Function("'use strict; alert(arguments.callee);'")(); // TypeError // OK in non-strict for arguments.callee Function("alert(arguments.callee);'")(); // OK, function })();
有一些方法可以绕过去,这里就不展开了,可以见http://dmitrysoshnikov.com/ecmascript/es5-chapter-2-strict-mode/#codecalleecode-and-codecallercode-restrictions。
重复命名
重复命名将会报错,包括属性名和参数名
"use strict"; // SyntaxError var foo = { x: 10, x: 10 }; // SyntaxError function foo(x, x) {}
get和set方法在严格或非严格的模式下,都不能重名
// strict or non-strict mode // SyntaxError var foo = { get x() {}, get x() {} }; // the same with setters // SyntaxError var bar = { set y(value) {}, set y(value) {} }; // SyntaxError var foo = { x: 10, get x() {} };
delete操作
实际上,变量、函数参数、函数都是无法被删除的,除非是在eval环境中,不过无法删除并不会报错。而在严格模式下,删除变量、函数参数、函数都会报错,包括在eval环境中删除也会报错。
"use strict"; var foo = {}; function bar(x) { delete x; // SyntaxError } bar(10); // SyntaxError delete foo; // SyntaxError delete bar; // SyntaxError Object.defineProperty(foo, "baz", { value: 10, configurable: false }); // but when delete a // property, then TypeError delete foo.baz; // TypeError // SyntaxError eval("var x = 10; delete x;"); // in non-strict is OK
with语句
在严格模式下,不允许使用with语句
"use strict"; // SyntaxError with ({a: 10}) { alert(a); }
this值
在严格模式中,this值不再自动转换成一个对象, null和undefined的this值不再转换成全局对象,基本类型的值不再转换成包装对象,通过 Function.prototype.apply 和 Function.prototype.call 传入的this值不再转换成对象:
"use strict"; // undefined "this" value, // but not the global object function foo() { alert(this); // undefined } foo(); // undefined // "this" is a primitive Number.prototype.test = function () { alert(typeof this); // number }; 1..test(); // number foo.call(null); // null foo.apply(undefined); // undefined
this被设置为undefined可以避免忘记使用new 来调用构造函数
// non-strict function A(x) { this.x = x; } var a = A(10); // forget "new" keyword // as a result "a" is undefined, // because exactly this value is returned // implicitly from the A function alert(a); // undefined // and again created "x" property // of the global object, because "this" // is coerced to global object in the // non-strict in such case alert(x); // 10
在严格模式下会抛出异常
"use strict"; function A(x) { this.x = x; } // forget "new" keyword, // error, because undefined.x = 10 var a = A(10); var b = new A(10); // OK
总结
可以看到,严格模式限制了Javascript现有的一些有风险的做法,减少了我们出错的几率。同时一些将来会废弃的用法,在严格模式中也禁用了,所以启用严格模式也能避免产生一些历史遗留代码。IE10+才支持严格模式,在低版本下会被忽略,而firefox、chrome和safari浏览器在比较早的版本就支持了,包括移动版。
参考资料
http://dmitrysoshnikov.com/ecmascript/es5-chapter-2-strict-mode/