Strict mode – JavaScript

ECMAScript 5的严格模式是Javascript中的一种限制性更强的变种方式。严格模式不是一个子集:它在语义上与正常代码有着特意的差异。不支持严格模式的浏览器与同支持严格模式的浏览器行为上也不一样, 所以不要在未经严格模式特性测试情况下使用严格模式。严格模式可以与非严格模式共存,所以脚本可以逐渐的选择性加入严格模式。严格模式在语义上与正常的JavaSript有一些变化。 首先,严格模式会将JavaScript陷阱直接变成明显的错误。其次,严格模式修正了一些引擎难以优化的错误:同样的代码有些时候严格模式会比非严格模式下更快。 第三,严格模式禁用了一些有可能在未来版本中定义的语法。如果你想让你的JavaScript代码在严格模板下运行,可以参考转换成严格模式。
开启严格模式
严格模式可以应用到整个script标签或某个别函数中。不要在封闭大括弧( {} )内这样做;在这样的上下文中这么做是没有效果的。 例如,eval 代码,Function 代码,事件处理属性,传入 setTimeout方法的字符串 等等包括整个脚本中开启严格模式都会如预期一样工作。
为某个script标签开启严格模式
为整个script标签开启严格模式, 需要在所有语句之前放一个特定语句 “use strict”; (或 ‘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."; }

严格模式有哪些不同
严格模式同时改变了语法及运行时行为。变化通常分为这几类:将问题直接转化为错误(如语法错误或运行时错误), 简化了如何为给定名称的特定变量计算,简化了 eval 以及 arguments, 将写”安全“JavaScript的步骤变得更简单,以及改变了预测未来ECMAScript行为的方式。
将拼写错转成异常
在严格模式下, 先前被接受的拼写错误将会被认为是异常. JavaScript被设计为能使新人开发者更易于上手, 所以有时候会给本来错误操作赋予新的不报错误的语义(non-error semantics). 有时候这可以解决当前的问题, 但有时候却会给以后留下更大的问题. 严格模式则把这些失误当成错误, 以便可以发现并立即将其改正.
首先,严格模式下无法再意外创建全局变量。在普通的JavaScript里面给一个拼写错误的变量名赋值会使全局对象新增一个属性并继续“工作”(仅管后面可能出错:在现在的JavaScript中有可能)。严格模式中意外创建全局变量被抛出错误替代:

"use strict";
mistypedVariable = 17; // 抛出 ReferenceError

其次, 严格模式会使引起静默失败(silently fail,注:不报错也没有任何效果)的赋值操作抛出异常. 例如, NaN 是一个不可写的全局变量. 在正常模式下, 给 NaN 赋值不会产生任何作用; 开发者也不会受到任何错误反馈. 但在严格模式下, 给 NaN 赋值会抛出一个异常. 任何在正常模式下引起静默失败的赋值操作 (给不可写属性赋值, 给只读属性(getter-only)赋值赋值, 给不可扩展对象(non-extensible object)的新属性赋值) 都会抛出异常:

"use strict";
// 给不可写属性赋值
var obj1 = {};
Object.defineProperty(obj1, "x", { value: 42, writable: false });
obj1.x = 9; // throws a TypeError
// 给只读属性赋值
var obj2 = { get x() { return 17; } };
obj2.x = 5; // throws a TypeError
// 给不可扩展对象的新属性赋值
var fixed = {};
Object.preventExtensions(fixed);
fixed.newProp = "ohai"; // throws a TypeError

第三, 在严格模式下, 试图删除不可删除的属性时会抛出异常(之前这种操作不会产生任何效果):

"use strict";
delete Object.prototype; // throws a TypeError

第四, 严格模式要求一个对象内的所有属性名在对象内必须唯一. 正常模式下重名属性是允许的, 重名的最后一个属性决定其属性值. 因为只有最后一个属性有效, 当修改代码要改变属性值而却不是修改的最后一个重名属性的时候, 这种重复就成了bug的温床. 在严格模式下, 重名属性被认为是语法错误:

"use strict";
var o = { p: 1, p: 2 }; // !!! 语法错误

第五, 严格模式要求函数的参数名唯一. 在正常模式下, 最后一个重名参数名会掩盖之前的重名参数. 之前的参数仍然可以通过 arguments[i] 来访问, 还不是完全无法访问. 然而, 这种隐藏豪无意义或者并不是故意为之 (比如, 就是一个打字错误), 所以在严格模式下重名参数被认为是语法错误:

function sum(a, a, c) // !!! 语法错误
{
"use strict";
return a + b + c; // 如果代码运行到这里,将会有一个bug
}

第六, 严格模式禁止八进制数字语法. ECMAScript并不包含八进制语法, 但所有的浏览器都支持这种以零(0)开头的八进制语法: 0644 === 420 还有 “\045” === “%”. 有些新手开发者认为数字的前导零没有语法意义, 所以他们会用作对齐措施 — 但其实这会改变数字的意义! 八进制语法很少有用并且可能会错误使用, 所以严格模式下八进制语法会引起语法错误:

"use strict";
var sum = 015 + // !!! 语法错误
197 +
142;

简化变量的使用
严格模式简化了代码中变量名字映射到变量定义的方式. Many compiler optimizations rely on the ability to say that variable X is stored in that location: 这对全面优化JavaScript代码至关重要. JavaScript有些情况会使得代码中名字到变量定义的基本映射只在运行时才产生. 严格模式移除了大多数这种情况的发生, 所以编译器可以更好的优化严格模式的代码.
首先, 严格模式禁用 with. with 所引起的问题是块内的任何名称可以映射(map)到with传进来的对象的属性, 也可以映射到包围这个块的作用域内的变量(甚至是全局变量), 这一切都是在运行时决定的: 在代码运行之前是无法得知的. 严格模式下, 使用 with 会引起语法错误, 所以就不会存在 with 块内的变量在运行是才决定引用到哪里的情况了:

"use strict";
var x = 17;
with (obj) // !!! 语法错误
{
// 如果没有开启严格模式,with中的这个x会指向with上面的那个x,还是obj.x?如果不运行代码,我们无法知道,因此,这种代码让引擎无法进行优化,速度也就会很慢了.
x;
}

一种取代 with 的简单方法是,将目标对象赋给一个短命名变量,然后访问这个变量上的相应属性.
第二, 严格模式下的 eval 不在为上层范围(surrounding scope,注:包围eval代码块的范围)引入新变量. 在正常模式下, 代码 eval(“var x;”) 会给上层函数(surrounding function)或者全局引入一个新的变量 x . 这意味着, 一般情况下, 在一个包含 eval 调用的函数内所有没有引用到参数或者局部变量的名称都必须在运行时才能被映射到特定的定义 (因为 eval 可能引入的新变量会覆盖它的外层变量). 在严格模式下 eval 仅仅为被运行的代码创建变量, 所以 eval 不会影响到名称映射到外部变量或者其他局部变量:

var x = 17;
var evalX = eval("'use strict'; var x = 42; x");
assert(x === 17);
assert(evalX === 42);

相应的, 如果函数 eval 在被严格模式下的eval(…)以表达式的形式调用时, 其代码会被当做严格模式下的代码执行. 当然也可以在代码中显式开启严格模式, 但这样做并不是必须的.

function strict1(str)
{
"use strict";
return eval(str); // str中包含的代码肯定会在严格模式下运行
}
function strict2(f, str)
{
"use strict";
return f(str); // f是个处于严格模式下的函数,或者f为eval且str中的适当位置处添加了"use strict"的情况下,str中的代码才会在严格模式下运行
}
function nonstrict(str)
{
return eval(str); // 只有在str中的适当位置处添加了"use strict",str中的代码才会在严格模式下运行
}
strict1("'Strict mode code!'");
strict1("'use strict'; 'Strict mode code!'");
strict2(eval, "'Non-strict code.'");
strict2(eval, "'use strict'; 'Strict mode code!'");
nonstrict("'Non-strict code.'");
nonstrict("'use strict'; 'Strict mode code!'");
Thus names in strict mode eval code behave identically to names in strict mode code not being evaluated as the result of eval.

第三, 严格模式禁止删除plain names. delete name 在严格模式下会引起语法错误:

"use strict";
eval("var x; delete x;"); // !!! 语法错误

让eval和arguments变的简单
严格模式让arguments和eval少了一些奇怪的行为。两者在通常的代码中都包含了很多奇怪的行为: eval to add or remove bindings and to change binding values, and arguments by its indexed properties aliasing named arguments. Strict mode makes great strides toward treating eval and arguments as keywords, although full fixes will not come until a future edition of ECMAScript.
首先, 名称 eval 和 arguments 不能被绑定(be bound)或赋值 in language syntax. 以下的所有尝试将一起语法错误:

"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;");

