# 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過時了嗎?
- ==個人筆記==