3.2 严格模式与 ECMAScript 6
ECMAScript 5 引入严格模式来清理语言,在文件或者函数的第一行放入下面的内容就可以开启严格模式:
'use strict';
严格模式引入了三种破坏性的改变:
语法改变:一些之前合法的语法在严格模式下面是不允许的。例如:
- 禁止
with
语句。它允许使用者添加任何对象到变量作用域链,这会减缓程序的执行速度,并且很难指出某个变量指向哪里。 - 删除一个独立的标识符(一个变量,而不是一个属性)是不允许的。
- 函数只能在作用域的顶层声明。
- 更多的保留字: implements interface let package private protected public static yield 。
- 禁止
更多种类的错误。例如:
- 给一个未声明的变量赋值会抛出
ReferenceError
。在非严格模式下,这样干就会创建一个全局变量。 - 修改只读的属性(比如字符串的长度属性)会抛出
TypeError
。在非严格模式下,不会产生任何效果。
- 给一个未声明的变量赋值会抛出
不同的语义:在严格模式下,一些语法结构表现得不一样。例如:
arguments
不再随着当前参数值的改变而改变。- 在非方法的函数中
this
是undefined
。在非严格模式下,它指向全局对象(window
),也就是说如果调用一个构造器的时候没有使用new
,就会创建一些全局变量。
严格模式是一个很好地说明了版本化是棘手的:即便能够制作一个干净版本的 JavaScript ,也很难被大家接受。主要原因是破坏了一些现存的代码,降低了执行速度,并且加入到文件中也很麻烦(更不用说交互的命令行)。我喜欢严格模式这种想法,但是基本不使用。
3.2.1 支持松散(非严格)模式
一个 JavaScript 意味着我们不能放弃松散模式:此模式将会继续存在(例如在 HTML 属性中)。因此,我们不能基于严格模式来构建 ECMAScript 6 ,必须同时在严格模式和非严格模式(又称为松散模式)下添加特性。否则,严格模式就会成为一个不同的语言版本,就退回了版本化的方式。很不幸,有两个特性很难引入松散模式: let
声明和块级函数声明。让我们看看为什么很难引入和如何引入。
3.2.2 松散模式中的 let
声明
let
使你能够声明块级变量。这很难被引入到松散模式,因为 let
仅在严格模式下是保留字。也就是说,下面两条语句在 ES5 的松散模式下是合法的:
var let = [];
let[x] = 'abc';
在 ECMASCript 6 的严格模式下,第一行就会抛出异常。因为使用了 let
作为变量名。然后第二行会被解析为一个 let
变量声明(使用解构)。
在 ECMAScript 6 的松散模式下,第一行不会抛出异常,但是第二行依然被解析为一个 let
声明。这种使用 let
的方式在 web 上是极少见的,因此 ES6 可以直接这样来解析。 ES5 松散模式下的其他 let
声明的书写方式不会被误解:
let foo = 123;
let {x,y} = computeCoordinates();
3.2.3 松散模式下的块级函数声明
ECMAScript 5 严格模式禁止在块中声明函数,在松散模式下,规范却允许这么做,但是没说这样会发生什么。因此,很多 JavaScript 实现都支持块级函数声明,但是处理方式是不一样的。
ECMAScript 6 想要块中的函数声明本地化(即该函数的作用域救在该块中)。作为 ES5 严格模式的扩展,这是没问题的,但是破坏了一些松散模式的代码。因此, ES6 为浏览器提供了“ web 遗留的兼容语义”,允许块中的函数声明在函数作用域中存在。
3.2.4 其它关键字
标识符 yield
和 static
仅在 ES5 的严格模式下是保留字。 ECMAScript 6 使用上下文相关的语法规则来使它们在松散模式下起作用:
- 在松散模式下,
yield
仅在生成器函数中是保留字。 static
现在仅用于类字面量中,类字面中默认就是严格的(见下文)。
3.2.5 隐式的严格模式
在 ECMAScript 6 中,模块体和类体默认就是严格模式的–没必要使用 use strict
标记。考虑到我们所有的代码都将会位于模块中, ECMAScript 6 有效地将整个语言升级到了严格模式。
其它结构体(比如箭头函数和生成器函数)本来也应该隐式地为严格模式。但是考虑到通常情况下这些结构都很小,在非严格模式下使用它们就会造成代码中两种模式的碎片化切换。类,尤其是模块一般是足够大的,这样一来就可以忽略碎片化的代码片段问题了。
3.2.6 无法修复的东西
一个 JavaScript 的缺陷就是无法修复已有的怪异行为,尤其是下面这两个。
第一个, typeof null
应该返回字符串 null
而不是 object
。但是修正这个就会破坏已有的代码。另一方面,给新种类的操作数添加新的操作结果是没问题的,因为当前的 JavaScript 引擎对于一些宿主对象已经会返回自定义的值。 ECMAScript 6 的 Symbol 就是一个例子:
> typeof Symbol.iterator
'symbol'
第二个,全局对象(浏览器中的 window
对象)不应该在变量作用域链。但是现在修正这个也太晚了。但是至少,在模块中不会处于全局作用域下,并且 let
永远不会创建全局对象属性,甚至在全局作用域下使用也不会。