1.5萬字概括ES6全部特性

發表於2019-10-14

關注Uzero公眾號,更多前端小乾貨等著你喔!

前言

第三次閱讀阮一峰老師的《ES6標準入門》了,以前閱讀時不細心,很多地方都是一目十行。最近這次閱讀都是逐個逐個字來讀,發現很多以前都沒有注意到的知識點,為了方便記憶和預覽全部ES6特性,所以寫下本文。

以下提到的《ES6標準入門》統一使用《ES6》這個名稱來代替,而最新的ES6版本也是截止到當前的ES2019

本文的知識點完全是參考或摘錄《ES6》裡的語句,有部分語句為了方便理解和記憶,進行了相同意思的轉義,同時對知識點進行歸類劃分。為了讓大家能集中精力來記住這些特性,全文一句廢話和題外話都沒有,全部模組以筆記的形式進行書寫,如果看得不是很慣建議對照《ES6》的內容來學習。

本文整理出來的筆記都是書中的精華內容,囊括了整個ES6體系的所有特性,非常方便大家重新認識全部ES6特性。半小時的閱讀就可以對ES6有一個全面的瞭解,可以認為是一本ES6特性小字典,收藏後可隨時查閱。即使看不完也要拉到本文末尾喔,有個大彩蛋,嘻嘻!

ES6縮略

修正

ES6ECMAJavaScript制定的第6個標準版本,相關歷史可檢視此章節《ES6-ECMAScript6簡介》

標準委員會最終決定,標準在每年6月正式釋出並作為當年的正式版本,接下來的時間裡就在此版本的基礎上進行改動,直到下一年6月草案就自然變成新一年的版本,這樣一來就無需以前的版本號,只要用年份標記即可。ECMAscript 2015是在2015年6月釋出ES6的第一個版本。以此類推,ECMAscript 2016是ES6的第二個版本、 ECMAscript 2017是ES6的第三個版本。ES6既是一個歷史名詞也是一個泛指,含義是5.1版本以後的JavaScript下一代標準,目前涵蓋了ES2015ES2016ES2017ES2018ES2019

所以有些文章上提到的ES7(實質上是ES2016)、ES8(實質上是ES2017)、ES9(實質上是ES2018)、ES10(實質上是ES2019),實質上都是一些不規範的概念。從ES1到ES6,每個標準都是花了好幾年甚至十多年才制定下來,你一個ES6到ES7,ES7到ES8,才用了一年,按照這樣的定義下去,那不是很快就ES20了。用正確的概念來說ES6目前涵蓋了ES2015ES2016ES2017ES2018ES2019

ES6組成

另外,ES6更新的內容主要分為以下幾點

  • 表示式:宣告、解構賦值
  • 內建物件:字串擴充套件、數值擴充套件、物件擴充套件、陣列擴充套件、函式擴充套件、正則擴充套件、Symbol、Set、Map、Proxy、Reflect
  • 語句與運算:Class、Module、Iterator
  • 非同步程式設計:Promise、Generator、Async

ES2015

ES2015

宣告

  • const命令:宣告常量
  • let命令:宣告變數
作用
  • 作用域

    • 全域性作用域
    • 函式作用域function() {}
    • 塊級作用域{}
  • 作用範圍

    • var命令在全域性程式碼中執行
    • const命令let命令只能在程式碼塊中執行
  • 賦值使用

    • const命令宣告常量後必須立馬賦值
    • let命令宣告變數後可立馬賦值或使用時賦值
  • 宣告方法:varconstletfunctionclassimport
重點難點
  • 不允許重複宣告
  • 未定義就使用會報錯:const命令let命令不存在變數提升
  • 暫時性死區:在程式碼塊內使用let命令宣告變數之前,該變數都不可用

解構賦值

  • 字串解構const [a, b, c, d, e] = "hello"
  • 數值解構const { toString: s } = 123
  • 布林值解構const { toString: b } = true
  • 物件解構

    • 形式:const { x, y } = { x: 1, y: 2 }
    • 預設:const { x, y = 2 } = { x: 1 }
    • 改名:const { x, y: z } = { x: 1, y: 2 }
  • 陣列解構

    • 規則:資料結構具有Iterator介面可採用陣列形式的解構賦值
    • 形式:const [x, y] = [1, 2]
    • 預設:const [x, y = 2] = [1]
  • 函式引數解構

    • 陣列解構:function Func([x = 0, y = 1]) {}
    • 物件解構:function Func({ x = 0, y = 1 } = {}) {}
應用場景
  • 交換變數值:[x, y] = [y, x]
  • 返回函式多個值:const [x, y, z] = Func()
  • 定義函式引數:Func([1, 2])
  • 提取JSON資料:const { name, version } = packageJson
  • 定義函式引數預設值:function Func({ x = 1, y = 2 } = {}) {}
  • 遍歷Map結構:for (let [k, v] of Map) {}
  • 輸入模組指定屬性和方法:const { readFile, writeFile } = require("fs")
重點難點
  • 匹配模式:只要等號兩邊的模式相同,左邊的變數就會被賦予對應的值
  • 解構賦值規則:只要等號右邊的值不是物件或陣列,就先將其轉為物件
  • 解構預設值生效條件:屬性值嚴格等於undefined
  • 解構遵循匹配模式
  • 解構不成功時變數的值等於undefined
  • undefinednull無法轉為物件,因此無法進行解構

字串擴充套件

  • Unicode表示法大括號包含表示Unicode字元(\u{0xXX}\u{0XXX})
  • 字串遍歷:可通過for-of遍歷字串
  • 字串模板:可單行可多行可插入變數的增強版字串
  • 標籤模板:函式引數的特殊呼叫
  • String.raw():返回把字串所有變數替換且對斜槓進行轉義的結果
  • String.fromCodePoint():返回碼點對應字元
  • codePointAt():返回字元對應碼點(String.fromCodePoint()的逆操作)
  • normalize():把字元的不同表示方法統一為同樣形式,返回新字串(Unicode正規化)
  • repeat():把字串重複n次,返回新字串
  • matchAll():返回正規表示式在字串的所有匹配
  • includes():是否存在指定字串
  • startsWith():是否存在字串頭部指定字串
  • endsWith():是否存在字串尾部指定字串
重點難點
  • 以上擴充套件方法均可作用於由4個位元組儲存Unicode字元

數值擴充套件

  • 二進位制表示法0b或0B開頭表示二進位制(0bXX0BXX)
  • 八進位制表示法0o或0O開頭表示二進位制(0oXX0OXX)
  • Number.EPSILON:數值最小精度
  • Number.MIN_SAFE_INTEGER:最小安全數值(-2^53)
  • Number.MAX_SAFE_INTEGER:最大安全數值(2^53)
  • Number.parseInt():返回轉換值的整數部分
  • Number.parseFloat():返回轉換值的浮點數部分
  • Number.isFinite():是否為有限數值
  • Number.isNaN():是否為NaN
  • Number.isInteger():是否為整數
  • Number.isSafeInteger():是否在數值安全範圍內
  • Math.trunc():返回數值整數部分
  • Math.sign():返回數值型別(正數1負數-1零0)
  • Math.cbrt():返回數值立方根
  • Math.clz32():返回數值的32位無符號整數形式
  • Math.imul():返回兩個數值相乘
  • Math.fround():返回數值的32位單精度浮點數形式
  • Math.hypot():返回所有數值平方和的平方根
  • Math.expm1():返回e^n - 1
  • Math.log1p():返回1 + n的自然對數(Math.log(1 + n))
  • Math.log10():返回以10為底的n的對數
  • Math.log2():返回以2為底的n的對數
  • Math.sinh():返回n的雙曲正弦
  • Math.cosh():返回n的雙曲餘弦
  • Math.tanh():返回n的雙曲正切
  • Math.asinh():返回n的反雙曲正弦
  • Math.acosh():返回n的反雙曲餘弦
  • Math.atanh():返回n的反雙曲正切

