1. 程式人生 > >ES6語法特性精華

ES6語法特性精華

1. let和const的特性

  1. let用來宣告變數;const用來宣告常量;
  2. 塊級作用域;
  3. 不會變數提升;
  4. 不允許重複宣告;
  5. 在全域性作用域內用let、const、class宣告的量不會成為全域性物件的屬性;
  6. 在塊級作用內任何地方用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的屬性:

  1. for...in迴圈:只遍歷物件自身的和繼承的可列舉的屬性;
  2. Object.keys():返回物件自身的所有可列舉的屬性的鍵名;
  3. JSON.stringify():只序列化物件自身的可列舉的屬性;
  4. Object.assign(): 忽略enumerable為false的屬性,只拷貝物件自身的可列舉的屬性;

6. 模板字串

  1. 模板字串是增強版的字串,用反引號`標識;可以通過${表示式}在模板字串中嵌入變數或者表示式;
  2. 模板字元中的所有空格和換行都會被保留;

7. 標籤模板

標籤模板是函式結合模板字串的一種呼叫形式;即標籤模板是一種函式的呼叫;標籤指代函式名字,模板指代模板字串;
標籤模板的格式如下:

函式名 模板字串

標籤模板會自動把模板字串拆分,然後把各部分以引數的形式傳給函式,具體的拆分規則和傳參規則如下:
模板字串的拆分規則:

  1. 如果模板字串中有n個模板插值,則會以模板插值${表示式}為節點把模板字串拆分成n+1個不含插值的字串;
  2. 如果模板插值在模板字串中的最前端或者最後端,則拆分後的 第1個 或者 最後一個 字串為不包含任何字元的空字串;
  3. 如果模板字串不包含模板插值,則不對模板字串進行拆分';

模板字串的傳參規則:

  1. 把被拆分後的n+1個字串(不包含模板插值)按照在模板字元中的次序裝進陣列中,然後把該陣列傳為第1個引數傳給標籤函式;
  2. 模板中第m個插值的值會作為標籤函式的第m+1個引數傳給標籤函式;

8. 陣列

  1. 支援擴充套件運算子...
    格式為:...陣列
    作用:把陣列展開為用逗號分隔的該陣列中所有元素的序列;它就像rest引數操作符的逆運算;如果陣列為空,則不產生任何內容;
  2. ES6規定,對陣列進行的任何的操作,都不能將空位忽略,必須將陣列中的空位將會被轉為undefined;

9. 物件

  1. 物件支援屬性簡寫語法;
  2. 在物件字面量中支援用表示式定義屬性名,格式為{[表示式]:屬性值},屬性的名為把表示式的值被轉成字串型別後的值;
  3. (本條是ES2017的新特性)支援擴充套件運算子...,格式為:{...物件};規則如下:
    假設:x是個物件;
    則有:表示式{...x}會取出物件x的所有可遍歷屬性,並將取出的所有屬性及屬性值拷貝到當前物件字面量中;
  4. 支援Null傳導運算子;

10. 屬性的簡寫語法規則

在ES6中,物件的屬性支援簡寫,具體規則如下:
假設:x是 變數 或者 常量 或者 函式;
則有:{x}相當於{'x':x}


11. 變數的解構賦值

  1. 允許模式不完全匹配,即支援不完全解構;
  2. 對於沒有匹配值的變數的值為undefined;
  3. 可以給模式中的變數設定預設值,預設值生效的條件是:該變數沒有配置的值,或者配置的值全等於===undefind;預設值可以是表示式,且該表示式是惰性求值的,即:只有在需要設定該預設值時,才會求值該表示式;
  4. 陣列解構賦值的機制:根據模式中變數的位置對變數賦值;
  5. 物件解構賦值的機制:根據模式中的屬性名對該屬性相對應的變數賦值;
  6. 物件的解構模式支援屬性的簡寫語法;
  7. 可以對陣列應用物件解構,因為陣列本質上是物件;(詳情請參考《JavaScript的發現與理解》)
  8. 當解構字串時,字串會被轉換成類似陣列的物件,該物件有length屬性,所以可以對字串應用陣列解構;
  9. 當被解構的資料是非物件型別時,會先把該資料轉換成相應的基本包裝物件,然後再對其進行解構;
  10. 變數解析也可以應用於函式的引數;

12. 函式

  1. 引數可以設定預設值;
  2. 當函式的部分或者全部引數帶有預設值時,函式的任何引數都不能有同名引數;
  3. 引數的預設值是惰性求值的,且每次需要預設值時都會重新計算;
  4. 當函式的引數是解構時,在呼叫該函式時,傳入的引數必須使所有的解構引數成功解構,否則將報錯;通過給解構引數設定能成功解構的預設值可以防止函式在呼叫時因傳解構失敗而報錯;
  5. 函式物件有個length屬性,該屬性表示的準確意義是:函式預期傳入的引數個數;所以,length屬性的值為函式的不帶預設值的引數的個數;
  6. 如果函式帶有包含預設值的引數,那麼函式在被呼叫時,會為所有的引數會形成一個單獨的作用域;該作用域會在所有引數被初始化後消失;如果函式的任何引數都不包含預設值,則在函式在被呼叫時不會為引數生成單獨的作用域;
  7. 如果函式的任何引數都不包含預設值,則函式的引數是在函式內預設用var宣告的;如果函式帶有包含預設值的引數,則函式的引數是在被用let宣告的;(此結論是經過測試研究推理得出的,沒有得到權威的承認);
  8. 支援rest引數(剩餘引數),且rest引數必須是最後一個引數;
  9. 在ES2016中,只要函式引數使用了預設值、解構賦值、或者擴充套件運算子,那麼函式內部就不能顯式設定為嚴格模式,否則會報錯;
  10. 函式物件的name屬性儲存的是函式最初設定的名字;
    在ES6中:對於匿名函式,在其第1次賦值給的變數之前,其name屬性的值為空字串"",在其第1次賦值給的變數之後,其name屬性的值為該匿名函式第1次賦值給的變數的名字;
    在ES5及之前中:對於匿名函式,其name屬性的值一直為空字串"";
    Function建構函式返回的函式例項的name屬性的值為anonymous;
    bind()返回的函式的name屬性值是;bound 原name;
  11. 支援尾呼叫優化;

13. 箭頭函式

  1. 箭頭函式會繫結其this的值為:該箭頭函式被定義處所在作用域中的this的值;即:箭頭函式中的this的值 是 定義該箭頭函式處所在作用域中的this的值;
  2. 不能用作建構函式;
  3. 箭頭函式中沒有arguments物件;
  4. 箭頭函式中不能使用yield關鍵字;

14. Set和Map

Set:類似陣列,但成員值都是唯一的,不會重複,並且沒有索引;
WeakSet:特殊的Set;成員只能是物件型別,並且對成員的引用是弱引用;當成員被回收時,該成員會自動被移除;
Map:鍵值集合,鍵可以是各種型別的值;
WeakSet:特殊的Map,只能用物件或者null作為鍵,鍵對物件的引用是弱用;當鍵引用的物件被回收時,該鍵及其對應的值會被自動移除;


15. Proxy代理

通過proxy物件呼叫的方法中的this的值為proxy自身;


16. Promise特點

  1. promise物件的狀態不受處界影響;
  2. promise物件共有3種可能的狀態:Pending、Fulfiled、Rejected;
  3. 但promise物件的狀態變化只能是以下2種情況之一:
    1. 由Pending變為Fulfiled
    2. 由Pending變為Rejected
      promise物件的狀態一旦改變,就不會再變,任何時候都可以得到這個結果;
  4. 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遍歷的機制:

  1. for...of遍歷開始;
  2. 呼叫資料結構的Symbol.iterator方法資料結構[Symbol.iterator]()獲取資料結構的迭代器iterator;
  3. 呼叫迭代器的next()方法iterator.next()獲取迭代器的結果iterationResult;
  4. 如果iterationResult的done屬性的值為true,則結束for...of遍歷;
  5. 如果iterationResult的done屬性的值不為true,或者不存在,則將iterationResult.value的值賦給值變數memberValue;
  6. 執行迴圈體;
  7. 重複步驟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 表示式;
}

說明:

  1. 引數不是必須的;
  2. yield語句和return語句都不是必須的;
  3. yield關鍵字只能用在Generator函式內部,不能用在其它地方;
  4. 如果yield語句如果用在另一個表示式之中,則必須將yield語句用圓括號包住;
  5. 如果yield語句用作函式引數或放在賦值表示式的右邊,則可以不用將yield語句用圓括號包住;
  6. 執行Generator函式會返回一個迭代器物件iterator,該迭代器物件iterator有個[Symbol.iterator]屬性,該[Symbol.iterator]屬性的值為該迭代器物件iterator自身;
  7. yield語句的返回值為呼叫迭代器的next()方法時傳入的引數;如果沒有給迭代器的next()方法傳入的引數,則yield語句的返回值為undefined;
  8. 編譯器會忽略第1次呼叫迭代器的next()方法時傳入的引數;
  9. Generator函式可用於for...of迴圈;
  10. yield* 表示式會展開表示式的值;
  11. Generator函式總是返回一個遍歷器,ES6規定這個遍歷器是 Generator函式 的例項,也會繼承 Generator函式 的prototype物件;

使用機制:

  1. 呼叫Generator函式函式名(引數),會返回一個迭代器物件iterator;
  2. 呼叫迭代器的next()方法iterator.next()執行函式體內未執行的程式碼;
  3. 如果遇到yield表示式,就暫停執行後面的操作,並將緊跟在yield後面的那個表示式的值,作為返回物件iterationResult(迭代器結果)的value屬性值,並給迭代器結果iterationResult的done屬性賦值為false;
  4. 如果沒有再遇到新的yield表示式,就一直執行到函式結束,直到return語句為止,並將return語句後面的表示式的值,作為返回物件iterationResult(迭代器結果)的value屬性值,並給迭代器結果iterationResult的done屬性賦值為true;
  5. 如果該函式沒有return語句,則返回物件iterationResult(迭代器結果)的value屬性值為undefined,並給迭代器結果iterationResult的done屬性賦值為true;
  6. 當下一次手動呼叫next方法時,重複步驟2;

20. async函式

定義語法格式:

async function 函式名(引數){
var 結果 = await 非同步表示式;
...
var 結果 = await 非同步表示式;
return 表示式;
}

說明:

  1. async函式會返回一個Promise物件;
  2. async函式內部return語句返回的值,會成為then方法回撥函式的引數;
  3. async函式內部丟擲錯誤,會導致返回的 Promise 物件變為reject狀態。丟擲的錯誤物件會被catch方法回撥函式接收到;
  4. async函式返回的 Promise 物件,必須等到內部所有await命令後面的 非同步表示式執行完,才會發生狀態改變,除非遇到return語句或者丟擲錯誤。也就是說,只有async函式內部的非同步操作執行完,才會執行then方法指定的回撥函式;
  5. 正常情況下,await命令後面是一個 Promise 物件。如果不是,會被轉成一個立即resolve的 Promise 物件;
  6. await命令後面的 Promise 物件如果變為reject狀態,則reject的引數會被傳遞到async函式會返回的Promise物件;
  7. 只要一個await語句後面的 Promise 變為reject,那麼整個async函式都會中斷執行;
  8. 若希望await失敗後繼續執行後面的程式碼,可以將await放在try...catch結構裡面,這樣不管這個非同步操作是否成功,後面的程式碼都會執行;
  9. await命令只能用在async函式之中,如果用在普通函式,就會報錯;
  10. 目前,@std/esm模組載入器支援頂層await,即await命令可以不放在 async 函式裡面,直接使用;

21. class

定義語法格式:

class 類名 extends 父類名 {

}

說明:

  1. 基本上,ES6 的class可以看作只是一個語法糖,它的絕大部分功能,ES5 都可以做到,新的class寫法只是讓物件原型的寫法更加清晰、更像面向物件程式設計的語法而已;
  2. 類的資料型別就是函式,類本身就是它的構造方法constructor;
  3. 類的方法都定義在其prototype物件上面;
  4. constructor方法是類的預設方法,通過new命令生成物件例項時,自動呼叫該方法。一個類必須有constructor方法,如果沒有顯式定義,一個空的constructor方法會被預設新增;
  5. 類必須使用new呼叫,否則會報錯。這是它跟普通建構函式的一個主要區別,後者不用new也可以執行;
  6. 生成類的例項物件的寫法,與 ES5 完全一樣,也是使用new命令;
  7. 與函式一樣,類也可以使用表示式的形式定義,格式為:var BClass = class AClass {};;注意:這個類的名字是BClass而不是AClass,AClass只在 Class 的內部可用,指代當前類;如果類的內部沒用到AClass的話,可以省略AClass,也就是可以寫成var BClass = class {};
  8. 因為子類必須在父類之後定義,類不存在宣告提升;
  9. 類的name屬性總是返回緊跟在class關鍵字後面的類名;
  10. 如果需要給類定義Generator方法,只需在Generator方法前加上星號*號;
  11. 通過在一個方法前加上static關鍵字可以定義類的靜態方法,靜態方案是定義在類上的,通過類來呼叫,不能在類的例項上訪問到;
  12. ES6 明確規定,Class 內部允許定義靜態方法,不允許定義靜態屬性;
  13. 子類必須在constructor方法中呼叫super方法,否則新建例項時會報錯。這是因為子類沒有自己的this物件,而是繼承父類的this物件,然後對其進行加工。如果不呼叫super方法,子類就得不到this物件;ES5 的繼承,實質是先創造子類的例項物件this,然後再將父類的方法新增到this上面(Parent.apply(this))。ES6 的繼承機制完全不同,實質是先創造父類的例項物件this(所以必須先呼叫super方法),然後再用子類的建構函式修改this;
  14. 在子類的建構函式中,只有呼叫super之後,才可以使用this關鍵字,否則會報錯。這是因為子類例項的構建,是基於對父類例項加工,只有super方法才能返回父類例項;

22. super關鍵字

  1. super關鍵字,既可以當作函式,也可以當作物件;
  2. 當把super關鍵字作為函式呼叫時,代表父類的建構函式;ES6 要求,子類的建構函式必須執行一次super函式;
  3. 當把super關鍵字作為物件時,在例項方法中,super表示父類的原型物件;在靜態方法中,super表示父類;
  4. ES6 規定,通過super呼叫父類的例項方法時,super會繫結this為super被呼叫處的作用域中的this值;
  5. 使用super時,必須顯式表明super是作為函式用還是作為物件用,否則會報錯;

23. Decorator裝飾器

  1. 當裝飾器裝飾類時,裝飾器對類的修改是在程式碼編譯階段發生的;
  2. 修飾器只能用於類和類的方法,不能直接用於函式(可通過其它方式作用於函式),因為存在函式提升;

24. 模組

模組的特性

  1. ES6的模組自動採用嚴格模式;
  2. 在ES6模組中,頂層的this的值是undefined,不應該在頂層程式碼使用this;
  3. export語句輸出的介面與其對應的值是動態繫結關係,即通過該介面,可以獲取到模組內部實時的值;這一點與CommonJS規範完全不同,CommonJS模組輸出的是值的快取,不存在動態更新;
  4. export 和 import 命令可以並且只能出現在模組頂層的任意位置;如果export 和 import 命令處於塊級作用域內,就會報錯;這是因為如果export 和 import 命令處於程式碼塊之中,就沒法做靜態優化了,違背了ES6模組的設計初衷;
  5. 因為:export default命令的本質是:將該命令後面的值,賦給default變數,然後輸出一個叫做default的量;
    所以:
    1. 可以直接將一個值寫在export default之後;
    2. export default之後不能跟變數宣告語句;
  6. import後面的from指定模組檔案的位置,可以是相對路徑或者絕對路徑,.js字尾也可以省略;如果from後面指定的不是路徑,只是一個模組名字,那麼必須有配置檔案能使JavaScript引擎找到該模組的位置;
  7. import命令具有提升效果,會提升到整個模組的頂部,首先執行。這種行為的本質是:import命令是編譯階段執行的,所以它會在所有程式碼執行之前執行;
  8. 由於import語句是在編譯階段執行,所以import語句中不能包含表示式、變數等只能在執行時才能得到結果的語法結構;
  9. 如果有相同的多條import語句,那麼只會執行一次同一條import語句;
  10. 由於 ES6 輸入的模組變數,只是一個“符號連線”,所以這個變數是隻讀的,對它進行重新賦值會報錯;
  11. 在 Node 環境中,使用import命令載入 CommonJS 模組,Node 會自動將module.exports屬性,當作模組的預設輸出,即等同於export default;
  12. 採用require命令載入 ES6 模組時,ES6 模組的所有輸出介面,會成為輸入物件的屬性。
  13. ES6模組與CommonJS模組的區別:
  • CommonJS 模組輸出的是一個值的拷貝,ES6 模組輸出的是值的引用。
  • CommonJS 模組是執行時載入,ES6 模組是編譯時輸出介面。
  • ES6 模組之中,頂層的this指向undefined;CommonJS 模組的頂層this指向當前模組。
  1. import()提案:import(modulePath) 函式可以執行時載入內容,也就是說,只有當代碼執行到 import() 語句時,才會載入指定的內容;該函式返回一個 Promise 物件;import() 函式可以用在任地方,不僅僅是模組,非模組的指令碼也可以使用;與 import 語句不同的是: import() 函式所加載出的值 與 源模組中的值不具有動態繫結關係;注意: import()暫時還只是個提案;

模組匯入和匯出的各種寫法

匯出

有以下幾種匯出方法:

  1. 匯出宣告:
    直接在(變數、函式、型別、類型別名、介面)宣告前新增export關鍵字;
    示例如下:
export const numberRegexp = /^[0-9]+$/;

export class ZipCodeValidator implements StringValidator {
    isAcceptable(s: string) {
            return s.length === 5 && numberRegexp.test(s);
    }
}
  1. 匯出語句:
    匯出語句可以把需要匯出的例項放在一對大括號中一塊匯出,也可以對匯出的部分重新命名;(其實這種寫法只是屬性的簡寫語法,詳見<屬性的簡寫語法規則>;)
    示例如下:
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 };
  1. 重新匯出:
    我們經常會去擴充套件其它模組,並且只匯出那個模組的部分內容。 重新匯出功能並不會在當前模組匯入那個模組或定義一個新的區域性變數;
    示例如下:
// 匯出原先的類但做了重新命名
export {ZipCodeValidator as RegExpBasedZipCodeValidator} from "./ZipCodeValidator";
  1. 預設匯出:
    每個模組都可以有一個default匯出。 預設匯出使用default關鍵字標記;並且一個模組只能夠有一個default匯出。 需要使用一種特殊的匯入形式來匯入default匯出。標記為預設匯出的實體可以省略名字;
    示例如下:
export default function (s: string) {
    return s.length === 5 && numberRegexp.test(s);
}

匯入

模組的匯入操作與匯出一樣簡單。 可以使用以下import形式之一來匯入其它模組中的匯出內容:

  1. 匯入一個模組中的某個匯出內容,也可對於進行重新命名:(其實這相當於物件的解構)
    格式如下:
import { 要匯出的內容的名字 } from "模組路徑";
import { 要匯出的內容的名字 as 新名字 } from "模組路徑";

示例如下:

import { ZipCodeValidator as ZCV } from "./ZipCodeValidator";
let myValidator = new ZCV();
  1. 將整個模組匯入到一個變數,並通過它來訪問模組的匯出部分:
    格式如下:
import * as 新名字 from "模組路徑";

示例如下:

import * as validator from "./ZipCodeValidator";
let myValidator = new validator.ZipCodeValidator();
  1. 匯出預設的匯出項:
    每個模組都可以有一個default匯出。 預設匯出使用default關鍵字標記;並且一個模組只能夠有一個default匯出。
    匯出預設的導郵專案的格式如下:
import 自定義名字 from "模組路徑";

示例如下:

import JQ from "JQuery";

JQ("button.continue").html( "Next Step..." );
  1. 具有副作用的匯入模組:
    格式如下:
import "模組路徑";

此種匯入,相當於把相應模組的程式碼插入到了本模組;

  1. 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. # 嚴格模式的限制

嚴格模式主要有以下限制。

  1. 變數必須聲明後再使用
  2. 函式的引數不能有同名屬性,否則報錯
  3. 不能使用with語句
  4. 不能對只讀屬性賦值,否則報錯
  5. 不能使用字首0表示八進位制數,否則報錯
  6. 不能刪除不可刪除的屬性,否則報錯
  7. 不能刪除變數delete prop,會報錯,只能刪除屬性delete global[prop]
  8. eval不會在它的外層作用域引入變數
  9. eval和arguments不能被重新賦值
  10. arguments不會自動反映函式引數的變化
  11. 不能使用arguments.callee
  12. 不能使用arguments.caller
  13. 禁止this指向全域性物件
  14. 不能使用fn.caller和fn.arguments獲取函式呼叫的堆疊
  15. 增加了保留字(比如protected、static和interface)