Javascript严格模式(Javascript Strict Mode)

原文地址: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与我对于这些测试方法的网上讨论)。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值