物件擴充套件

  • 簡潔表示法:直接寫入變數和函式作為物件的屬性和方法({ prop, method() {} })
  • 屬性名錶達式:字面量定義物件時使用[]定義鍵([prop],不能與上同時使用)
  • 方法的name屬性:返回方法函式名

    • 取值函式(getter)和存值函式(setter):get/set 函式名(屬性的描述物件在getset上)
    • bind返回的函式:bound 函式名
    • Function建構函式返回的函式例項:anonymous
  • 屬性的可列舉性和遍歷:描述物件的enumerable
  • super關鍵字:指向當前物件的原型物件(只能用在物件的簡寫方法中method() {})
  • Object.is():對比兩值是否相等
  • Object.assign():合併物件(淺拷貝),返回原物件
  • Object.getPrototypeOf():返回物件的原型物件
  • Object.setPrototypeOf():設定物件的原型物件
  • __proto__:返回或設定物件的原型物件
屬性遍歷
  • 描述:自身可繼承可列舉非列舉Symbol
  • 遍歷

    • for-in:遍歷物件自身可繼承可列舉屬性
    • Object.keys():返回物件自身可列舉屬性的鍵組成的陣列
    • Object.getOwnPropertyNames():返回物件自身可繼承可列舉非列舉屬性的鍵組成的陣列
    • Object.getOwnPropertySymbols():返回物件Symbol屬性的鍵組成的陣列
    • Reflect.ownKeys():返回物件自身可繼承可列舉非列舉Symbol屬性的鍵組成的陣列
  • 規則

    • 首先遍歷所有數值鍵,按照數值升序排列
    • 其次遍歷所有字串鍵,按照加入時間升序排列
    • 最後遍歷所有Symbol鍵,按照加入時間升序排列

陣列擴充套件

  • 擴充套件運算子(...):轉換陣列為用逗號分隔的引數序列([...arr],相當於rest/spread引數的逆運算)
  • Array.from():轉換具有Iterator介面的資料結構為真正陣列,返回新陣列

    • 類陣列物件:包含length的物件Arguments物件NodeList物件
    • 可遍歷物件:StringSet結構Map結構Generator函式
  • Array.of():轉換一組值為真正陣列,返回新陣列
  • copyWithin():把指定位置的成員複製到其他位置,返回原陣列
  • find():返回第一個符合條件的成員
  • findIndex():返回第一個符合條件的成員索引值
  • fill():根據指定值填充整個陣列,返回原陣列
  • keys():返回以索引值為遍歷器的物件
  • values():返回以屬性值為遍歷器的物件
  • entries():返回以索引值和屬性值為遍歷器的物件
  • 陣列空位:ES6明確將陣列空位轉為undefined(空位處理規不一,建議避免出現)
擴充套件應用
  • 克隆陣列:const arr = [...arr1]
  • 合併陣列:const arr = [...arr1, ...arr2]
  • 拼接陣列:arr.push(...arr1)
  • 代替apply:Math.max.apply(null, [x, y]) => Math.max(...[x, y])
  • 轉換字串為陣列:[..."hello"]
  • 轉換類陣列物件為陣列:[...Arguments, ...NodeList]
  • 轉換可遍歷物件為陣列:[...String, ...Set, ...Map, ...Generator]
  • 與陣列解構賦值結合:const [x, ...rest/spread] = [1, 2, 3]
  • 計算Unicode字元長度:Array.from("hello").length => [..."hello"].length
重點難點
  • 使用keys()values()entries()返回的遍歷器物件,可用for-of自動遍歷或next()手動遍歷

函式擴充套件

  • 引數預設值:為函式引數指定預設值

    • 形式:function Func(x = 1, y = 2) {}
    • 引數賦值:惰性求值(函式呼叫後才求值)
    • 引數位置:尾引數
    • 引數作用域:函式作用域
    • 宣告方式:預設宣告,不能用constlet再次宣告
    • length:返回沒有指定預設值的引數個數
    • 與解構賦值預設值結合:function Func({ x = 1, y = 2 } = {}) {}
    • 應用

      • 指定某個引數不得省略,省略即丟擲錯誤:function Func(x = throwMissing()) {}
      • 將引數預設值設為undefined,表明此引數可省略:Func(undefined, 1)
  • rest/spread引數(...):返回函式多餘引數

    • 形式:以陣列的形式存在,之後不能再有其他引數
    • 作用:代替Arguments物件
    • length:返回沒有指定預設值的引數個數但不包括rest/spread引數
  • 嚴格模式:在嚴格條件下執行JS

    • 應用:只要函式引數使用預設值、解構賦值、擴充套件運算子,那麼函式內部就不能顯式設定為嚴格模式
  • name屬性:返回函式的函式名

    • 將匿名函式賦值給變數:空字串(ES5)、變數名(ES6)
    • 將具名函式賦值給變數:函式名(ES5和ES6)
    • bind返回的函式:bound 函式名(ES5和ES6)
    • Function建構函式返回的函式例項:anonymous(ES5和ES6)
  • 箭頭函式(=>):函式簡寫

    • 無引數:() => {}
    • 單個引數:x => {}
    • 多個引數:(x, y) => {}
    • 解構引數:({x, y}) => {}
    • 巢狀使用:部署管道機制
    • this指向固定化

      • 並非因為內部有繫結this的機制,而是根本沒有自己的this,導致內部的this就是外層程式碼塊的this
      • 因為沒有this,因此不能用作建構函式
  • 尾呼叫優化:只保留內層函式的呼叫幀

    • 尾呼叫

      • 定義:某個函式的最後一步是呼叫另一個函式
      • 形式:function f(x) { return g(x); }
    • 尾遞迴

      • 定義:函式尾呼叫自身
      • 作用:只要使用尾遞迴就不會發生棧溢位,相對節省記憶體
      • 實現:把所有用到的內部變數改寫成函式的引數並使用引數預設值
箭頭函式誤區
  • 函式體內的this定義時所在的物件而不是使用時所在的物件
  • 可讓this指向固定化,這種特性很有利於封裝回撥函式
  • 不可當作建構函式,因此箭頭函式不可使用new命令
  • 不可使用yield命令,因此箭頭函式不能用作Generator函式
  • 不可使用Arguments物件,此物件在函式體內不存在(可用rest/spread引數代替)
  • 返回物件時必須在物件外面加上括號

