以下內容是我在學習和研究ES6時,對ES6的特性、重點和注意事項的提取、精練和總結,可以做為ES6特性的字典;在本文中,不但詳細嚴謹地羅列了ES6各特性的具體規則,對于一些較難理解的特性也給出了其內部的運行機制,方便大家深入理解;
1. let和const的特性
- let用來聲明變量;const用來聲明常量;
- 塊級作用域;
- 不會變量提升;
- 不允許重復聲明;
- 在全局作用域內用let、const、class聲明的量不會成為全局對象的屬性;
- 在塊級作用內任何地方用let或const聲明的變量,在該塊級作用域開始處就已經存在,但不可獲取,只能在聲明之后才能獲??;
備注:
這個特性說明:在實現機制上,let和const在塊級作用域內還是存在變量提升的,編譯器只是從語法規則上模仿了沒有變量提升的特性;
2. 能聲明變量或常量的關鍵字
var、function、let、const、import、class
3. 代碼塊
代碼塊不存在于表達式中;
應用示例:
let x;
{x} = {x:34}; // 提示語法錯誤: SyntaxError: syntax error
這段代碼會被提示語法錯誤;因為解析器認為{x}
是個代碼塊;
如果想讓解析器認為{x} = {x:34}
是變量解構表達式,則只需要在{x} = {x:34}
外加個圓括號即可,因為({x} = {x:34})
會被認為是表達式,而代碼塊是不會存在于表達式中的;如下:
let x;
({x} = {x:34});
4. Null傳導運算符
Null傳導運算符為標識符?.操作
表示:如果標識符的值為null或undefined,則返回undefined,并且不執行后面的操作;
(此特性應該是借鑒的swift語言中的可選鏈特性;)
Null傳導運算符有四種用法,如下:
obj?.prop // 讀取對象屬性
obj?.[expr] // 讀取對象屬性
func?.(...args) // 函數或對象方法的調用
new C?.(...args) // 構造函數的調用
5. 屬性特性enumerable影響的幾個操作
目前,有四個操作會忽略屬性特性enumerable為false的屬性:
- for...in循環:只遍歷對象自身的和繼承的可枚舉的屬性;
- Object.keys():返回對象自身的所有可枚舉的屬性的鍵名;
- JSON.stringify():只串行化對象自身的可枚舉的屬性;
- Object.assign(): 忽略enumerable為false的屬性,只拷貝對象自身的可枚舉的屬性;
6. 模板字符串
- 模板字符串是增強版的字符串,用反引號`標識;可以通過${表達式}在模板字符串中嵌入變量或者表達式;
- 模板字符中的所有空格和換行都會被保留;
7. 標簽模板
標簽模板是函數結合模板字符串的一種調用形式;即標簽模板是一種函數的調用;標簽指代函數名字,模板指代模板字符串;
標簽模板的格式如下:
函數名 模板字符串
標簽模板會自動把模板字符串拆分,然后把各部分以參數的形式傳給函數,具體的拆分規則和傳參規則如下:
模板字符串的拆分規則:
- 如果模板字符串中有n個模板插值,則會以模板插值
${表達式}
為節點把模板字符串拆分成n+1個不含插值的字符串;- 如果模板插值在模板字符串中的最前端或者最后端,則拆分后的 第1個 或者 最后一個 字符串為不包含任何字符的空字符串;
- 如果模板字符串不包含模板插值,則不對模板字符串進行拆分';
模板字符串的傳參規則:
- 把被拆分后的n+1個字符串(不包含模板插值)按照在模板字符中的次序裝進數組中,然后把該數組傳為第1個參數傳給標簽函數;
- 模板中第m個插值的值會作為標簽函數的第m+1個參數傳給標簽函數;
8. 數組
- 支持擴展運算符
...
;
格式為:...數組
作用:把數組展開為用逗號分隔的該數組中所有元素的序列;它就像rest參數操作符的逆運算;如果數組為空,則不產生任何內容;- ES6規定,對數組進行的任何的操作,都不能將空位忽略,必須將數組中的空位將會被轉為undefined;
9. 對象
- 對象支持屬性簡寫語法;
- 在對象字面量中支持用表達式定義屬性名,格式為
{[表達式]:屬性值}
,屬性的名為把表達式的值被轉成字符串類型后的值;- (本條是ES2017的新特性)支持擴展運算符
...
,格式為:{...對象}
;規則如下:
假設:x是個對象;
則有:表達式{...x}
會取出對象x的所有可遍歷屬性,并將取出的所有屬性及屬性值拷貝到當前對象字面量中;- 支持Null傳導運算符;
10. 屬性的簡寫語法規則
在ES6中,對象的屬性支持簡寫,具體規則如下:
假設:x是 變量 或者 常量 或者 函數;
則有:{x}
相當于{'x':x}
;
11. 變量的解構賦值
- 允許模式不完全匹配,即支持不完全解構;
- 對于沒有匹配值的變量的值為undefined;
- 可以給模式中的變量設置默認值,默認值生效的條件是:該變量沒有配置的值,或者配置的值全等于===undefind;默認值可以是表達式,且該表達式是惰性求值的,即:只有在需要設置該默認值時,才會求值該表達式;
- 數組解構賦值的機制:根據模式中變量的位置對變量賦值;
- 對象解構賦值的機制:根據模式中的屬性名對該屬性相對應的變量賦值;
- 對象的解構模式支持屬性的簡寫語法;
- 可以對數組應用對象解構,因為數組本質上是對象;(詳情請參考《JavaScript的發現與理解》)
- 當解構字符串時,字符串會被轉換成類似數組的對象,該對象有length屬性,所以可以對字符串應用數組解構;
- 當被解構的數據是非對象類型時,會先把該數據轉換成相應的基本包裝對象,然后再對其進行解構;
- 變量解析也可以應用于函數的參數;
12. 函數
- 參數可以設置默認值;
- 當函數的部分或者全部參數帶有默認值時,函數的任何參數都不能有同名參數;
- 參數的默認值是惰性求值的,且每次需要默認值時都會重新計算;
- 當函數的參數是解構時,在調用該函數時,傳入的參數必須使所有的解構參數成功解構,否則將報錯;通過給解構參數設置能成功解構的默認值可以防止函數在調用時因傳解構失敗而報錯;
- 函數對象有個
length
屬性,該屬性表示的準確意義是:函數預期傳入的參數個數;所以,length
屬性的值為函數的不帶默認值的參數的個數;- 如果函數帶有包含默認值的參數,那么函數在被調用時,會為所有的參數會形成一個單獨的作用域;該作用域會在所有參數被初始化后消失;如果函數的任何參數都不包含默認值,則在函數在被調用時不會為參數生成單獨的作用域;
- 如果函數的任何參數都不包含默認值,則函數的參數是在函數內默認用var聲明的;如果函數帶有包含默認值的參數,則函數的參數是在被用let聲明的;(此結論是經過測試研究推理得出的,沒有得到權威的承認);
- 支持rest參數(剩余參數),且rest參數必須是最后一個參數;
- 在ES2016中,只要函數參數使用了默認值、解構賦值、或者擴展運算符,那么函數內部就不能顯式設定為嚴格模式,否則會報錯;
- 函數對象的
name
屬性保存的是函數最初設置的名字;
在ES6中:對于匿名函數,在其第1次賦值給的變量之前,其name
屬性的值為空字符串"",在其第1次賦值給的變量之后,其name
屬性的值為該匿名函數第1次賦值給的變量的名字;
在ES5及之前中:對于匿名函數,其name
屬性的值一直為空字符串"";
Function構造函數返回的函數實例的name
屬性的值為anonymous;
bind()返回的函數的name
屬性值是;bound 原name;- 支持尾調用優化;
13. 箭頭函數
- 箭頭函數會綁定其this的值為:該箭頭函數被定義處所在作用域中的this的值;即:箭頭函數中的this的值 是 定義該箭頭函數處所在作用域中的this的值;
- 不能用作構造函數;
- 箭頭函數中沒有arguments對象;
- 箭頭函數中不能使用yield關鍵字;
14. Set和Map
Set:類似數組,但成員值都是唯一的,不會重復,并且沒有索引;
WeakSet:特殊的Set;成員只能是對象類型,并且對成員的引用是弱引用;當成員被回收時,該成員會自動被移除;
Map:鍵值集合,鍵可以是各種類型的值;
WeakSet:特殊的Map,只能用對象或者null作為鍵,鍵對對象的引用是弱用;當鍵引用的對象被回收時,該鍵及其對應的值會被自動移除;
15. Proxy代理
通過proxy對象調用的方法中的this的值為proxy自身;
16. Promise特點
- promise對象的狀態不受處界影響;
- promise對象共有3種可能的狀態:Pending、Fulfiled、Rejected;
- 但promise對象的狀態變化只能是以下2種情況之一:
- 由Pending變為Fulfiled
- 由Pending變為Rejected
promise對象的狀態一旦改變,就不會再變,任何時候都可以得到這個結果;- promise對象一旦創建就會立即執行創建該promise對象時傳入的異步函數;
17. for...of遍歷
for...of循環只能用于遍歷實現了Iterator接口的數據結構;
只要數據結構部署了Symbol.iterator屬性,就被視為實現了iterator接口;
for...of遍歷的語法:
for (值變量 of 數據結構) {
循環體
}
示例:
let array = ['我','是','成','員'];
for (let memberValue of array) {
console.log(value);
}
/*輸出結果是:
*我
*是
*成
*員
*/
for...of遍歷的機制:
- for...of遍歷開始;
- 調用數據結構的Symbol.iterator方法
數據結構[Symbol.iterator]()
獲取數據結構的迭代器iterator;- 調用迭代器的next()方法
iterator.next()
獲取迭代器的結果iterationResult;- 如果iterationResult的done屬性的值為true,則結束for...of遍歷;
- 如果iterationResult的done屬性的值不為true,或者不存在,則將iterationResult.value的值賦給值變量memberValue;
- 執行循環體;
- 重復步驟3;
18. Iterator接口
由于JavaScript沒有描述接口的語法,所以,就用TypeScript語法來描述遍歷器接口Iterable、指針對象接口Iterator和next方法的返回值接口IterationResult 的定義,如下:
遍歷器接口Iterable的定義:
interface Iterable {
[Symbol.iterator]() : Iterator,
}
指針對象接口Iterator的定義:
interface Iterator {
next(value?: any) : IterationResult,
}
next方法的返回值接口IterationResult的定義:
interface IterationResult {
value: any,
done: boolean,
}
19. Generator函數
定義語法格式:
function* 函數名(參數){
yield 表達式;
...
yield 表達式;
return 表達式;
}
說明:
- 參數不是必須的;
- yield語句和return語句都不是必須的;
- yield關鍵字只能用在Generator函數內部,不能用在其它地方;
- 如果yield語句如果用在另一個表達式之中,則必須將yield語句用圓括號包住;
- 如果yield語句用作函數參數或放在賦值表達式的右邊,則可以不用將yield語句用圓括號包??;
- 執行Generator函數會返回一個迭代器對象iterator,該迭代器對象iterator有個[Symbol.iterator]屬性,該[Symbol.iterator]屬性的值為該迭代器對象iterator自身;
- yield語句的返回值為調用迭代器的next()方法時傳入的參數;如果沒有給迭代器的next()方法傳入的參數,則yield語句的返回值為undefined;
- 編譯器會忽略第1次調用迭代器的next()方法時傳入的參數;
- Generator函數可用于for...of循環;
yield* 表達式
會展開表達式的值;- Generator函數總是返回一個遍歷器,ES6規定這個遍歷器是 Generator函數 的實例,也會繼承 Generator函數 的prototype對象;
使用機制:
- 調用Generator函數
函數名(參數)
,會返回一個迭代器對象iterator;- 調用迭代器的next()方法
iterator.next()
執行函數體內未執行的代碼;- 如果遇到yield表達式,就暫停執行后面的操作,并將緊跟在yield后面的那個表達式的值,作為返回對象iterationResult(迭代器結果)的value屬性值,并給迭代器結果iterationResult的done屬性賦值為false;
- 如果沒有再遇到新的yield表達式,就一直運行到函數結束,直到return語句為止,并將return語句后面的表達式的值,作為返回對象iterationResult(迭代器結果)的value屬性值,并給迭代器結果iterationResult的done屬性賦值為true;
- 如果該函數沒有return語句,則返回對象iterationResult(迭代器結果)的value屬性值為undefined,并給迭代器結果iterationResult的done屬性賦值為true;
- 當下一次手動調用next方法時,重復步驟2;
20. async函數
定義語法格式:
async function 函數名(參數){
var 結果 = await 異步表達式;
...
var 結果 = await 異步表達式;
return 表達式;
}
說明:
- async函數會返回一個Promise對象;
- async函數內部return語句返回的值,會成為then方法回調函數的參數;
- async函數內部拋出錯誤,會導致返回的 Promise 對象變為reject狀態。拋出的錯誤對象會被catch方法回調函數接收到;
- async函數返回的 Promise 對象,必須等到內部所有await命令后面的 異步表達式執行完,才會發生狀態改變,除非遇到return語句或者拋出錯誤。也就是說,只有async函數內部的異步操作執行完,才會執行then方法指定的回調函數;
- 正常情況下,await命令后面是一個 Promise 對象。如果不是,會被轉成一個立即resolve的 Promise 對象;
- await命令后面的 Promise 對象如果變為reject狀態,則reject的參數會被傳遞到async函數會返回的Promise對象;
- 只要一個await語句后面的 Promise 變為reject,那么整個async函數都會中斷執行;
- 若希望await失敗后繼續執行后面的代碼,可以將await放在try...catch結構里面,這樣不管這個異步操作是否成功,后面的代碼都會執行;
- await命令只能用在async函數之中,如果用在普通函數,就會報錯;
- 目前,@std/esm模塊加載器支持頂層await,即await命令可以不放在 async 函數里面,直接使用;
21. class
定義語法格式:
class 類名 extends 父類名 {
}
說明:
- 基本上,ES6 的class可以看作只是一個語法糖,它的絕大部分功能,ES5 都可以做到,新的class寫法只是讓對象原型的寫法更加清晰、更像面向對象編程的語法而已;
- 類的數據類型就是函數,類本身就是它的構造方法constructor;
- 類的方法都定義在其prototype對象上面;
- constructor方法是類的默認方法,通過new命令生成對象實例時,自動調用該方法。一個類必須有constructor方法,如果沒有顯式定義,一個空的constructor方法會被默認添加;
- 類必須使用new調用,否則會報錯。這是它跟普通構造函數的一個主要區別,后者不用new也可以執行;
- 生成類的實例對象的寫法,與 ES5 完全一樣,也是使用new命令;
- 與函數一樣,類也可以使用表達式的形式定義,格式為:
var BClass = class AClass {};
;注意:這個類的名字是BClass而不是AClass,AClass只在 Class 的內部可用,指代當前類;如果類的內部沒用到AClass的話,可以省略AClass,也就是可以寫成var BClass = class {};
;- 因為子類必須在父類之后定義,類不存在聲明提升;
- 類的name屬性總是返回緊跟在class關鍵字后面的類名;
- 如果需要給類定義Generator方法,只需在Generator方法前加上星號
*
號;- 通過在一個方法前加上
static
關鍵字可以定義類的靜態方法,靜態方案是定義在類上的,通過類來調用,不能在類的實例上訪問到;- ES6 明確規定,Class 內部允許定義靜態方法,不允許定義靜態屬性;
- 子類必須在constructor方法中調用super方法,否則新建實例時會報錯。這是因為子類沒有自己的this對象,而是繼承父類的this對象,然后對其進行加工。如果不調用super方法,子類就得不到this對象;ES5 的繼承,實質是先創造子類的實例對象this,然后再將父類的方法添加到this上面(Parent.apply(this))。ES6 的繼承機制完全不同,實質是先創造父類的實例對象this(所以必須先調用super方法),然后再用子類的構造函數修改this;
- 在子類的構造函數中,只有調用super之后,才可以使用this關鍵字,否則會報錯。這是因為子類實例的構建,是基于對父類實例加工,只有super方法才能返回父類實例;
22. super關鍵字
- super關鍵字,既可以當作函數,也可以當作對象;
- 當把super關鍵字作為函數調用時,代表父類的構造函數;ES6 要求,子類的構造函數必須執行一次super函數;
- 當把super關鍵字作為對象時,在實例方法中,super表示父類的原型對象;在靜態方法中,super表示父類;
- ES6 規定,通過super調用父類的實例方法時,super會綁定this為super被調用處的作用域中的this值;
- 使用super時,必須顯式表明super是作為函數用還是作為對象用,否則會報錯;
23. Decorator裝飾器
- 當裝飾器裝飾類時,裝飾器對類的修改是在代碼編譯階段發生的;
- 修飾器只能用于類和類的方法,不能直接用于函數(可通過其它方式作用于函數),因為存在函數提升;
24. 模塊
模塊的特性
- ES6的模塊自動采用嚴格模式;
- 在ES6模塊中,頂層的this的值是undefined,不應該在頂層代碼使用this;
- export語句輸出的接口與其對應的值是動態綁定關系,即通過該接口,可以獲取到模塊內部實時的值;這一點與CommonJS規范完全不同,CommonJS模塊輸出的是值的緩存,不存在動態更新;
- export 和 import 命令可以并且只能出現在模塊頂層的任意位置;如果export 和 import 命令處于塊級作用域內,就會報錯;這是因為如果export 和 import 命令處于代碼塊之中,就沒法做靜態優化了,違背了ES6模塊的設計初衷;
- 因為:export default命令的本質是:將該命令后面的值,賦給default變量,然后輸出一個叫做default的量;
所以:
- 可以直接將一個值寫在export default之后;
- export default之后不能跟變量聲明語句;
- import后面的from指定模塊文件的位置,可以是相對路徑或者絕對路徑,
.js
后綴也可以省略;如果from后面指定的不是路徑,只是一個模塊名字,那么必須有配置文件能使JavaScript引擎找到該模塊的位置;- import命令具有提升效果,會提升到整個模塊的頂部,首先執行。這種行為的本質是:import命令是編譯階段執行的,所以它會在所有代碼運行之前執行;
- 由于import語句是在編譯階段執行,所以import語句中不能包含表達式、變量等只能在運行時才能得到結果的語法結構;
- 如果有相同的多條import語句,那么只會執行一次同一條import語句;
- 由于 ES6 輸入的模塊變量,只是一個“符號連接”,所以這個變量是只讀的,對它進行重新賦值會報錯;
- 在 Node 環境中,使用import命令加載 CommonJS 模塊,Node 會自動將module.exports屬性,當作模塊的默認輸出,即等同于export default;
- 采用require命令加載 ES6 模塊時,ES6 模塊的所有輸出接口,會成為輸入對象的屬性。
- ES6模塊與CommonJS模塊的區別:
- CommonJS 模塊輸出的是一個值的拷貝,ES6 模塊輸出的是值的引用。
- CommonJS 模塊是運行時加載,ES6 模塊是編譯時輸出接口。
- ES6 模塊之中,頂層的this指向undefined;CommonJS 模塊的頂層this指向當前模塊。
- import()提案:import(modulePath) 函數可以運行時加載內容,也就是說,只有當代碼執行到 import() 語句時,才會加載指定的內容;該函數返回一個 Promise 對象;import() 函數可以用在任地方,不僅僅是模塊,非模塊的腳本也可以使用;與 import 語句不同的是: import() 函數所加載出的值 與 源模塊中的值不具有動態綁定關系;注意: import()暫時還只是個提案;
模塊導入和導出的各種寫法
導出
有以下幾種導出方法:
- 導出聲明:
直接在(變量、函數、類型、類型別名、接口)聲明前添加export
關鍵字;
示例如下:
export const numberRegexp = /^[0-9]+$/;
export class ZipCodeValidator implements StringValidator {
isAcceptable(s: string) {
return s.length === 5 && numberRegexp.test(s);
}
}
- 導出語句:
導出語句可以把需要導出的實例放在一對大括號中一塊導出,也可以對導出的部分重新命名;(其實這種寫法只是屬性的簡寫語法,詳見<屬性的簡寫語法規則>;)
示例如下:
export const numberRegexp = /^[0-9]+$/;
class ZipCodeValidator implements StringValidator {
isAcceptable(s: string) {
return s.length === 5 && numberRegexp.test(s);
}
}
export { ZipCodeValidator,numberRegexp };
export { ZipCodeValidator as mainValidator };
- 重新導出:
我們經常會去擴展其它模塊,并且只導出那個模塊的部分內容。 重新導出功能并不會在當前模塊導入那個模塊或定義一個新的局部變量;
示例如下:
// 導出原先的類但做了重命名
export {ZipCodeValidator as RegExpBasedZipCodeValidator} from "./ZipCodeValidator";
- 默認導出:
每個模塊都可以有一個default導出。 默認導出使用default關鍵字標記;并且一個模塊只能夠有一個default導出。 需要使用一種特殊的導入形式來導入default導出。標記為默認導出的實體可以省略名字;
示例如下:
export default function (s: string) {
return s.length === 5 && numberRegexp.test(s);
}
導入
模塊的導入操作與導出一樣簡單。 可以使用以下import形式之一來導入其它模塊中的導出內容:
- 導入一個模塊中的某個導出內容,也可對于進行重命名:(其實這相當于對象的解構)
格式如下:
import { 要導出的內容的名字 } from "模塊路徑";
import { 要導出的內容的名字 as 新名字 } from "模塊路徑";
示例如下:
import { ZipCodeValidator as ZCV } from "./ZipCodeValidator";
let myValidator = new ZCV();
- 將整個模塊導入到一個變量,并通過它來訪問模塊的導出部分:
格式如下:
import * as 新名字 from "模塊路徑";
示例如下:
import * as validator from "./ZipCodeValidator";
let myValidator = new validator.ZipCodeValidator();
- 導出默認的導出項:
每個模塊都可以有一個default導出。 默認導出使用default關鍵字標記;并且一個模塊只能夠有一個default導出。
導出默認的導郵項目的格式如下:
import 自定義名字 from "模塊路徑";
示例如下:
import JQ from "JQuery";
JQ("button.continue").html( "Next Step..." );
- 具有副作用的導入模塊:
格式如下:
import "模塊路徑";
此種導入,相當于把相應模塊的代碼插入到了本模塊;
- import()函數:
注意: import()暫時還只是個提案;
import() 函數可以運行時加載內容,也就是說,只有當代碼執行到 import() 語句時,才會加載指定的內容;該函數返回一個 Promise 對象;import() 函數可以用在任地方,不僅僅是模塊,非模塊的腳本也可以使用;與 import 語句不同的是: import() 函數所加載出的值 與 源模塊中的值不具有動態綁定關系;
語法:
import(路徑):Promise
說明:
import() 函數會返回一個 Promis 對象,當模塊被加載成功之后,會生成一個模塊對象,該模塊對象會作為參數傳給 Promise 的成功的回調函數;所以也可以用對象解構賦值語法定義成功的回調函數的參數;
示例:
var modulePromise = import("./myModule.js");
modulePromise.then(function(moduleObj){
moduleObj.export1 ...... ;
moduleObj.export2 ...... ;
moduleObj.default ...... ;
});
或者:
var modulePromise = import("./myModule.js");
modulePromise.then(function({export1,export2,default}){
});
25. # 嚴格模式的限制
嚴格模式主要有以下限制。
- 變量必須聲明后再使用
- 函數的參數不能有同名屬性,否則報錯
- 不能使用with語句
- 不能對只讀屬性賦值,否則報錯
- 不能使用前綴0表示八進制數,否則報錯
- 不能刪除不可刪除的屬性,否則報錯
- 不能刪除變量delete prop,會報錯,只能刪除屬性delete global[prop]
- eval不會在它的外層作用域引入變量
- eval和arguments不能被重新賦值
- arguments不會自動反映函數參數的變化
- 不能使用arguments.callee
- 不能使用arguments.caller
- 禁止this指向全局對象
- 不能使用fn.caller和fn.arguments獲取函數調用的堆棧
- 增加了保留字(比如protected、static和interface)