# 3. 一个JavaScript:在 ECMAScript 6 中避免版本化
> 本章地址:https://exploringjs.com/es6/ch_one-javascript.html
给一门语言添加新特性的最好方式是什么?本章描述了 ECMAScript 6 使用的方式。它被称作 \*一个 JavaScript \*,因为避免了版本化。
## 3.1 版本化
原则上,语言的一个新版本是一个清理的机会,可以清理过时的特性或者改变特性的工作方式。这意味着新的代码在语言的旧的实现中无法工作,老的代码在新的实现中无法工作。每段代码都和特定的语言版本关联。针对两个不同语言版本写两种不同代码是很常见的。
首先,你可以使用一种“用所有还是什么都不用”的方式:如果一个代码库需要使用新的语言版本,就必须彻底升级。Python 在从 Python 2 升级到 Python 3 的时候就是这样的。这样的话有个问题,将已有的代码库一次性全部升级可能是做不到的,尤其是代码库很大的时候。而且,这种方式对于 web 来说是不可行的,因为总是有老代码,而且 JavaScript 引擎会自动升级。
第二,你可以让一个代码库包含在多个语言版本都可运行的代码,通过根据版本切换代码的方式。你可以通过一个专用的[互联网媒体类型](http://en.wikipedia.org/wiki/Internet_media_type)标记 ECMAScript 6 的代码。这样的媒体类型可以和一个 HTTP 头关联在一起:
```
Content-Type: application/ecmascript;version=6
```
也可以和`<script>`元素中的`type`属性关联在一起(`type`[默认值](http://www.w3.org/TR/html5/scripting-1.html#attr-script-type)是`text/javascript`):
```html
<script type="application/ecmascript;version=6">
···
</script>
```
这是在代码之外指定版本。另一种方案是在代码内指定版本。例如,将下面的代码放在 JavaScript 文件的第一行:
~~~js
use version 6;
~~~
两种标记的方法都很容易产生问题:外部版本标记法很脆弱,容易丢失;内部版本标记法又会使代码显得杂乱。
一个更根本的问题是,针对不同的语言版本,要维护不同的执行引擎。这就引起了一些问题:
* 引擎变得臃肿,因为要实现所有版本的语义。对于分析语言的工具也带来了同样的问题(比如类型检测, JSLint )。
* 开发者需要记住版本之间的不同点。
* 代码变得更加难以重构,因为在移动代码的时候需要考虑语言版本的问题。
因此,应该避免版本化,尤其是 JavaScript 和 web 。
### 3.1.1 非版本化的升级
但是我们如何解决版本化的问题?通过一直向后兼容。这意味着我们必须放弃一些野心,比如清理 JavaScript :我们不能引入破坏性改变。向后兼容意味着不要移除特性,也不要修改特性。原则是:“不要破坏 web ”。
然而,我们可以添加新的特性,并且使已有的特性更加强大。
因此,对于新的引擎不需要版本化管理了,因为仍然可以运行老的代码。 David Herman 称这种避免版本化的方法为[`一个 JavaScript ( One JavaScript(1JS) [1] )`](http://exploringjs.com/es6/ch_one-javascript.html#one-js_1),因为它使 JavaScript 避免分成几种不同的版本或模式。正如我们将要看到的,由于严格模式,使得 1JS 甚至移除了一些已有的分裂。
一个 JavaScript ( One JavaScript )不是说你必须完全放弃语言清理。你可以引入新的干净的特性,而不是清理掉已有的特性。其中一个例子就是`let`,它声明了块级范围的变量,是`var`的一个升级版本。它并不取代`var`,而是作为高级可选项一直和`var`并存。
某一天,甚至可能清除掉没人使用的特性。一些 ES6 特性是在调查过 web 上的 JavaScript 代码才设计的。举两个例子:
* `let`声明很难添加到非严格模式,因为`let`在这种模式下是保留字。使用`let`关键字看起来像是合法 ES5 代码的形式是:
```js
let[x] = arr;
```
经研究得出,在 web 上没人以这种方式在非严格模式下使用变量`let`。这使得 TC39 将`let`添加进了严格模式。在本章后面讲述了这是如何做的。
* 函数声明确实偶尔会在非严格模式下的代码块中出现,这就是为什么 ES6 规范描述了 web 浏览器可以采用的措施来确保这样的代码不会被破坏。后面详细讲解([3.2.3 松散模式下的块级函数声明](#))。
## 3.2 严格模式与 ECMAScript 6
ECMAScript 5 引入 [Strict mode](http://speakingjs.com/es5/ch07.html#strict_mode)来清理语言,在文件或者函数的第一行放入下面的内容就可以开启严格模式:
```js
'use strict';
```
严格模式引入了三种破坏性的改变:
* 语法改变:一些之前合法的语法在严格模式下面是不允许的。例如:
* 禁止`with`语句。它允许使用者添加任何对象到变量作用域链,这会减缓程序的执行速度,并且很难指出某个变量指向哪里。
* 删除一个独立的标识符(一个变量,而不是一个属性)是不允许的。
* 函数只能在作用域的顶层声明。
* 更多的保留字: implements interface let package private protected public static yield 。
* 更多种类的错误。例如:
* 给一个未声明的变量赋值会抛出`ReferenceError`。在非严格模式下,这样干就会创建一个全局变量。
* 修改只读的属性(比如字符串的长度属性)会抛出`TypeError`。在非严格模式下,不会产生任何效果。
* 不同的语义:在严格模式下,一些语法结构表现得不一样。例如:
* `arguments`不再随着当前参数值的改变而改变。
* 在非方法的函数中`this`是`undefined`。在非严格模式下,它指向全局对象(`window`),也就是说如果调用一个构造器的时候没有使用`new`,就会创建一些全局变量。
严格模式是一个很好地说明了版本化是棘手的:即便能够制作一个干净版本的 JavaScript ,也很难被大家接受。主要原因是破坏了一些现存的代码,降低了执行速度,并且加入到文件中也很麻烦(更不用说交互的命令行)。我喜欢严格模式这种想法,但却很少使用它。
### 3.2.1 支持松散( sloppy 非严格)模式
一个 JavaScript (One JavaScript) 意味着我们不能放弃松散模式:此模式将会继续存在(例如在 HTML 属性中)。因此,我们不能基于严格模式来构建 ECMAScript 6 ,必须同时在严格模式和非严格模式(又称为松散模式)下添加特性。否则,严格模式就会成为一个不同的语言版本,就退回了版本化的方式。很不幸,有两个特性很难引入松散模式:`let`声明和块级函数声明。让我们看看为什么很难引入和如何引入。
### 3.2.2 松散模式中的`let`声明
`let`使你能够声明块级变量。这很难被引入到松散模式,因为`let`仅在严格模式下是保留字。也就是说,下面两条语句在 ES5 的松散模式下是合法的:
```js
var let = [];
let[x] = 'abc';
```
在 ECMASCript 6 的严格模式下,第一行就会抛出异常。因为使用了`let`作为变量名。然后第二行会被解析为一个`let`变量声明(使用解构)。
在 ECMAScript 6 的松散模式下,第一行不会抛出异常,但是第二行依然被解析为一个`let`声明。这种使用`let`的方式在 web 上是极少见的,因此 ES6 可以直接这样来解析。 ES5 松散模式下的其他`let`声明的书写方式不会被误解:
```js
let foo = 123;
let {x,y} = computeCoordinates();
```
### 3.2.3 松散模式下的块级函数声明
ECMAScript 5 严格模式禁止在块中声明函数,在松散模式下,规范却允许这么做,但是没说这样会发生什么。因此,很多 JavaScript 实现都支持块级函数声明,但是处理方式是不一样的。
ECMAScript 6 想要块中的函数声明本地化(即该函数的作用域救在该块中)。作为 ES5 严格模式的扩展,这是没问题的,但是破坏了一些松散模式的代码。因此, ES6 为浏览器提供了“[web 遗留的兼容语义](http://www.ecma-international.org/ecma-262/6.0/#sec-block-level-function-declarations-web-legacy-compatibility-semantics)”,允许块中的函数声明在函数作用域中存在。
### 3.2.4 其它关键字
标识符`yield`和`static`仅在 ES5 的严格模式下是保留字。 ECMAScript 6 使用上下文相关的语法规则来使它们在松散模式下起作用:
* 在松散模式下,`yield`仅在生成器函数中是保留字。
* `static`现在仅用于类字面量中,类字面中默认就是严格的(见下文)。
### 3.2.5 **隐式的严格模式**
在 ECMAScript 6 中,模块体和类体 默认就是严格模式的–没必要使用`use strict`标记。考虑到我们所有的代码都将会位于模块中, ECMAScript 6 有效地将整个语言升级到了严格模式。
其它结构体(比如箭头函数和生成器函数)本来也应该隐式地为严格模式。但是考虑到通常情况下这些结构都很小,在非严格模式下使用它们就会造成代码中两种模式的碎片化切换。类,尤其是模块一般是足够大的,这样一来就可以忽略碎片化的代码片段问题了。
> 那么需要我们去留心 严格模式 下的一些约束,会发生什么变化。
### 3.2.6 无法修复的东西
一个 JavaScript 的缺陷(The downside of One JavaScript)就是无法修复已有的怪异行为,尤其是下面这两个。
第一个,`typeof null`应该返回字符串`null`而不是`object`。但是修正这个就会破坏已有的代码。另一方面,给新种类的操作数添加新的操作结果是没问题的,因为当前的 JavaScript 引擎对于一些宿主对象已经会返回自定义的值。 ECMAScript 6 的 Symbol 就是一个例子:
```js
> typeof Symbol.iterator
'symbol'
```
第二个,全局对象(浏览器中的`window`对象)不应该在变量作用域链。但是现在修正这个也太晚了。但是至少,在模块中不会处于全局作用域下,并且`let`永远不会创建全局对象属性,甚至在全局作用域下使用也不会。
```
let letStr = 'letStr';
console.log(window.letStr) // undefined
var varStr = 'varStr';
console.log(window.varStr) // “varStr”
```
## 3.3 ES6中的突破性变化(Breaking changes)
ECMAScript 6确实引入了一些微小的突破性变化(你可能不会遇到)。它们列在两个附件(annexes)中:
- [附件D:2015年电子手册中可能对兼容性产生影响的更正和澄清](http://www.ecma-international.org/ecma-262/6.0/#sec-corrections-and-clarifications-in-ecmascript-2015-with-possible-compatibility-impact)
- [附件E:引入与以前版本不兼容的添加和更改](http://www.ecma-international.org/ecma-262/6.0/#sec-additions-and-changes-that-introduce-incompatibilities-with-prior-editions)
## 3.4 总结
一个 JavaScript 意思就是使 ECMAScript 6 完全地向后兼容,很高兴这获得了成功。尤其是模块隐式就是严格模式的(这样一来我们大部分的代码都会处于严格模式下)。
在短期内,对于制定 ES6 规范和引擎实现来说,给严格模式和松散模式添加 ES6 的语法结构会耗费更多的精力。从长远来看,规范和引擎将会受益于语言不分叉(更少的膨胀等等)。开发人员会立即从一个 JavaScript 中获得好处,因为开始使用 ECMAScript 6 变得更加容易。
## 3.5 深入阅读
[1] 原始的 1 JS 提案(警告:已过时): “[ES6 doesn’t need opt-in](http://esdiscuss.org/topic/es6-doesn-t-need-opt-in)”,作者 David Herman 。
- 关于本书
- 目录简介
- 关于这本书你需要知道的
- 序
- 前言
- I 背景
- 1. About ECMAScript 6 (ES6)
- 2. 常见问题:ECMAScript 6
- 3. 一个JavaScript:在 ECMAScript 6 中避免版本化
- 4. 核心ES6特性
- II 数据
- 5. New number and Math features
- 6. 新的字符串特性
- 7. Symbol
- 8. Template literals
- 第9章 变量与作用域
- 第10章 解构
- 第11章 参数处理
- III 模块化
- 12. ECMAScript 6中的可调用实体
- 13. 箭头函数
- 14. 除了类之外的新OOP特性
- 15. 类
- 16. 模块
- IV 集合
- 17. The for-of loop
- 18. New Array features
- 19. Maps and Sets
- 20. 类型化数组
- 21. 可迭代对象和迭代器
- 22. 生成器( Generator )
- V 标准库
- 23. 新的正则表达式特性
- 24. 异步编程 (基础知识)
- 25. 异步编程的Promise
- VI 杂项
- 26. Unicode in ES6
- 27. 尾部调用优化
- 28 用 Proxy 实现元编程
- 29. Coding style tips for ECMAScript 6
- 30. 概述ES6中的新内容
- 注释
- ES5过时了吗?
- ==个人笔记==