正則擴充套件

  • 變更RegExp建構函式入參:允許首引數為正則物件,尾引數為正則修飾符(返回的正規表示式會忽略原正規表示式的修飾符)
  • 正則方法呼叫變更:字串物件的match()replace()search()split()內部呼叫轉為呼叫RegExp例項對應的RegExp.prototype[Symbol.方法]
  • u修飾符:Unicode模式修飾符,正確處理大於\uFFFFUnicode字元

    • 點字元(.)
    • Unicode表示法
    • 量詞
    • 預定義模式
    • i修飾符
    • 轉義
  • y修飾符:粘連修飾符,確保匹配必須從剩餘的第一個位置開始全域性匹配(與g修飾符作用類似)
  • unicode:是否設定u修飾符
  • sticky:是否設定y修飾符
  • flags:正規表示式的修飾符
重點難點
  • y修飾符隱含頭部匹配標誌^
  • 單單一個y修飾符match()只能返回第一個匹配,必須與g修飾符聯用才能返回所有匹配

Symbol

  • 定義:獨一無二的值
  • 宣告:const set = Symbol(str)
  • 入參:字串(可選)
  • 方法

    • Symbol():建立以引數作為描述的Symbol值(不登記在全域性環境)
    • Symbol.for():建立以引數作為描述的Symbol值,如存在此引數則返回原有的Symbol值(先搜尋後建立,登記在全域性環境)
    • Symbol.keyFor():返回已登記的Symbol值的描述(只能返回Symbol.for()key)
    • Object.getOwnPropertySymbols():返回物件中所有用作屬性名的Symbol值的陣列
  • 內建

    • Symbol.hasInstance:指向一個內部方法,當其他物件使用instanceof運算子判斷是否為此物件的例項時會呼叫此方法
    • Symbol.isConcatSpreadable:指向一個布林值,定義物件用於Array.prototype.concat()時是否可展開
    • Symbol.species:指向一個建構函式,當例項物件使用自身建構函式時會呼叫指定的建構函式
    • Symbol.match:指向一個函式,當例項物件被String.prototype.match()呼叫時會重新定義match()的行為
    • Symbol.replace:指向一個函式,當例項物件被String.prototype.replace()呼叫時會重新定義replace()的行為
    • Symbol.search:指向一個函式,當例項物件被String.prototype.search()呼叫時會重新定義search()的行為
    • Symbol.split:指向一個函式,當例項物件被String.prototype.split()呼叫時會重新定義split()的行為
    • Symbol.iterator:指向一個預設遍歷器方法,當例項物件執行for-of時會呼叫指定的預設遍歷器
    • Symbol.toPrimitive:指向一個函式,當例項物件被轉為原始型別的值時會返回此物件對應的原始型別值
    • Symbol.toStringTag:指向一個函式,當例項物件被Object.prototype.toString()呼叫時其返回值會出現在toString()返回的字串之中表示物件的型別
    • Symbol.unscopables:指向一個物件,指定使用with時哪些屬性會被with環境排除
資料型別
  • Undefined
  • Null
  • String
  • Number
  • Boolean
  • Object(包含ArrayFunctionDateRegExpError)
  • Symbol
應用場景
  • 唯一化物件屬性名:屬性名屬於Symbol型別,就都是獨一無二的,可保證不會與其他屬性名產生衝突
  • 消除魔術字串:在程式碼中多次出現且與程式碼形成強耦合的某一個具體的字串或數值
  • 遍歷屬性名:無法通過for-infor-ofObject.keys()Object.getOwnPropertyNames()JSON.stringify()返回,只能通過Object.getOwnPropertySymbols返回
  • 啟用模組的Singleton模式:呼叫一個類在任何時候返回同一個例項(windowglobal),使用Symbol.for()來模擬全域性的Singleton模式
重點難點
  • Symbol()生成一個原始型別的值不是物件,因此Symbol()前不能使用new命令
  • Symbol()參數列示對當前Symbol值的描述,相同引數的Symbol()返回值不相等
  • Symbol值不能與其他型別的值進行運算
  • Symbol值可通過String()toString()顯式轉為字串
  • Symbol值作為物件屬性名時,此屬性是公開屬性,但不是私有屬性
  • Symbol值作為物件屬性名時,只能用方括號運算子([])讀取,不能用點運算子(.)讀取
  • Symbol值作為物件屬性名時,不會被常規方法遍歷得到,可利用此特性為物件定義非私有但又只用於內部的方法

Set

Set
  • 定義:類似於陣列的資料結構,成員值都是唯一且沒有重複的值
  • 宣告:const set = new Set(arr)
  • 入參:具有Iterator介面的資料結構
  • 屬性

    • constructor:建構函式,返回Set
    • size:返回例項成員總數
  • 方法

    • add():新增值,返回例項
    • delete():刪除值,返回布林值
    • has():檢查值,返回布林值
    • clear():清除所有成員
    • keys():返回以屬性值為遍歷器的物件
    • values():返回以屬性值為遍歷器的物件
    • entries():返回以屬性值和屬性值為遍歷器的物件
    • forEach():使用回撥函式遍歷每個成員
應用場景
  • 去重字串:[...new Set(str)].join("")
  • 去重陣列:[...new Set(arr)]Array.from(new Set(arr))
  • 集合陣列

    • 宣告:const a = new Set(arr1)const b = new Set(arr2)
    • 並集:new Set([...a, ...b])
    • 交集:new Set([...a].filter(v => b.has(v)))
    • 差集:new Set([...a].filter(v => !b.has(v)))
  • 對映集合

    • 宣告:let set = new Set(arr)
    • 對映:set = new Set([...set].map(v => v * 2))set = new Set(Array.from(set, v => v * 2))
重點難點
  • 遍歷順序:插入順序
  • 沒有鍵只有值,可認為鍵和值兩值相等
  • 新增多個NaN時,只會存在一個NaN
  • 新增相同的物件時,會認為是不同的物件
  • 新增值時不會發生型別轉換(5 !== "5")
  • keys()values()的行為完全一致,entries()返回的遍歷器同時包括鍵和值且兩值相等
WeakSet
  • 定義:和Set結構類似,成員值只能是物件
  • 宣告:const set = new WeakSet(arr)
  • 入參:具有Iterator介面的資料結構
  • 屬性

    • constructor:建構函式,返回WeakSet
  • 方法

    • add():新增值,返回例項
    • delete():刪除值,返回布林值
    • has():檢查值,返回布林值
應用場景
  • 儲存DOM節點:DOM節點被移除時自動釋放此成員,不用擔心這些節點從文件移除時會引發記憶體洩漏
  • 臨時存放一組物件或存放跟物件繫結的資訊:只要這些物件在外部消失,它在WeakSet結構中的引用就會自動消
重點難點
  • 成員都是弱引用,垃圾回收機制不考慮WeakSet結構對此成員的引用
  • 成員不適合引用,它會隨時消失,因此ES6規定WeakSet結構不可遍歷
  • 其他物件不再引用成員時,垃圾回收機制會自動回收此成員所佔用的記憶體,不考慮此成員是否還存在於WeakSet結構

Map

Map
  • 定義:類似於物件的資料結構,成員鍵可以是任何型別的值
  • 宣告:const set = new Map(arr)
  • 入參:具有Iterator介面且每個成員都是一個雙元素陣列的資料結構
  • 屬性

    • constructor:建構函式,返回Map
    • size:返回例項成員總數
  • 方法

    • get():返回鍵值對
    • set():新增鍵值對,返回例項
    • delete():刪除鍵值對,返回布林值
    • has():檢查鍵值對,返回布林值
    • clear():清除所有成員
    • keys():返回以鍵為遍歷器的物件
    • values():返回以值為遍歷器的物件
    • entries():返回以鍵和值為遍歷器的物件
    • forEach():使用回撥函式遍歷每個成員
