ECMAScript 5 最早引入了 严格模式(strict mode) 的概念。

通过严格模式,可以在函数内部选择进行较为严格的全局或局部的错误条件检测。使用严格模式的好处是可以提前知道代码中存在的错误,及时捕获一些可能代指编程错误的 ECMAScript 行为。

ECMAScript 的下一个版本将以严格模式为基础制定。

支持严格模式的浏览器包括:IE10+、Firefox4+、Safari5.1+、Chrome

1. 选择使用

要选择进入严格模式,可以使用严格模式的编译指示,实际上就是一个不会赋给任何变量的字符串:

  1. //=> webstrom 中输入 us [tab]
  2. "use strict";

这种语法可以向后兼容那些不支持严格模式的 JS 引擎。支持的会启动严格模式,不支持的会忽略这个编译指示,当作一个未赋值的字符串字面量。

  • 如果在全局作用域中给出这个编译指示,则整个脚本都将使用严格模式。

  • 只对当前 JS 文件中的代码生效,下一个 JS 文件需要开启严格模式,还是需要再次编写编译指示

  • 如果把带有编译指示的脚本引入到其他文件中,则该文件中的 JS 代码也会处于严格模式。

真实项目中,我们一般会把所有 JS 合并压缩为一个后再导入页面中,此时只要有一个 JS 文件在全局作用域使用了严格模式,那么所有代码都会使用严格模式。所以需要在编写自己代码的时候,不在全局中使用,而是在函数中开启。

只在函数中打开严格模式:

  1. function doSomething() {
  2. "use strict";
  3. }
  4. ~function () {
  5. "use strict";
  6. //=> 你的代码
  7. };

2. 严格模式的区别

  1. 在严格模式下不支持使用 arguments.callee / arguments.callee.caller 以及函数的 caller

  2. 在严格模式下,arguments 和形参没有映射机制

  3. 在严格模式下不允许给一个对象字面量设置重复属性名的 obj = {n:10,n:20}

  4. 在严格模式下,函数执行,如果没有明确指定执行主体(函数前面没有点),不再像非严格模式下一样,统一交给 window,而是让 this 指向 undefined,代表没有执行主体。严格模式下,有执行主体 this 就指向谁,没有执行主体,this 就是 undefined

3. 变量

在严格模式下,什么时候创建变量,怎么创建变量都是有限制的。

首先,不允许意外创建全局变量。

  1. //=> 未声明变量
  2. //=> 非严格模式下:创建全局变量
  3. //=> 严格模式下:抛出 ReferenceError
  4. message = 'Hello world';

在严格模式在,如果给一个未声明的变量赋值,就会抛出 ReferenceError。

其次,不能对变量调用 delete`` 操作符。

  1. //=> 删除变量
  2. /=> 非严格模式下:静默失败
  3. //=> 严格模式下:抛出 ReferenceError
  4. var color = 'red';
  5. delete color;

严格模式下,不能使用保留字作为变量名,会导致语法错误。

4. 对象

在严格模式下操作对象比在非严格模式下更容易导致错误。一般来说,非严格模式下会静默失败的情形,在严格模式下都会抛出错误。因此,在开发中使用严格模式会加大早发现错误的可能性。

以下情形下操作对象属性会导致错误:

  • 为只读属性赋值会抛出 TypeError

  • 对不可配置的(noconfigurable)性使用 delete 操作符会抛出 TypeError

  • 对不可扩展的(noextensible)对象添加属性会抛出 TypeError

另外一个限制是,在使用字面量时,属性名必须是唯一的。

  1. //=> 重名属性
  2. //=> 非严格模式下:后面的覆盖前面的
  3. //=> 严格模式下:抛出语法错误
  4. var person = {
  5. name: 'aa',
  6. name: 'bb'
  7. }

5. 函数

首先,严格模式下要求命名函数的参数必须唯一。

  1. //=> 重名参数
  2. //=> 非严格模式下:只能访问第二个参数
  3. //=> 严格模式下:抛出语法错误
  4. function sum(num, num) {
  5. }

其次,在严格模式下,arguments 对象的行为也有所不同。在非严格模式下,修改命名参数的值也会反映到 arguments 对象中,而严格模式下,这两个值是完全独立的,不存在映射机制

  1. //=> 修改命名参数的值
  2. //=> 非严格模式下:修改会反映到 arguments 中
  3. //=> 严格模式下:修改不会反映到 arguments 中
  4. function showValue(value) {
  5. value = 'foo';
  6. console.log(value);
  7. console.log(arguments[0]);
  8. }
  9. showValue('hi');
  10. //=> 非严格模式下:'foo','foo'
  11. //=> 严格模式下:'foo', 'hi'

另外一个变化是,淘汰了 arguments.callee** 和 arguments.caller**。在非严格模式下,这两个属性一个引用函数本身,另一个引用调用函数。而在严格模式下,访问它们会抛出 TypeError。同样,访问函数的 caller 属性,也会抛出错误。

  1. //=> 访问 arguments.callee
  2. //=> 非严格模式下:没有问题
  3. //=> 严格模式下:抛出 TypeError
  4. function factorial(num){
  5. if(num <=1) {
  6. return 1;
  7. } else {
  8. return num * arguments.callee(num-1)
  9. }
  10. }

最后,只能在脚本的顶级和在函数内部声明函数,也就是不能在 if 语句中声明函数,会导致语法错误。

5. eval

饱受诟病的 eval() 函数在严格模式下也得到了提升。最大的变化就是它在包含上下文中不再创建变量或函数。

  1. //=> 使用 eval 创建变量
  2. //=> 非严格模式下:输出 10
  3. //=> 严格模式下:抛出 ReferenceError
  4. function doSomething() {
  5. eval('var x=10');
  6. console.log(x);
  7. }

可以在 eval 中声明变量或函数,但是这些变量或函数只能在被求值的特殊作用域中有效,随后就将被销毁。

6. 抑制 this

JavaScript 中一个最大的安全问题,也是最容易让人迷茫的地方,就是在某些情况下如何抑制 this 的值。

在严格模式下,函数执行,如果没有明确指定执行主体(函数前面没有点),不再像非严格模式下一样,统一交给 window,而是让 this 指向 undefined,代表没有执行主体。严格模式下,有执行主体 this 就指向谁,没有执行主体,this 就是 undefined

在非严格模式下,使用函数的 callapply 方法时,传入 nullundefined 值会被转换为全局变量。
而严格模式下,函数的 this 值始终是指定的值,无论指定的是什么值,使用 call 不指定值是,值为 undefined

  1. //=> 访问属性
  2. //=> 非严格模式下:访问全局属性
  3. //=> 严格模式下:抛出错误,因为 this 的值为 null,访问 null 的属性会抛出错误
  4. var color = 'red';
  5. function displayColor() {
  6. console.log(this.color);
  7. }
  8. displayColor.call(null);

7. 其他变化

首先,抛弃了 with 语句。

其次,去掉了 JavaScript 中的八进制字面量。以 0 开头的八进制字面量过去经常导致很多错误。

最后,parseInt 的行为,八进制字面量在严格模式下会被当做以 0 开头的十进制字面量来处理。