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/