重點難點
  • 遍歷順序:插入順序
  • 對同一個鍵多次賦值,後面的值將覆蓋前面的值
  • 對同一個物件的引用,被視為一個鍵
  • 對同樣值的兩個例項,被視為兩個鍵
  • 鍵跟記憶體地址繫結,只要記憶體地址不一樣就視為兩個鍵
  • 新增多個以NaN作為鍵時,只會存在一個以NaN作為鍵的值
  • Object結構提供字串—值的對應,Map結構提供值—值的對應
WeakMap
  • 定義:和Map結構類似,成員鍵只能是物件
  • 宣告:const set = new WeakMap(arr)
  • 入參:具有Iterator介面且每個成員都是一個雙元素陣列的資料結構
  • 屬性

    • constructor:建構函式,返回WeakMap
  • 方法

    • get():返回鍵值對
    • set():新增鍵值對,返回例項
    • delete():刪除鍵值對,返回布林值
    • has():檢查鍵值對,返回布林值
應用場景
  • 儲存DOM節點:DOM節點被移除時自動釋放此成員鍵,不用擔心這些節點從文件移除時會引發記憶體洩漏
  • 部署私有屬性:內部屬性是例項的弱引用,刪除例項時它們也隨之消失,不會造成記憶體洩漏
重點難點
  • 成員鍵都是弱引用,垃圾回收機制不考慮WeakMap結構對此成員鍵的引用
  • 成員鍵不適合引用,它會隨時消失,因此ES6規定WeakMap結構不可遍歷
  • 其他物件不再引用成員鍵時,垃圾回收機制會自動回收此成員所佔用的記憶體,不考慮此成員是否還存在於WeakMap結構
  • 一旦不再需要,成員會自動消失,不用手動刪除引用
  • 弱引用的只是鍵而不是值,值依然是正常引用
  • 即使在外部消除了成員鍵的引用,內部的成員值依然存在

Proxy

  • 定義:修改某些操作的預設行為
  • 宣告:const proxy = new Proxy(target, handler)
  • 入參

    • target:攔截的目標物件
    • handler:定製攔截行為
  • 方法

    • Proxy.revocable():返回可取消的Proxy例項(返回{ proxy, revoke },通過revoke()取消代理)
  • 攔截方式

    • get():攔截物件屬性讀取
    • set():攔截物件屬性設定,返回布林值
    • has():攔截物件屬性檢查k in obj,返回布林值
    • deleteProperty():攔截物件屬性刪除delete obj[k],返回布林值
    • defineProperty():攔截物件屬性定義Object.defineProperty()Object.defineProperties(),返回布林值
    • ownKeys():攔截物件屬性遍歷for-inObject.keys()Object.getOwnPropertyNames()Object.getOwnPropertySymbols(),返回陣列
    • getOwnPropertyDescriptor():攔截物件屬性描述讀取Object.getOwnPropertyDescriptor(),返回物件
    • getPrototypeOf():攔截物件原型讀取instanceofObject.getPrototypeOf()Object.prototype.__proto__Object.prototype.isPrototypeOf()Reflect.getPrototypeOf(),返回物件
    • setPrototypeOf():攔截物件原型設定Object.setPrototypeOf(),返回布林值
    • isExtensible():攔截物件是否可擴充套件讀取Object.isExtensible(),返回布林值
    • preventExtensions():攔截物件不可擴充套件設定Object.preventExtensions(),返回布林值
    • apply():攔截Proxy例項作為函式呼叫proxy()proxy.apply()proxy.call()
    • construct():攔截Proxy例項作為建構函式呼叫new proxy()
應用場景
  • Proxy.revocable():不允許直接訪問物件,必須通過代理訪問,一旦訪問結束就收回代理權不允許再次訪問
  • get():讀取未知屬性報錯、讀取陣列負數索引的值、封裝鏈式操作、生成DOM巢狀節點
  • set():資料繫結(Vue資料繫結實現原理)、確保屬性值設定符合要求、防止內部屬性被外部讀寫
  • has():隱藏內部屬性不被發現、排除不符合屬性條件的物件
  • deleteProperty():保護內部屬性不被刪除
  • defineProperty():阻止屬性被外部定義
  • ownKeys():保護內部屬性不被遍歷
重點難點
  • 要使Proxy起作用,必須針對例項進行操作,而不是針對目標物件進行操作
  • 沒有設定任何攔截時,等同於直接通向原物件
  • 屬性被定義為不可讀寫/擴充套件/配置/列舉時,使用攔截方法會報錯
  • 代理下的目標物件,內部this指向Proxy代理

Reflect

  • 定義:保持Object方法的預設行為
  • 方法

    • get():返回物件屬性
    • set():設定物件屬性,返回布林值
    • has():檢查物件屬性,返回布林值
    • deleteProperty():刪除物件屬性,返回布林值
    • defineProperty():定義物件屬性,返回布林值
    • ownKeys():遍歷物件屬性,返回陣列(Object.getOwnPropertyNames()+Object.getOwnPropertySymbols())
    • getOwnPropertyDescriptor():返回物件屬性描述,返回物件
    • getPrototypeOf():返回物件原型,返回物件
    • setPrototypeOf():設定物件原型,返回布林值
    • isExtensible():返回物件是否可擴充套件,返回布林值
    • preventExtensions():設定物件不可擴充套件,返回布林值
    • apply():繫結this後執行指定函式
    • construct():呼叫建構函式建立例項
設計目的
  • Object屬於語言內部的方法放到Reflect
  • 將某些Object方法報錯情況改成返回false
  • Object操作變成函式行為
  • ProxyReflect相輔相成
廢棄方法
  • Object.defineProperty() => Reflect.defineProperty()
  • Object.getOwnPropertyDescriptor() => Reflect.getOwnPropertyDescriptor()
重點難點
  • Proxy方法Reflect方法一一對應
  • ProxyReflect聯合使用,前者負責攔截賦值操作,後者負責完成賦值操作
資料繫結:觀察者模式
const observerQueue = new Set();
const observe = fn => observerQueue.add(fn);
const observable = obj => new Proxy(obj, {
    set(tgt, key, val, receiver) {
        const result = Reflect.set(tgt, key, val, receiver);
        observerQueue.forEach(v => v());
        return result;
    }
});

const person = observable({ age: 25, name: "Yajun" });
const print = () => console.log(`${person.name} is ${person.age} years old`);
observe(print);
person.name = "Joway";