第二, strict mode code doesn’t alias properties of arguments objects created within it. 在正常模式下, 在第一个参数为 arg 的函数中, 设置 arg 的值会同时设置 arguments[0], 反之亦然(除非没有参数或者 arguments[0] 被删除了). 严格模式下, 在函数中的 arguments 对象存储其被调用时候的原始值. arguments[i] 不在追踪相应名称参数的值的变化, 名称参数也不在追踪相应的 arguments[i].

function f(a)
{
"use strict";
a = 42;
return [a, arguments[0]];
}
var pair = f(17);
assert(pair[0] === 42);
assert(pair[1] === 17);

第三点,不再支持arguments.callee属性.在非严格模式中,arguments.callee指向当前正在执行的函数. 这个用处很弱: 直接用名字就可以了! 此外, arguments.callee substantially hinders optimizations like inlining functions, because it must be made possible to provide a reference to the un-inlined function if arguments.callee is accessed. 严格模式下函数中的 arguments.callee 是一个不可删除属性, 被设置或读取都会跑出异常:

"use strict";
var f = function() { return arguments.callee; };
f(); // throws a TypeError

“Securing” JavaScript
Strict mode makes it easier to write “secure” JavaScript. Some websites now provide ways for users to write JavaScript which will be run by the website on behalf of other users. JavaScript in browsers can access the user’s private information, so such JavaScript must be partially transformed before it is run, to censor access to forbidden functionality. JavaScript’s flexibility makes it effectively impossible to do this without many runtime checks. Certain language functions are so pervasive that performing runtime checks has considerable performance cost. A few strict mode tweaks, plus requiring that user-submitted JavaScript be strict mode code and that it be invoked in a certain manner, substantially reduce the need for those runtime checks.
First, the value passed as this to a function in strict mode isn’t boxed into an object. For a normal function, this is always an object: the provided object if called with an object-valued this; the value, boxed, if called with a Boolean, string, or number this; or the global object if called with an undefined or null this. (Use call, apply, or bind to specify a particular this.) Automatic boxing is a performance cost, but exposing the global object in browsers is a security hazard, because the global object provides access to functionality “secure” JavaScript environments must restrict. Thus for a strict mode function, the specified this is used unchanged:

