原文地址:http://javascriptweblog.wordpress.com/2011/05/03/javascript-strict-mode/
ECMAScript规范第五版介绍了严格模式。严格模式对Javascript制定了约束——目的是为了保护你免于遇到该语言中的各种坑。
在写这篇文章的同时,我写了38个测试方法,它包含了所有在ES5规范中定义的严格模式规则。你可以通过以下链接来检测你现在用的浏览器。
为了更好的理解这些规范,我会将每个测试的代码放在本文的结尾处供用户阅读。你也可以手动复制、粘贴这些代码到你的浏览器控制台中来测试。欢迎来我的github查看全部源代码。
Firefox4和IE10都已经支持严格模式,Chrome12也差不多已经全部支持。严格模式就在眼前——让我们开始进行深入探讨……
如何启用严格模式?
在javascript代码中的第一行添加"use strict",将会使所有代码都启用严格模式。
"use strict";
012; //Octal literal throws a SyntaxError in Strict Mode
另外,可以通过在方法内部的第一行添加"use strict",来实现对一个给定的方法执行严格模式。
012; //No Error (Strict Mode not enforced globally)
function foo() {
"use strict";
x=3; //Strict Mode will throw an error for implicit globals
}
foo(); //ReferenceError (Strict Mode is enforced for function foo)
@kangax指出"use strict"可以出现在任意的字符串描述范围内——大概是为了允许将来的指令可以并行执行。
一个方法会因为它的外部方法启用严格模式而受影响吗?
如果一个内部方法定义在一个声明了"use strict"的外部方法内,那么它也会启用严格模式。
var wrapper = function(fn) {
'use strict';
var deleteNonConfigurable = function () {
var obj = {};
Object.defineProperty(obj, "name", {
configurable: false
});
delete obj.name; //Will throw TypeError in Strict Mode
}
return deleteNonConfigurable;
}
wrapper()(); //TypeError (Strict Mode enforced)
然而需要注意的是,当严格方法(启用了严格模式的方法)调用了一个非严格方法时,该非严格方法不会启用严格模式。(因为非严格方法被当作参数传递或是通过call和apply调用)
var test = function(fn) {
'use strict';
fn();
}
var deleteNonConfigurable = function () {
var obj = {};
Object.defineProperty(obj, "name", {
configurable: false
});
delete obj.name; //will throw TypeError in Strict Mode
}
test(deleteNonConfigurable); //no error (Strict Mode not enforced)
为什么不能在浏览器控制台中启用严格模式?
当在火狐或其他浏览器控制台中执行代码时,开始的"use strict"(在方法体外)不起作用。这是因为大多数的控制台是通过将所有代码传递给eval来执行的,所以"use strict"就不是代码中的第一段描述了。一种解决方式是将代码放在一个以"use strict"开始的自执行方法中(就算如此,我发现在控制台中强制启用严格模式相当奇怪,尤其是在使用webkit内核的浏览器时。所以,最好是在一个页面中来测试代码)。
(function() {
"use strict";
var a;
var b;
function bar() {
x = 5; //Strict Mode will throw an error for implicit globals
}
bar(); //ReferenceError (Strict Mode is enforced)
})();
译者注:猎豹浏览器这样写也不会启用严格模式。FF直接在console中写"use strict"就可启用,但chrome不行。
如果浏览器不支持严格模式会发生什么?
啥都不会发生。不支持严格模式的Javascript引擎会忽略"use strict"这个字符串。这样就允许跨浏览器的使用严格模式语法,这是为了保证向前兼容,防止有一天某些浏览器仅仅支持严格模式。
严格模式有哪些规则?
严格模式规范定义的限制包括加载时的行为和执行时的行为。下面是一段简短的概述(每个规则的更多细节将会在下一章通过一个例子详细描述)。
语法错误
在很多情况下,严格模式将会阻止模棱两可或涉嫌误导的价码加载。八进制字符串、重复的属性名、delete的错误使用、尝试用eval和参数关键词做任何狡猾的事都会抛出一个SyntaxError的异常。使用with语句也会报相同的异常。(感谢@brendaneich解释为什么with被删掉)
this的值
在严格模式中,this的值不会被自动的变成一个对象。这一定是严格模式中最有趣的一部分,而且我相信这也会给开发者带来巨大的影响。最值得注意的是,当call或apply的第一个参数为null或undefined时,被引用方法中的this的值将不会被转换成全局对象。(译者注:非严格模式下会转化成window,严格模式下为null)
隐变量
没有多少人会反对这一点。隐变量的创造几乎都是错误的。在严格模式中,你将得到ReferenceError异常。它会让你明白的!
arguments.caller和arguments.callee
这些有用的方法在严格模式中将不受欢迎。这肯定是有争议的。很少使用的arguments方法和caller方法也被删掉了。
对象属性违法定义
在严格模式中,如果想修改一个属性,则该属性必须事先定义,否则将会抛出TypeError的异常。
测试
下面是我所有的严格模式测试的源代码。每一个测试集的上面都有一段来自ECMAScript规范的测试结果描述。这一版本的源代码是可以在控制台直接执行的——这意味着你可以拷贝并将它复制到控制台然后执行。就像我在本文开头提到的,同样的代码通过html模式来生成可视化的页面。我的github中有更完善的带有更多对象的源代码。我肯定会犯一些错误——随时让我知道!
(function() {
//TEST UTILS...
var HTML_MODE = 0;
var CONSOLE_MODE = 1;
var mode = CONSOLE_MODE;
var banner = document.getElementById('banner');
var currentTestWidget;
var testsPassed = 0;
var totalTests = 0;
window.console = window.console || {log:alert};
function testException(testName, code, expectedError) {
'use strict';
startTest(testName);
try {
expectedError == SyntaxError ? eval(code) : code();
finishTest(false);
} catch (e) {
(e instanceof expectedError) ? finishTest(true) : finishTest(false);
}
}
function testValue(testName, fn, expectedValue, options) {
'use strict';
options = options || {};
startTest(testName);
var result = (fn.apply(options.ctx, options.args || []) === expectedValue);
finishTest(result);
}
function startTest(testName) {
if (mode == CONSOLE_MODE) {
console.log("testing..." + testName);
} else {
this.currentWidget = document.createElement('DIV');
this.currentWidget.innerHTML = testName;
document.body.appendChild(this.currentWidget);
}
}
function finishTest(passed) {
totalTests++;
passed && testsPassed++;
var result = passed ? "passed" : "failed";
if (mode == CONSOLE_MODE) {
console.log(result);
} else {
this.currentWidget.className = result;
}
}
function startAll() {
if (mode == HTML_MODE) {
banner.innerHTML += [":", browser.browserName, browser.fullVersion].join(' ');
}
}
function finishAll() {
var result = ["","(", testsPassed, "out of", totalTests, "tests passed", ")"].join(' ');
if (mode == HTML_MODE) {
banner.innerHTML += result;
} else if (mode == CONSOLE_MODE) {
console.log(result);
}
}
//THE TESTS...
startAll();
// A conforming implementation, when processing strict mode code, may not extend the
//syntax of NumericLiteral (7.8.3) to include OctalIntegerLiteral as described in B.1.1.
testException("no octal literals", '012', SyntaxError);
// A conforming implementation, when processing strict mode code (see 10.1.1), may not
//extend the syntax of EscapeSequence to include OctalEscapeSequence as described in B.1.2.
testException("no octal escape sequence", '"\\012"', SyntaxError);
// Assignment to an undeclared identifier or otherwise unresolvable reference does not
//create a property in the global object. When a simple assignment occurs within strict
//mode code, its LeftHandSide must not evaluate to an unresolvable Reference. If it does
//a ReferenceError exception is thrown (8.7.2).
testException(
"no implied globals",
function () {'use strict'; x = 3;},
ReferenceError
);
//The LeftHandSide also may not be a reference to a data property with the attribute
//value {[[Writable]]:false}, to an accessor property with the attribute value
//{[[Set]]:undefined}, nor to a non-existent property of an object whose [[Extensible]]
//internal property has the value false. In these cases a TypeError exception is thrown
//(11.13.1).
var assignToNonWritable = function () {
'use strict';
var obj = {};
Object.defineProperty(obj, "name", {
writable: false
});
obj.name = "octopus";
}
testException("can't assign to non-writable properties", assignToNonWritable, TypeError);
var assignWhenSetterUndefined = function () {
'use strict';
var obj = {};
Object.defineProperty(obj, "name", {
set: undefined
});
obj.name = "cuttlefish";
}
testException("can't assign when setter undefined", assignWhenSetterUndefined, TypeError);
var assignToNonExtensible = function () {
'use strict';
var obj = {};
Object.preventExtensions(obj);
obj.name = "jellyfish";
}
testException("can't assign to non extensible", assignToNonExtensible, TypeError);
//The identifier eval or arguments may not appear as the LeftHandSideExpression of an
//Assignment operator (11.13) or of a PostfixExpression (11.13) or as the UnaryExpression
//operated upon by a Prefix Increment (11.4.4) or a Prefix Decrement (11.4.5) operator.
testException("can't assign to eval", "eval=3", SyntaxError);
testException("can't assign to arguments", "arguments=3", SyntaxError);
testException("can't postfix eval", "eval++", SyntaxError);
testException("can't postfix arguments", "arguments++", SyntaxError);
testException("can't prefix eval", "++eval", SyntaxError);
testException("can't prefix arguments", "++arguments", SyntaxError);
//Arguments objects for strict mode functions define non-configurable accessor properties
//named "caller" and "callee" which throw a TypeError exception on access (10.6).
testException(
"can't use arguments.caller",
function () {'use strict'; arguments.caller;},
TypeError
);
testException(
"can't use arguments.callee",
function () {'use strict'; arguments.callee},
TypeError
);
//Arguments objects for strict mode functions do not dynamically share their array indexed
//property values with the corresponding formal parameter bindings of their functions. (10.6).
var assignToArguments = function (x) {
'use strict';
arguments[0] = 3;
return x;
}
testValue(
"arguments not bound to formal params",
assignToArguments,
5,
{args: [5]}
);
//For strict mode functions, if an arguments object is created the binding of the local
//identifier arguments to the arguments object is immutable and hence may not be the
//target of an assignment expression. (10.5).
var assignToFormalParams = function (x) {
'use strict';
x = 3;
return arguments[0];
}
testValue(
"arguments object is immutable",
assignToFormalParams,
5,
{args: [5]}
);
//It is a SyntaxError if strict mode code contains an ObjectLiteral with more than one
//definition of any data property (11.1.5).
testException("no duplicate properties", "({a:1, a:2})", SyntaxError);
//It is a SyntaxError if the Identifier "eval" or the Identifier "arguments occurs as the
//Identifier in a PropertySetParameterList of a PropertyAssignment that is contained in
//strict code or if its FunctionBody is strict code (11.1.5).
testException(
"eval not allowed in propertySetParameterList",
"({set a(eval){ }})",
SyntaxError
);
testException(
"arguments not allowed in propertySetParameterList",
"({set a(arguments){ }})",
SyntaxError
);
//Strict mode eval code cannot instantiate variables or functions in the variable environment
//of the caller to eval. Instead, a new variable environment is created and that environment
//is used for declaration binding instantiation for the eval code (10.4.2).
testException(
"eval cannot create var in calling context",
function () {'use strict'; eval('var a = 99'); a},
ReferenceError
);
//If this is evaluated within strict mode code, then the this value is not coerced to an object.
//A this value of null or undefined is not converted to the global object and primitive values
//are not converted to wrapper objects. The this value passed via a function call (including
//calls made using Function.prototype.apply and Function.prototype.call) do not coerce the
//passed this value to an object (10.4.3, 11.1.1, 15.3.4.3, 15.3.4.4).
var getThis = function () {
'use strict';
return this;
}
testValue(
"this is not coerced",
getThis,
4,
{ctx: 4}
);
testValue(
"no global coercion for null",
getThis,
null,
{ctx: null}
);
//When a delete operator occurs within strict mode code, a SyntaxError is thrown if its
//UnaryExpression is a direct reference to a variable, function argument, or function name
//(11.4.1).
testException("can't delete variable directly", "var a = 3; delete a", SyntaxError);
testException("can't delete argument", "function(a) {delete a}", SyntaxError);
testException("can't delete function by name", "function fn() {}; delete fn", SyntaxError);
//When a delete operator occurs within strict mode code, a TypeError is thrown if the
//property to be deleted has the attribute { [[Configurable]]:false } (11.4.1).
var deleteNonConfigurable = function () {
'use strict';
var obj = {};
Object.defineProperty(obj, "name", {
configurable: false
});
delete obj.name;
}
testException("error when deleting non configurable", deleteNonConfigurable, TypeError);
//It is a SyntaxError if a VariableDeclaration or VariableDeclarationNoIn occurs within
//strict code and its Identifier is eval or arguments (12.2.1).
testException("can't use eval as var name", "var eval;", SyntaxError);
testException("can't use arguments as var name", "var arguments;", SyntaxError);
//Strict mode code may not include a WithStatement. The occurrence of a WithStatement
//in such a context is an SyntaxError (12.10).
testException("can't use with", "with (Math) {round(sqrt(56.67))}", SyntaxError);
//It is a SyntaxError if a TryStatement with a Catch occurs within strict code and the
//Identifier of the Catch production is eval or arguments (12.14.1)
testException("can't use eval as catch id", "try {'cake'} catch(eval) {}", SyntaxError);
testException("can't use arguments as catch id", "try {'cake'} catch(arguments) {}", SyntaxError);
//It is a SyntaxError if the identifier eval or arguments appears within a
//FormalParameterList of a strict mode FunctionDeclaration or FunctionExpression (13.1)
testException("can't use eval as formal param", "function(eval) {}", SyntaxError);
testException("can't use arguments as formal param", "function(arguments) {}", SyntaxError);
//A strict mode function may not have two or more formal parameters that have the same
//name. An attempt to create such a function using a FunctionDeclaration, FunctionExpression,
//or Function constructor is a SyntaxError (13.1, 15.3.2).
testException("can't duplicate formal params", "function(me, me, me) {}", SyntaxError);
//An implementation may not associate special meanings within strict mode functions to
//properties named caller or arguments of function instances. ECMAScript code may not
//create or modify properties with these names on function objects that correspond to
//strict mode functions (13.2).
testException(
"can't use caller obj of function",
function () {'use strict'; (function () {}).caller},
TypeError
);
//It is a SyntaxError to use within strict mode code the identifiers eval or arguments as
//the Identifier of a FunctionDeclaration or FunctionExpression or as a formal parameter
//name (13.1). Attempting to dynamically define such a strict mode function using the
//Function constructor (15.3.2) will throw a SyntaxError exception.
testException("can't use eval as function name", "function eval() {}", SyntaxError);
testException("can't use arguments as function name", "function arguments() {}", SyntaxError);
var functionConstructorStr = "new Function('eval', 'use strict')";
testException("can't use eval as param name via constructor", functionConstructorStr, SyntaxError);
finishAll();
})();
总结
限制性的使用语言是否给开发者带来好处是值得讨论的——但是让我们以后再讨论这一问题。严格模式是一个介于全语言的的改变(这将无法做到向后兼容)和什么都不做(这将提高那些希望淘汰javascript中令人震惊的部分的人的呼声)的折中方案。如果没有其他的事了,严格模式将会是那些迷恋语言之间细微差别的人的美食!享受它吧!
进一步阅读
ECMA-262 第五版本:The Strict Mode of ECMAScript
Asen Bozhilov: Strict tester ——一个有想法的完全的严格模式测试集
Juriy Zaytsev (aka “kangax”): ECMAScript 5 compatibility table — Strict mode support ——另外一个很好的源代码,它是由@kangax写的,一个主要的兼容性参考资料的一部分(感谢@kangax与我对于这些测试方法的网上讨论)。