Class

  • 定義:對一類具有共同特徵的事物的抽象(建構函式語法糖)
  • 原理:類本身指向建構函式,所有方法定義在prototype上,可看作建構函式的另一種寫法(Class === Class.prototype.constructor)
  • 方法和關鍵字

    • constructor():建構函式,new命令生成例項時自動呼叫
    • extends:繼承父類
    • super:新建父類的this
    • static:定義靜態屬性方法
    • get:取值函式,攔截屬性的取值行為
    • set:存值函式,攔截屬性的存值行為
  • 屬性

    • __proto__建構函式的繼承(總是指向父類)
    • __proto__.__proto__:子類的原型的原型,即父類的原型(總是指向父類的__proto__)
    • prototype.__proto__屬性方法的繼承(總是指向父類的prototype)
  • 靜態屬性:定義類完成後賦值屬性,該屬性不會被例項繼承,只能通過類來呼叫
  • 靜態方法:使用static定義方法,該方法不會被例項繼承,只能通過類來呼叫(方法中的this指向類,而不是例項)
  • 繼承

    • 實質

      • ES5實質:先創造子類例項的this,再將父類的屬性方法新增到this上(Parent.apply(this))
      • ES6實質:先將父類例項的屬性方法加到this上(呼叫super()),再用子類建構函式修改this
    • super

      • 作為函式呼叫:只能在建構函式中呼叫super(),內部this指向繼承的當前子類(super()呼叫後才可在建構函式中使用this)
      • 作為物件呼叫:在普通方法中指向父類的原型物件,在靜態方法中指向父類
    • 顯示定義:使用constructor() { super(); }定義繼承父類,沒有書寫則顯示定義
    • 子類繼承父類:子類使用父類的屬性方法時,必須在建構函式中呼叫super(),否則得不到父類的this

      • 父類靜態屬性方法可被子類繼承
      • 子類繼承父類後,可從super上呼叫父類靜態屬性方法
  • 例項:類相當於例項的原型,所有在類中定義的屬性方法都會被例項繼承

    • 顯式指定屬性方法:使用this指定到自身上(使用Class.hasOwnProperty()可檢測到)
    • 隱式指定屬性方法:直接宣告定義在物件原型上(使用Class.__proto__.hasOwnProperty()可檢測到)
  • 表示式

    • 類表示式:const Class = class {}
    • name屬性:返回緊跟class後的類名
    • 屬性表示式:[prop]
    • Generator方法:* mothod() {}
    • Async方法:async mothod() {}
  • this指向:解構例項屬性或方法時會報錯

    • 繫結this:this.mothod = this.mothod.bind(this)
    • 箭頭函式:this.mothod = () => this.mothod()
  • 屬性定義位置

    • 定義在建構函式中並使用this指向
    • 定義在類最頂層
  • new.target:確定建構函式是如何呼叫
原生建構函式
  • String()
  • Number()
  • Boolean()
  • Array()
  • Object()
  • Function()
  • Date()
  • RegExp()
  • Error()
重點難點
  • 在例項上呼叫方法,實質是呼叫原型上的方法
  • Object.assign()可方便地一次向類新增多個方法(Object.assign(Class.prototype, { ... }))
  • 類內部所有定義的方法是不可列舉的(non-enumerable)
  • 建構函式預設返回例項物件(this),可指定返回另一個物件
  • 取值函式和存值函式設定在屬性的Descriptor物件
  • 類不存在變數提升
  • 利用new.target === Class寫出不能獨立使用必須繼承後才能使用的類
  • 子類繼承父類後,this指向子類例項,通過super對某個屬性賦值,賦值的屬性會變成子類例項的屬性
  • 使用super時,必須顯式指定是作為函式還是作為物件使用
  • extends不僅可繼承類還可繼承原生的建構函式
私有屬性方法
const name = Symbol("name");
const print = Symbol("print");
class Person {
    constructor(age) {
        this[name] = "Bruce";
        this.age = age;
    }
    [print]() {
        console.log(`${this[name]} is ${this.age} years old`);
    }
}
繼承混合類
function CopyProperties(target, source) {
    for (const key of Reflect.ownKeys(source)) {
        if (key !== "constructor" && key !== "prototype" && key !== "name") {
            const desc = Object.getOwnPropertyDescriptor(source, key);
            Object.defineProperty(target, key, desc);
        }
    }
}
function MixClass(...mixins) {
    class Mix {
        constructor() {
            for (const mixin of mixins) {
                CopyProperties(this, new mixin());
            }
        }
    }
    for (const mixin of mixins) {
        CopyProperties(Mix, mixin);
        CopyProperties(Mix.prototype, mixin.prototype);
    }
    return Mix;
}
class Student extends MixClass(Person, Kid) {}

Module

  • 命令

    • export:規定模組對外介面

      • 預設匯出export default Person(匯入時可指定模組任意名稱,無需知曉內部真實名稱)
      • 單獨匯出export const name = "Bruce"
      • 按需匯出export { age, name, sex }(推薦)
      • 改名匯出export { name as newName }
    • import:匯入模組內部功能

      • 預設匯入import Person from "person"
      • 整體匯入import * as Person from "person"
      • 按需匯入import { age, name, sex } from "person"
      • 改名匯入import { name as newName } from "person"
      • 自執匯入import "person"
      • 複合匯入import Person, { name } from "person"
    • 複合模式export命令import命令結合在一起寫成一行,變數實質沒有被匯入當前模組,相當於對外轉發介面,導致當前模組無法直接使用其匯入變數

      • 預設匯入匯出export { default } from "person"
      • 整體匯入匯出export * from "person"
      • 按需匯入匯出export { age, name, sex } from "person"
      • 改名匯入匯出export { name as newName } from "person"
      • 具名改預設匯入匯出export { name as default } from "person"
      • 預設改具名匯入匯出export { default as name } from "person"
  • 繼承:預設匯出改名匯出結合使用可使模組具備繼承性
  • 設計思想:儘量地靜態化,使得編譯時就能確定模組的依賴關係,以及輸入和輸出的變數
  • 嚴格模式:ES6模組自動採用嚴格模式(不管模組頭部是否新增use strict)
模組方案
  • CommonJS:用於伺服器(動態化依賴)
  • AMD:用於瀏覽器(動態化依賴)
  • CMD:用於瀏覽器(動態化依賴)
  • UMD:用於瀏覽器和伺服器(動態化依賴)
  • ESM:用於瀏覽器和伺服器(靜態化依賴)
載入方式
  • 執行時載入

    • 定義:整體載入模組生成一個物件,再從物件上獲取需要的屬性和方法進行載入(全部載入)
    • 影響:只有執行時才能得到這個物件,導致無法在編譯時做靜態優化
  • 編譯時載入

    • 定義:直接從模組中獲取需要的屬性和方法進行載入(按需載入)
    • 影響:在編譯時就完成模組載入,效率比其他方案高,但無法引用模組本身(本身不是物件),可擴充JS高階語法(巨集和型別校驗)
載入實現
  • 傳統載入:通過<script>進行同步或非同步載入指令碼

    • 同步載入:<script src=""></script>
    • Defer非同步載入:<script src="" defer></script>(順序載入,渲染完再執行)
    • Async非同步載入:<script src="" async></script>(亂序載入,下載完就執行)
  • 模組載入<script type="module" src=""></script>(預設是Defer非同步載入)
CommonJS和ESM的區別
  • CommonJS輸出值的拷貝ESM輸出值的引用

    • CommonJS一旦輸出一個值,模組內部的變化就影響不到這個值
    • ESM是動態引用且不會快取值,模組裡的變數繫結其所在的模組,等到指令碼真正執行時,再根據這個只讀引用到被載入的那個模組裡去取值
  • CommonJS是執行時載入,ESM是編譯時載入

    • CommonJS載入模組是物件(即module.exports),該物件只有在指令碼執行完才會生成
    • ESM載入模組不是物件,它的對外介面只是一種靜態定義,在程式碼靜態解析階段就會生成