"use strict";
function fun() { return this; }
assert(fun() === undefined);
assert(fun.call(2) === 2);
assert(fun.apply(null) === null);
assert(fun.call(undefined) === undefined);
assert(fun.bind(true)() === true);

Second, in strict mode it’s no longer possible to “walk” the JavaScript stack via commonly-implemented extensions to ECMAScript. In normal code with these extensions, when a function fun is in the middle of being called, fun.caller is the function that most recently called fun, and fun.arguments is the arguments for that invocation of fun. Both extensions are problematic for “secure” JavaScript, because they allow “secured” code to access “privileged” functions and their (potentially unsecured) arguments. If fun is in strict mode, both fun.caller and fun.arguments are non-deletable properties which throw when set or retrieved:

function restricted()
{
"use strict";
restricted.caller; // throws a TypeError
restricted.arguments; // throws a TypeError
}
function privilegedInvoker()
{
return restricted();
}
privilegedInvoker();

Third, arguments for strict mode functions no longer provide access to the corresponding function call’s variables. In some old ECMAScript implementations arguments.caller was an object whose properties aliased variables in that function. This is a security hazard because it breaks the ability to hide privileged values via function abstraction; it also precludes most optimizations. For these reasons no recent browsers implement it. Yet because of its historical functionality, arguments.caller for a strict mode function is also a non-deletable property which throws when set or retrieved:

"use strict";
function fun(a, b)
{
"use strict";
var v = 12;
return arguments.caller; // throws a TypeError
}
fun(1, 2); // doesn't expose v (or a or b)

Paving the way for future ECMAScript versions
Future ECMAScript versions will likely introduce new syntax, and strict mode in ECMAScript 5 applies some restrictions to ease the transition. It will be easier to make some changes if the foundations of those changes are prohibited in strict mode.
First, in strict mode a short list of identifiers become reserved keywords. These words are implements, interface, let, package, private, protected, public, static, and yield. In strict mode, then, you can’t name or use variables or arguments with these names.

function package(protected) // !!!
{
"use strict";
var implements; // !!!
interface: // !!!
while (true)
{
break interface; // !!!
}
function private() { } // !!!
}
function fun(static) { 'use strict'; } // !!!

Two Mozilla-specific caveats: First, if your code is JavaScript 1.7 or greater (you’re chrome code, or you’ve used the right, won’t be able to use let/yield as identifiers. Second, while ES5 unconditionally reserves the words class, enum, export, extends, import, and super, before Firefox 5 Mozilla reserved them only in strict mode.
Second, strict mode prohibits function statements not at the top level of a script or function. In normal code in browsers, function statements are permitted “everywhere”. This is not part of ES5 (or even ES3)! It’s an extension with incompatible semantics in different browsers. Future ECMAScript editions will hopefully specify new semantics for function statements not at the top level of a script or function. Prohibiting such function statements in strict mode “clears the deck” for specification in a future ECMAScript release:

"use strict";
if (true)
{
function f() { } // !!! syntax error
f();
}
for (var i = 0; i < 5; i++)
{
function f2() { } // !!! syntax error
f2();
}
function baz() // kosher
{
function eit() { } // also kosher
}

This prohibition isn’t strict mode proper, because such function statements are an extension of basic ES5. But it is the recommendation of the ECMAScript committee, and browsers will implement it.
浏览器的严格模式
浏览器并未真的实现严格模式,所以请不要盲目的依赖严格模式。 Strict mode changes semantics. Relying on those changes will cause mistakes and errors in browsers which don’t implement strict mode. Exercise caution in using strict mode, and back up reliance on strict mode with feature tests that check whether relevant parts of strict mode are implemented. Finally, make sure to test your code in browsers that do and don’t support strict mode. If you test only in browsers that don’t support strict mode, you’re very likely to have problems in browsers that do, and vice versa.