Node載入
  • 背景:CommonJSESM互不相容,目前解決方案是將兩者分開,採用各自的載入方案
  • 區分:要求ESM採用.mjs字尾檔名

    • require()不能載入.mjs檔案,只有import命令才可載入.mjs檔案
    • .mjs檔案裡不能使用require(),必須使用import命令載入檔案
  • 驅動:node --experimental-modules file.mjs
  • 限制:Node的import命令目前只支援載入本地模組(file:協議),不支援載入遠端模組
  • 載入優先順序

    • 指令碼檔案省略字尾名:依次嘗試載入四個字尾名檔案(.mjs.js.jsonnode)
    • 以上不存在:嘗試載入package.jsonmain欄位指定的指令碼
    • 以上不存在:依次嘗試載入名稱為index四個字尾名檔案(.mjs.js.jsonnode)
    • 以上不存在:報錯
  • 不存在的內部變數:argumentsexportsmodulerequirethis__dirname__filename
  • CommonJS載入ESM

    • 不能使用require(),只能使用import()
  • ESM載入CommonJS

    • 自動將module.exports轉化成export default
    • CommonJS輸出快取機制在ESM載入方式下依然有效
    • 採用import命令載入CommonJS模組時,不允許採用按需匯入,應使用預設匯入整體匯入
迴圈載入
  • 定義:指令碼A的執行依賴指令碼B,而指令碼A的執行又依賴指令碼B
  • 載入原理

    • CommonJS:require()首次載入指令碼就會執行整個指令碼,在記憶體裡生成一個物件快取下來,二次載入指令碼時直接從快取中獲取
    • ESM:import命令載入變數不會被快取,而是成為一個指向被載入模組的引用
  • 迴圈載入

    • CommonJS:只輸出已經執行的部分,還未執行的部分不會輸出
    • ESM:需開發者自己保證真正取值時能夠取到值(可把變數寫成函式形式,函式具有提升作用)
重點難點
  • ES6模組中,頂層this指向undefined,不應該在頂層程式碼使用this
  • 一個模組就是一個獨立的檔案,該檔案內部的所有變數,外部無法獲取
  • export命令輸出的介面與其對應的值是動態繫結關係,即通過該介面可獲取模組內部實時的值
  • import命令大括號裡的變數名必須與被匯入模組對外介面的名稱相同
  • import命令輸入的變數只讀(本質是輸入介面),不允許在載入模組的指令碼里改寫介面
  • import命令命令具有提升效果,會提升到整個模組的頭部,首先執行
  • 重複執行同一句import語句,只會執行一次
  • export default命令只能使用一次
  • export default命令匯出的整體模組,在執行import命令時其後不能跟大括號
  • export default命令本質是輸出一個名為default的變數,後面不能跟變數宣告語句
  • export default命令本質是將後面的值賦給名為default的變數,可直接將值寫在其後
  • export default命令export {}命令可同時存在,對應複合匯入
  • export命令import命令可出現在模組任何位置,只要處於模組頂層即可,不能處於塊級作用域
  • import()載入模組成功後,此模組會作為一個物件,當作then()的引數,可使用物件解構賦值來獲取輸出介面
  • 同時動態載入多個模組時,可使用Promise.all()import()相結合來實現
  • import()和結合async/await來書寫同步操作的程式碼
單例模式:跨模組常量
// 常量跨檔案共享
// person.js
const NAME = "Bruce";
const AGE = 25;
const SEX = "male";
export { AGE, NAME, SEX };
// file1.js
import { AGE } from "person";
console.log(AGE);
// file2.js
import { AGE, NAME, SEX } from "person";
console.log(AGE, NAME, SEX);
預設匯入互換整體匯入
import Person from "person";
console.log(Person.AGE);
import * as Person from "person";
console.log(Person.default.AGE);

Iterator

  • 定義:為各種不同的資料結構提供統一的訪問機制
  • 原理:建立一個指標指向首個成員,按照次序使用next()指向下一個成員,直接到結束位置(資料結構只要部署Iterator介面就可完成遍歷操作)
  • 作用

    • 為各種資料結構提供一個統一的簡便的訪問介面
    • 使得資料結構成員能夠按某種次序排列
    • ES6創造了新的遍歷命令for-ofIterator介面主要供for-of消費
  • 形式:for-of(自動去尋找Iterator介面)
  • 資料結構

    • 集合:ArrayObjectSetMap
    • 原生具備介面的資料結構:StringArraySetMapTypedArrayArgumentsNodeList
  • 部署:預設部署在Symbol.iterator(具備此屬性被認為可遍歷的iterable)
  • 遍歷器物件

    • next():下一步操作,返回{ done, value }(必須部署)
    • return()for-of提前退出呼叫,返回{ done: true }
    • throw():不使用,配合Generator函式使用
ForOf迴圈
  • 定義:呼叫Iterator介面產生遍歷器物件(for-of內部呼叫資料結構的Symbol.iterator())
  • 遍歷字串:for-in獲取索引for-of獲取(可識別32位UTF-16字元)
  • 遍歷陣列:for-in獲取索引for-of獲取
  • 遍歷物件:for-in獲取for-of需自行部署
  • 遍歷Set:for-of獲取 => for (const v of set)
  • 遍歷Map:for-of獲取鍵值對 => for (const [k, v] of map)
  • 遍歷類陣列:包含length的物件Arguments物件NodeList物件(無Iterator介面的類陣列可用Array.from()轉換)
  • 計算生成資料結構:ArraySetMap

    • keys():返回遍歷器物件,遍歷所有的鍵
    • values():返回遍歷器物件,遍歷所有的值
    • entries():返回遍歷器物件,遍歷所有的鍵值對
  • for-in區別

    • 有著同for-in一樣的簡潔語法,但沒有for-in那些缺點、
    • 不同於forEach(),它可與breakcontinuereturn配合使用
    • 提供遍歷所有資料結構的統一操作介面
應用場景
  • 改寫具有Iterator介面的資料結構的Symbol.iterator
  • 解構賦值:對Set進行結構
  • 擴充套件運算子:將部署Iterator介面的資料結構轉為陣列
  • yield*:yield*後跟一個可遍歷的資料結構,會呼叫其遍歷器介面
  • 接受陣列作為引數的函式:for-ofArray.from()new Set()new WeakSet()new Map()new WeakMap()Promise.all()Promise.race()

Promise

  • 定義:包含非同步操作結果的物件
  • 狀態

    • 進行中pending
    • 已成功resolved
    • 已失敗rejected
  • 特點

    • 物件的狀態不受外界影響
    • 一旦狀態改變就不會再變,任何時候都可得到這個結果
  • 宣告:new Promise((resolve, reject) => {})
  • 出參

    • resolve:將狀態從未完成變為成功,在非同步操作成功時呼叫,並將非同步操作的結果作為引數傳遞出去
    • reject:將狀態從未完成變為失敗,在非同步操作失敗時呼叫,並將非同步操作的錯誤作為引數傳遞出去
  • 方法

    • then():分別指定resolved狀態rejected狀態的回撥函式

      • 第一引數:狀態變為resolved時呼叫
      • 第二引數:狀態變為rejected時呼叫(可選)
    • catch():指定發生錯誤時的回撥函式
    • Promise.all():將多個例項包裝成一個新例項,返回全部例項狀態變更後的結果陣列(齊變更再返回)

      • 入參:具有Iterator介面的資料結構
      • 成功:只有全部例項狀態變成resolved,最終狀態才會變成resolved
      • 失敗:其中一個例項狀態變成rejected,最終狀態就會變成rejected
    • Promise.race():將多個例項包裝成一個新例項,返回全部例項狀態優先變更後的結果(先變更先返回)
    • Promise.resolve():將物件轉為Promise物件(等價於new Promise(resolve => resolve()))

      • Promise例項:原封不動地返回入參
      • Thenable物件:將此物件轉為Promise物件並返回(Thenable為包含then()的物件,執行then()相當於執行此物件的then())
      • 不具有then()的物件:將此物件轉為Promise物件並返回,狀態為resolved
      • 不帶引數:返回Promise物件,狀態為resolved
    • Promise.reject():將物件轉為狀態為rejected的Promise物件(等價於new Promise((resolve, reject) => reject()))
應用場景
  • 載入圖片
  • AJAX轉Promise物件
重點難點
  • 只有非同步操作的結果可決定當前狀態是哪一種,其他操作都無法改變這個狀態
  • 狀態改變只有兩種可能:從pending變為resolved、從pending變為rejected
  • 一旦新建Promise物件就會立即執行,無法中途取消
  • 不設定回撥函式,內部拋錯不會反應到外部
  • 當處於pending時,無法得知目前進展到哪一個階段
  • 例項狀態變為resolvedrejected時,會觸發then()繫結的回撥函式
  • resolve()reject()的執行總是晚於本輪迴圈的同步任務
  • then()返回新例項,其後可再呼叫另一個then()
  • then()執行中丟擲錯誤會被catch()捕獲
  • reject()的作用等同於丟擲錯誤
  • 例項狀態已變成resolved時,再丟擲錯誤是無效的,不會被捕獲,等於沒有丟擲
  • 例項狀態的錯誤具有冒泡性質,會一直向後傳遞直到被捕獲為止,錯誤總是會被下一個catch()捕獲
  • 不要在then()裡定義rejected狀態的回撥函式(不使用其第二引數)
  • 建議使用catch()捕獲錯誤,不要使用then()第二個引數捕獲
  • 沒有使用catch()捕獲錯誤,例項拋錯不會傳遞到外層程式碼,即不會有任何反應
  • 作為引數的例項定義了catch(),一旦被rejected並不會觸發Promise.all()catch()
  • Promise.reject()的引數會原封不動地作為rejected的理由,變成後續方法的引數

Generator

  • 定義:封裝多個內部狀態的非同步程式設計解決方案
  • 形式:呼叫Generator函式(該函式不執行)返回指向內部狀態的指標物件(不是執行結果)
  • 宣告:function* Func() {}
  • 方法

    • next():使指標移向下一個狀態,返回{ done, value }(入參會被當作上一個yield命令表示式的返回值)
    • return():返回指定值且終結遍歷Generator函式,返回{ done: true, value: 入參 }
    • throw():在Generator函式體外丟擲錯誤,在Generator函式體內捕獲錯誤,返回自定義的new Errow()
  • yield命令:宣告內部狀態的值(return宣告結束返回的值)

    • 遇到yield命令就暫停執行後面的操作,並將其後表示式的值作為返回物件的value
    • 下次呼叫next()時,再繼續往下執行直到遇到下一個yield命令
    • 沒有再遇到yield命令就一直執行到Generator函式結束,直到遇到return語句為止並將其後表示式的值作為返回物件的value
    • Generator函式沒有return語句則返回物件的valueundefined
  • yield*命令:在一個Generator函式裡執行另一個Generator函式(後隨具有Iterator介面的資料結構)
  • 遍歷:通過for-of自動呼叫next()
  • 作為物件屬性

    • 全寫:const obj = { method: function*() {} }
    • 簡寫:const obj = { * method() {} }
  • 上下文:執行產生的上下文環境一旦遇到yield命令就會暫時退出堆疊(但並不消失),所有變數和物件會凍結在當前狀態,等到對它執行next()時,這個上下文環境又會重新加入呼叫棧,凍結的變數和物件恢復執行
方法異同
  • 相同點:next()throw()return()本質上是同一件事,作用都是讓函式恢復執行且使用不同的語句替換yield命令
  • 不同點

    • next():將yield命令替換成一個
    • return():將yield命令替換成一個return語句
    • throw():將yield命令替換成一個throw語句
應用場景
  • 非同步操作同步化表達
  • 控制流管理
  • 為物件部署Iterator介面:把Generator函式賦值給物件的Symbol.iterator,從而使該物件具有Iterator介面
  • 作為具有Iterator介面的資料結構
重點難點
  • 每次呼叫next(),指標就從函式頭部上次停下的位置開始執行,直到遇到下一個yield命令return語句為止
  • 函式內部可不用yield命令,但會變成單純的暫緩執行函式(還是需要next()觸發)
  • yield命令是暫停執行的標記,next()是恢復執行的操作
  • yield命令用在另一個表示式中必須放在圓括號
  • yield命令用作函式引數或放在賦值表示式的右邊,可不加圓括號
  • yield命令本身沒有返回值,可認為是返回undefined
  • yield命令表示式為惰性求值,等next()執行到此才求值
  • 函式呼叫後生成遍歷器物件,此物件的Symbol.iterator是此物件本身
  • 在函式執行的不同階段,通過next()從外部向內部注入不同的值,從而調整函式行為
  • 首個next()用來啟動遍歷器物件,後續才可傳遞引數
  • 想首次呼叫next()時就能輸入值,可在函式外面再包一層
  • 一旦next()返回物件的donetruefor-of遍歷會中止且不包含該返回物件
  • 函式內部部署try-finally且正在執行try,那麼return()會導致立刻進入finally,執行完finally以後整個函式才會結束
  • 函式內部沒有部署try-catchthrow()拋錯將被外部try-catch捕獲
  • throw()拋錯要被內部捕獲,前提是必須至少執行過一次next()
  • throw()被捕獲以後,會附帶執行下一條yield命令
  • 函式還未開始執行,這時throw()拋錯只可能丟擲在函式外部
首次next()可傳值
function Wrapper(func) {
    return function(...args) {
        const generator = func(...args);
        generator.next();
        return generator;
    }
}
const print = Wrapper(function*() {
    console.log(`First Input: ${yield}`);
    return "done";
});
print().next("hello");

ES2016

ES2016

數值擴充套件

  • 指數運算子(**):數值求冪(相當於Math.pow())

陣列擴充套件

  • includes():是否存在指定成員

ES2017

ES2017

宣告

  • 共享記憶體和原子操作:由全域性物件SharedArrayBufferAtomics實現,將資料儲存在一塊共享記憶體空間中,這些資料可在JS主執行緒web-worker執行緒之間共享

字串擴充套件

  • padStart():把指定字串填充到字串頭部,返回新字串
  • padEnd():把指定字串填充到字串尾部,返回新字串

物件擴充套件

  • Object.getOwnPropertyDescriptors():返回物件所有自身屬性(非繼承屬性)的描述物件
  • Object.values():返回以值組成的陣列
  • Object.entries():返回以鍵和值組成的陣列

函式擴充套件

  • 函式引數尾逗號:允許函式最後一個引數有尾逗號

Async

  • 定義:使非同步函式以同步函式的形式書寫(Generator函式語法糖)
  • 原理:將Generator函式和自動執行器spawn包裝在一個函式裡
  • 形式:將Generator函式*替換成async,將yield替換成await
  • 宣告

    • 具名函式:async function Func() {}
    • 函式表示式:const func = async function() {}
    • 箭頭函式:const func = async() => {}
    • 物件方法:const obj = { async func() {} }
    • 類方法:class Cla { async Func() {} }
  • await命令:等待當前Promise物件狀態變更完畢

    • 正常情況:後面是Promise物件則返回其結果,否則返回對應的值
    • 後隨Thenable物件:將其等同於Promise物件返回其結果
  • 錯誤處理:將await命令Promise物件放到try-catch中(可放多個)
Async對Generator改進
  • 內建執行器
  • 更好的語義
  • 更廣的適用性
  • 返回值是Promise物件
應用場景
  • 按順序完成非同步操作
重點難點
  • Async函式返回Promise物件,可使用then()新增回撥函式
  • 內部return返回值會成為後續then()的出參
  • 內部丟擲錯誤會導致返回的Promise物件變為rejected狀態,被catch()接收到
  • 返回的Promise物件必須等到內部所有await命令Promise物件執行完才會發生狀態改變,除非遇到return語句丟擲錯誤
  • 任何一個await命令Promise物件變為rejected狀態,整個Async函式都會中斷執行
  • 希望即使前一個非同步操作失敗也不要中斷後面的非同步操作

    • await命令Promise物件放到try-catch
    • await命令Promise物件跟一個catch()
  • await命令Promise物件可能變為rejected狀態,最好把其放到try-catch
  • 多個await命令Promise物件若不存在繼發關係,最好讓它們同時觸發
  • await命令只能用在Async函式之中,否則會報錯
  • 陣列使用forEach()執行async/await會失效,可使用for-ofPromise.all()代替
  • 可保留執行堆疊,函式上下文隨著Async函式的執行而存在,執行完成就消失

ES2018

ES2018

字串擴充套件

  • 放鬆對標籤模板裡字串轉義的限制:遇到不合法的字串轉義返回undefined,並且從raw上可獲取原字串

物件擴充套件

  • 擴充套件運算子(...):轉換物件為用逗號分隔的引數序列({ ...obj },相當於rest/spread引數的逆運算)
擴充套件應用
  • 克隆物件:const obj = { __proto__: Object.getPrototypeOf(obj1), ...obj1 }
  • 合併物件:const obj = { ...obj1, ...obj2 }
  • 轉換字串為物件:{ ..."hello" }
  • 轉換陣列為物件:{ ...[1, 2] }
  • 與物件解構賦值結合:const { x, ...rest/spread } = { x: 1, y: 2, z: 3 }(不能複製繼承自原型物件的屬性)
  • 修改現有物件部分屬性:const obj = { x: 1, ...{ x: 2 } }

正則擴充套件

  • s修飾符:dotAll模式修飾符,使.匹配任意單個字元(dotAll模式)
  • dotAll:是否設定s修飾符
  • 後行斷言x只有在y後才匹配
  • 後行否定斷言x只有不在y後才匹配
  • Unicode屬性轉義:匹配符合Unicode某種屬性的所有字元

    • 正向匹配:\p{PropRule}
    • 反向匹配:\P{PropRule}
    • 限制:\p{...}\P{...}只對Unicode字元有效,使用時需加上u修飾符
  • 具名組匹配:為每組匹配指定名字(?<GroupName>)

    • 形式:str.exec().groups.GroupName
    • 解構賦值替換

      • 宣告:const time = "2017-09-11"const regexp = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u
      • 匹配:time.replace(regexp, "$<day>/$<month>/$<year>")

Promise

  • finally():指定不管最後狀態如何都會執行的回撥函式

Async

  • 非同步迭代器(for-wait-of):,迴圈等待每個Promise物件變為resolved狀態才進入下一步

ES2019

ES2019

字串擴充套件

  • 直接輸入U+2028和U+2029:字串可直接輸入行分隔符段分隔符
  • JSON.stringify()改造:可返回不符合UTF-8標準的字串
  • trimStart():消除字串頭部空格,返回新字串
  • trimEnd():消除字串尾部空格,返回新字串

物件擴充套件

  • Object.fromEntries():返回以鍵和值組成的物件(Object.entries()的逆操作)

陣列擴充套件

  • flat():扁平化陣列,返回新陣列
  • flatMap():對映且扁平化陣列,返回新陣列(只能展開一層陣列)

函式擴充套件

  • toString()改造:返回函式原始程式碼(與編碼一致)
  • catch()引數可省略catch()中的引數可省略

Symbol

  • description:返回Symbol值的描述

ES提案

ES提案

宣告

  • globalThis物件:作為頂層物件,指向全域性環境下的this
  • do表示式:封裝塊級作用域的操作,返回內部最後執行表示式的值(do{})
  • throw表示式:直接使用throw new Error(),無需(){}包括
  • !#命令:指定指令碼執行器(寫在檔案首行)

數值擴充套件

  • 數值分隔符(_):使用_作為千分位分隔符(增加數值的可讀性)
  • BigInt():建立任何位數的整數(新增的資料型別,使用n結尾)

物件擴充套件

  • 鏈判斷操作符(?.):是否存在物件屬性(不存在返回undefined且不再往下執行)
  • 空判斷操作符(??):是否值為undefinednull,是則使用預設值

函式擴充套件

  • 函式部分執行:複用函式功能(?表示單個引數佔位符,...表示多個引數佔位符)
  • 管道操作符(|>):把左邊表示式的值傳入右邊的函式進行求值(f(x) => x |> f)
  • 繫結運算子(::):函式繫結(左邊是物件右邊是函式,取代bindapplycall呼叫)

    • bind:bar.bind(foo) => foo::bar
    • apply:bar.apply(foo, arguments) => foo::bar(...arguments)

Proxy

  • Promise.try():不想區分是否同步非同步函式,包裝函式為例項,使用then()指定下一步流程,使用catch()捕獲錯誤

Realm

  • 定義:提供沙箱功能,允許隔離程式碼,防止被隔離的程式碼拿到全域性物件
  • 宣告:new Realm().global

Class

  • 靜態屬性:使用static定義屬性,該屬性不會被例項繼承,只能通過類來呼叫
  • 私有屬性:使用#定義屬性,該屬性只能在類內部訪問
  • 私有方法:使用#定義方法,該方法只能在類內部訪問
  • 裝飾器:使用@註釋或修改類和類方法

Module

  • import():動態匯入(返回Promise)

    • 背景:import命令被JS引擎靜態分析,先於模組內的其他語句執行,無法取代require()的動態載入功能,提案建議引入import()來代替require()
    • 位置:可在任何地方使用
    • 區別:require()同步載入import()非同步載入
    • 場景:按需載入、條件載入、模組路徑動態化
  • import.meta:返回指令碼元資訊

Async

  • 頂層Await:允許在模組的頂層獨立使用await命令(借用await解決模組非同步載入的問題)

結語

寫到最後總結得差不多了,後續如果我想起還有哪些ES6特性遺漏的,會繼續在這篇文章上補全,同時也希望各位倔友對文章裡的要點進行補充或者提出自己的見解。歡迎在下方進行評論或補充喔,喜歡的點個贊收個藏,保證你在開發時用得上。

最後送大家一張完整的ES6特性圖,記得給我點個贊喔,算是對我一種鼓勵啦,哈哈!

ES6