閱讀《深刻理解ES6》書籍,筆記整理(上)

因爲所有筆記有接近4W的字數,所以分開爲上、下兩部分,第二部份內容計劃於明後兩天更新。
若是你以爲寫的不錯請給一個star,若是你想閱讀上、下兩部分所有的筆記,請點擊全文javascript

前言

ECMAScript6標準定稿以前,已經開始出現了一些實驗性的轉譯器(Transpiler),例如谷歌的Traceur,能夠將代碼從ECMAScript6轉換成ECMAScript5。但它們大多功能很是有限,或難以插入現有的JavaScript構建管道。
可是,隨後出現了的新型轉譯器6to5改變了這一切。它易於安裝,能夠很好的集成現有的工具中,生成的代碼可讀,因而就像野火同樣逐步蔓延開來,6to5也就是如今鼎鼎大名的Babelhtml

ECMAScript6的演變之路

JavaScript核心的語言特性是在標準ECMA-262中被定義,該標準中定義的語言被稱做ECMAScript,它是JavaScript的子集。 java

演變之路:git

  • 停滯不前:逐漸興起的Ajax開創了動態Web引用的新時代,而自1999年第三版ECMA-262發佈以來,JavaScript卻沒有絲毫的改變。
  • 轉折點:2007年,TC-39委員會將大量規範草案整合在了ECMAScript4中,其中新增的語言特性涉足甚廣,包括:模塊、類、類繼承、私有對象成員等衆多其它的特性。
  • 分歧:然而TC-39組織內部對ECMAScript4草案產生了巨大的分歧,部分紅員認爲不該該一次性在第四版標準中加入過多的新功能,而來自雅虎、谷歌和微軟的技術負責人則共同提交了一份ECMAScript3.1草案做爲下一代ECMAScript的可選方案,其中此方案只是對現有標準進行小幅度的增量修改。行爲更專一於優化屬性特性、支持原生JSON以及爲已有對象增長新的方法。
  • 從未面世的ECMAScript4:2008年,JavaScript創始人Brendan Eich宣佈TC-39委員一方面會將合理推動ECMAScript3.1的標準化工做,另外一方面會暫時將ECMAScript4標準中提出的大部分針對語法及特性的改動擱置。
  • ECMAScript5:通過標準化的ECMAScript3.1最終做爲ECMA-262第五版於2009年正式發佈,同時被命名爲ECMAScript5
  • ECMAScript6:在ECMAScript5發佈後,TC-39委員會於2013年凍結了ECMAScript6的草案,再也不添加新的功能。2013年ECMAScript6草案發布,在進過12個月各方討論和反饋後。2015年ECMAScript6正式發佈,並命名爲ECMAScript 2015

塊級做用域綁定

過去JavaScript中的變量聲明機制一直令咱們感到困惑,大多數類C語言在聲明變量的同時也會建立變量,而在之前的JavaScript中,什麼時候建立變量要看如何聲明的變量,ES6引入塊級做用域可讓咱們更好的控制做用域。 es6

var聲明和變量提高機制

問:提高機制(hoisting)是什麼?
答:在函數做用域或全局做用域中經過關鍵字var聲明的變量,不管其實是在哪裏聲明的,都會被當成在當前做用域頂部聲明的變量,這就是咱們常說的提高機制。
如下實例代碼說明了這種提高機制:github

function getValue (condition) {
  if (condition) {
    var value = 'value'
    return value
  } else {
    // 這裏能夠訪問到value,只不過值爲undefined
    console.log(value)
    return null
  }
}
getValue(false) // 輸出undefined
複製代碼

你能夠在以上代碼中看到,當咱們傳遞了false的值,但依然能夠訪問到value這個變量,這是由於在預編譯階段,JavaScript引擎會將上面函數的代碼修改爲以下形式:正則表達式

function getValue (condition) {
  var value
  if (condition) {
    value = 'value'
    return value
  } else {
    console.log(value)
    return null
  }
}
複製代碼

通過以上示例,咱們能夠發現:變量value的聲明被提高至函數做用域的頂部,而初始化操做依舊留在原處執行,正由於value變量只是聲明瞭而沒有賦值,所以以上代碼纔會打印出undefined數組

塊級聲明

塊級聲明用於聲明在指定的做用於以外無妨訪問的變量,塊級做用域存在於:函數內部和塊中。瀏覽器

let聲明:緩存

  • let聲明和var聲明的用法基本相同。
  • let聲明的變量不會被提高。
  • let不能在同一個做用域中重複聲明已經存在的變量,會報錯。
  • let聲明的變量做用域範圍僅存在於當前的塊中,程序進入塊開始時被建立,程序退出塊時被銷燬。

根據let聲明的規則,改動上面的代碼後像下面這樣:

function getValue (condition) {
  if (condition) {
    // 變量value只存在於這個塊中。
    let value = 'value'
    return value
  } else {
    // 訪問不到value變量
    console.log(value)
    return null
  }
}
複製代碼

const聲明:const聲明和let聲明大多數狀況是相同的,惟一的本質區別在於,const是用來聲明常量的,其聲明後的變量值不能再被修改,即意味着:const聲明必須進行初始化。

const MAX_ITEMS = 30
// 報錯
MAX_ITEMS = 50
複製代碼

咱們說的const變量值不可變,須要分兩種類型來講: 值類型:變量的值不能改變。 引用類型:變量的地址不能改變,值能夠改變。

const num = 23
const arr = [1, 2, 3, 4]
const obj = {
  name: 'why',
  age: 23
}

// 報錯
num = 25

// 不報錯
arr[0] = 11
obj.age = 32
console.log(arr) // [11, 2, 3, 4]
console.log(obj) // { name: 'why', age: 32 }

// 報錯
arr = [4, 3, 2, 1]
複製代碼

暫時性死區

由於letconst聲明的變量不會進行聲明提高,因此在letconst變量聲明以前任何訪問(即便是typeof也不行)此變量的操做都會引起錯誤:

if (condition) {
  // 報錯
  console.log(typeof value)
  let value = 'value'
}
複製代碼

問:爲何會報錯?
答:JavaScript引擎在掃描代碼發現變量聲明時,要麼將它們提高至做用域的頂部(var聲明),要麼將聲明放在TDZ(暫時性死區)中(letconst聲明)。訪問TDZ中的變量會觸發錯誤,只有執行變量聲明語句以後,變量纔會從TDZ中移出,隨後才能正常訪問。

全局塊做用域綁定

咱們都知道:若是咱們在全局做用域下經過var聲明一個變量,那麼這個變量會掛載到全局對象window上:

var name = 'why'
console.log(window.name) // why
複製代碼

但若是咱們使用let或者const在全局做用域下建立一個新的變量,這個變量不會添加到window上。

const name = 'why'
console.log('name' in window) // false
複製代碼

塊級綁定最佳實踐的進化

ES6早期,人們廣泛認爲應該默認使用let來代替var,這是由於對於開發者而言,let實際上與他們想要的var同樣,直接替換符合邏輯。但隨着時代的發展,另外一種作法也愈來愈普及:默認使用const,只有肯定變量的值會在後續須要修改時纔會使用let聲明,由於大部分變量在初始化後不該再改變,而預料之外的變量值改變是不少bug的源頭。

字符串

本章節中關於unicode和正則部分未整理。

模塊字面量

模板字面量是擴展ECMAScript基礎語法的語法糖,其提供了一套生成、查詢並操做來自其餘語言裏內容的DSL,且能夠免受XSS注入攻擊和SQL注入等等。

ES6以前,JavaScript一直以來缺乏許多特性:

  • 多行字符串:一個正式的多行字符串的概念。
  • 基本的字符串格式化:將變量的值嵌入字符串的能力。
  • HTML轉義:向HTML插入通過安全轉換後的字符串的能力。

而在ECMAScript 6中,經過模板字面量的方式對以上問題進行了填補,一個最簡單的模板字面量的用法以下:

const message = `hello,world!`
console.log(message)        // hello,world!
console.log(typeof message) // string
複製代碼

一個須要注意的地方就是,若是咱們須要在字符串中使用反撇號,須要使用\來進行轉義,以下:

const message = `\`hello\`,world!`
console.log(message) // `hello`,world!
複製代碼

多行字符串

JavaScript誕生起,開發者們就一直在嘗試和建立多行字符串,如下是ES6以前的方法:
在字符串的新行最前方加上\能夠承接上一行代碼,能夠利用這個小bug來建立多行字符串。

const message = 'hello\ ,world!'
console.log(message) // hello,world
複製代碼

ES6以後,咱們可使用模板字面量,在裏面直接換行就能夠建立多行字符串,以下:

在模板字面量中,即反撇號中全部空白字符都屬於字符串的一部分。

const message = `hello ,world!`
console.log(message) // hello
                     // ,world!
複製代碼

字符串佔位符

模板字面量於普通字符串最大的區別是模板字符串中的佔位符功能,其中佔位符中的內容,能夠是任意合法的JavaScript表達式,例如:變量,運算式,函數調用,甚至是另一個模板字面量。

const age = 23
const name = 'why'
const message = `Hello ${name}, you are ${age} years old!`
console.log(message) // Hello why, you are 23 years old!
複製代碼

模板字面量嵌套:

const name = 'why'
const message = `Hello, ${`my name is ${name}`}.`
console.log(message) // Hello, my name is why.
複製代碼

標籤模板

標籤指的是在模板字面量第一個反撇號前方標註的字符串,每個模板標籤均可以執行模板字面量上的轉換並返回最終的字符串值。

// tag就是`Hello world!`模板字面量的標籤模板
const message = tag`Hello world!`
複製代碼

標籤能夠是一個函數,標籤函數一般使用不定參數特性來定義佔位符,從而簡化數據處理的過程,就像下面這樣:

function tag(literals, ...substitutions) {
  // 返回一個字符串
}
const name = 'why'
const age = 23
const message = tag`${name} is ${age} years old!`
複製代碼

其中literals是一個數組,它包含:

  • 第一個佔位符前的空白字符串:""。
  • 第一個、第二個佔位符之間的字符串:" is "。
  • 第二個佔位符後的字符串:" years old!"

substitutions也是一個數組:

  • 數組第一項爲:name的值,即:why
  • 數組第二項爲:age的值,即:23

經過以上規律咱們能夠發現:

  • literals[0]始終表明字符串的開頭。
  • literals總比substitutions多一個。

咱們能夠經過以上這種模式,將literalssubstitutions這兩個數組交織在一塊兒從新組成一個字符串,來模擬模板字面量的默認行爲,像下面這樣:

function tag(literals, ...substitutions) {
  let result = ''
  for (let i = 0; i< substitutions.length; i++) {
    result += literals[i]
    result += substitutions[i]
  }

  // 合併最後一個
  result += literals[literals.length - 1]
  return result
}
const name = 'why'
const age = 23
const message = tag`${name} is ${age} years old!`
console.log(message) // why is 23 years old!
複製代碼

原生字符串信息

經過模板標籤能夠訪問到字符串轉義被轉換成等價字符串前的原生字符串。

const message1 = `Hello\nworld`
const message2 = String.raw`Hello\nworld`
console.log(message1) // Hello
                      // world
console.log(message2) // Hello\nworld
複製代碼

函數

形參默認值

ES6以前,你可能會經過如下這種模式建立函數併爲參數提供默認值:

function makeRequest (url, timeout, callback) {
  timeout = timeout || 2000
  callback = callback || function () {}
}
複製代碼

代碼分析:在以上示例中,timeoutcallback是可選參數,若是不傳入則會使用邏輯或操做符賦予默認值。然而這種方式也有必定的缺陷,若是咱們想給timeout傳遞值爲0,雖然這個值是合法的,但由於有或邏輯運算符的存在,最終仍是爲timeout賦值2000

針對以上狀況,咱們應該經過一種更安全的作法(使用typeof)來重寫一下以上示例:

function makeRequest (url, timeout, callback) {
  timeout = typeof timeout !== 'undefined' ? timeout : 2000
  callback = typeof callback !== 'undefined' ? callback : function () {} 
}
複製代碼

代碼分析:儘管以上方法更安全一些,但咱們任然須要額外的撰寫更多的代碼來實現這種很是基礎的操做。針對以上問題,ES6簡化了爲形參提供默認值的過程,就像下面這樣:

對於默認參數而言,除非不傳或者主動傳遞undefined纔會使用參數默認值(若是傳遞null,這是一個合法的參數,不會使用默認值)。

function makeRequest (url, timeout = 2000, callback = function () {}) {
  // todo
}
// 同時使用timeout和callback默認值
makeRequest('https://www.taobao.com')
// 使用callback默認值
makeRequest('https://www.taobao.com', 500)
// 不使用默認值
makeRequest('https://www.taobao.com', 500, function (res) => {
  console.log(res)
})
複製代碼

形參默認值對arguments對象的影響

ES5非嚴格模式下,若是修改參數的值,這些參數的值會同步反應到arguments對象中,以下:

function mixArgs(first, second) {
  console.log(arguments[0]) // A
  console.log(arguments[1]) // B
  first = 'a'
  second = 'b'
  console.log(arguments[0]) // a
  console.log(arguments[1]) // b
}
mixArgs('A', 'B')
複製代碼

而在ES5嚴格模式下,修改參數的值再也不反應到arguments對象中,以下:

function mixArgs(first, second) {
 'use strict'
  console.log(arguments[0]) // A
  console.log(arguments[1]) // B
  first = 'a'
  second = 'b'
  console.log(arguments[0]) // A
  console.log(arguments[1]) // B
}
mixArgs('A', 'B')
複製代碼

對於使用了ES6的形參默認值,arguments對象的行爲始終保持和ES5嚴格模式同樣,不管當前是否爲嚴格模式,即:arguments老是等於最初傳遞的值,不會隨着參數的改變而改變,老是可使用arguments對象將參數還原爲最初的值,以下:

function mixArgs(first, second = 'B') {
  console.log(arguments.length) // 1
  console.log(arguments[0])      // A
  console.log(arguments[1])      // undefined
  first = 'a'
  second = 'b'
  console.log(arguments[0])      // A
  console.log(arguments[1])      // undefined
}
// arguments對象始終等於傳遞的值,形參默認值不會反映在arguments上
mixArgs('A')
複製代碼

默認參數表達式

函數形參默認值,除了能夠是原始值的默認值,也能夠是表達式,即:變量,函數調用也是合法的。

function getValue () {
  return 5
}
function add (first, second = getValue()) {
  return first + second
}
console.log(add(1, 1)) // 2
console.log(add(1))    // 6
複製代碼

代碼分析:當咱們第一次調用add(1,1)函數時,因爲未使用參數默認值,因此getValue並不會調用。只有當咱們使用了second參數默認值的時候add(1)getValue函數纔會被調用。

正由於默認參數是在函數調用時求值,因此咱們能夠在後定義的參數表達式中使用先定義的參數,便可以把先定義的參數當作變量或者函數調用的參數,以下:

function getValue(value) {
  return value + 5
}
function add (first, second = first + 1) {
  return first + second
}
function reduce (first, second = getValue(first)) {
  return first - second
}
console.log(add(1))     // 3
console.log(reduce(1))  // -5
複製代碼

默認參數的暫時性死區

在前面已經提到過letconst存在暫時性死區,即:在letconst變量聲明以前嘗試訪問該變量會觸發錯誤。相同的道理,在函數默認參數中也存在暫時性死區,以下:

function add (first = second, second) {
  return first + second
}
add(1, 1)         // 2
add(undefined, 1) // 拋出錯誤
複製代碼

代碼分析:在第一次調用add(1,1)時,咱們傳遞了兩個參數,則add函數不會使用參數默認值;在第二次調用add(undefined, 1)時,咱們給first參數傳遞了undefined,則first參數使用參數默認值,而此時second變量尚未初始化,因此被拋出錯誤。

不定參數

JavaScript的函數語法規定:不管函數已定義的命名參數有多少個,都不限制調用時傳入的實際參數的數量。在ES6中,當傳入更少的參數時,使用參數默認值來處理;當傳入更多數量的參數時,使用不定參數來處理。

咱們以underscore.js庫中的pick方法爲例:

pick方法的用法是:給定一個對象,返回指定屬性的對象的副本。

function pick(object) {
  let result = Object.create(null)
  for (let i = 1, len = arguments.length; i < len; i++) {
    let item = arguments[i]
    result[item] = object[item]
  }
  return result
}
const book = {
  title: '深刻理解ES6',
  author: '尼古拉斯',
  year: 2016
}
console.log(pick(book, 'title', 'author')) // { title: '深刻理解ES6', author: '尼古拉斯' }
複製代碼

代碼分析:

  • 不容易發現這個函數能夠接受任意數量的參數。
  • 當須要查找待拷貝的屬性的時候,不得不從索引1開始。

ES6中提供了不定參數,咱們可使用不定參數的特性來重寫pick函數:

function pick(object, ...keys) {
  let result = Object.create(null)
  for (let i = 0, len = keys.length; i < len; i++) {
    let item = keys[i]
    result[item] = object[item]
  }
  return result
}
const book = {
  title: '深刻理解ES6',
  author: '尼古拉斯',
  year: 2016
}
console.log(pick(book, 'title', 'author')) // { title: '深刻理解ES6', author: '尼古拉斯' }
複製代碼

不定參數的限制

不定參數在使用的過程當中有幾點限制:

  • 一個函數最多隻能有一個不定參數。
  • 不定參數必定要放在全部參數的最後一個。
  • 不能在對象字面量setter之中使用不定參數。
// 報錯,只能有一個不定參數
function add(first, ...rest1, ...rest2) {
  console.log(arguments)
}
// 報錯,不定參數只能放在最後一個參數
function add(first, ...rest, three) {
  console.log(arguments)
}
// 報錯,不定參數不能用在對象字面量`setter`之中
const object = {
  set name (...val) {
    console.log(val)
  }
}
複製代碼

展開運算符

ES6的新功能中,展開運算符和不定參數是最爲類似的,不定參數可讓咱們指定多個各自獨立的參數,並經過整合後的數組來訪問;而展開運算符可讓你指定一個數組,將它們打散後做爲各自獨立的參數傳入函數。
ES6以前,咱們若是使用Math.max函數比較一個數組中的最大值,則須要像下面這樣使用:

const arr = [4, 10, 5, 6, 32]
console.log(Math.max.apply(Math, arr)) // 32
複製代碼

代碼分析:在ES6以前使用這種方式是沒有任何問題的,但關鍵的地方在於咱們要借用apply方法,並且要特別當心的處理this(第一個參數),在ES6中咱們有更加簡單的方式來達到以上的目的:

const arr = [4, 10, 5, 6, 32]
console.log(Math.max(...arr)) // 32
複製代碼

函數name屬性

問:爲何ES6會引入函數的name屬性。
答:在JavaScript中有多重定義函數的方式,於是辨別函數就是一項具備挑戰性的任務,此外匿名函數表達式的普遍使用也加大了調試的難度,爲了解決這些問題,在ESCAScript 6中爲全部函數新增了name屬性。

常規name屬性

在函數聲明和匿名函數表達式中,函數的name屬性相對來講是固定的:

function doSomething () {
  console.log('do something')
}
let doAnotherThing = function () {
  console.log('do another thing')
}
console.log(doSomething.name)    // doSomething
console.log(doAnotherThing.name) // doAnotherThing
複製代碼

name屬性的特殊狀況

儘管肯定函數聲明和函數表達式的名稱很容易,但仍是有一些其餘狀況不是特別容易識別:

  • 匿名函數表達式顯示提供函數名的狀況:函數名稱自己比函數自己被賦值的變量的權重高。
  • 對象字面量:在不提供函數名稱的狀況下,取對象字面量的名稱;提供函數名稱的狀況下就是提供的名稱
  • 屬性的gettersetter:在對象上存在get + 屬性get或者set方法。
  • 經過bind:經過bind函數建立的函數,name爲會帶有bound前綴
  • 經過構造函數:函數名稱固定爲anonymous
let doSomething = function doSomethingElse () {
  console.log('do something else')
}
let person = {
  // person對象上存在name爲get firstName的方法
  get firstName () {
    return 'why'
  },
  sayName: function () {
    console.log('why')
  },
  sayAge: function sayNewAge () {
    console.log(23)
  }
}
console.log(doSomething.name)         // doSomethingElse
console.log(person.sayName.name)      // sayName
console.log(person.sayAge.name)       // sayNewAge
console.log(doSomething.bind().name)  // bound doSomethingElse
console.log(new Function().name)      // anonymous
複製代碼

函數的多種用途

JavaScript中函數具備多重功能,能夠結合new使用,函數內的this值指向一個新對象,函數最終會返回這個新對象,以下:

function Person (name) {
  this.name = name
}
const person = new Person('why')
console.log(person.toString()) // [object Object]
複製代碼

ES6中,函數有兩個不一樣的內部方法,分別是:

具備[[Construct]]方法的函數被稱爲構造函數,但並非全部的函數都有[[Construct]]方法,例如:箭頭函數。

  • [[Call]]:若是不經過new關鍵字進行調用函數,則執行[[Call]]函數,從而直接執行代碼中的函數體。
  • [[Construct]]:當經過new關鍵字調用函數時,執行的是[[Construct]]函數,它負責建立一個新對象,而後再執行函數體,將this綁定到實例上。

ES6以前,若是要判斷一個函數是否經過new關鍵詞調用,最流行的方法是使用instanceof來判斷,例如:

function Person (name) {
  if (this instanceof Person) {
    this.name = name
  } else {
    throw new Error('必須經過new關鍵詞來調用Person')
  }
}
const person = new Person('why')
const notPerson = Person('why') // 拋出錯誤
複製代碼

代碼分析:這段代碼中,首先會判斷this的值,看是不是Person的實例,若是是則繼續執行,若是不是則拋出錯誤。一般來講這種作法是正確的,可是也不是十分靠譜,有一種方式能夠不依賴new關鍵詞也能夠把this綁定到Person的實例上,以下:

function Person (name) {
  if (this instanceof Person) {
    this.name = name
  } else {
    throw new Error('必須經過new關鍵詞來調用Person')
  }
}
const person = new Person('why')
const notPerson = Person.call(person, 'why') // 不報錯,有效
複製代碼

爲了解決判斷函數是否經過new關鍵詞調用的問題,ES6引入了new.target這個元屬性
問:什麼是元屬性?
答:元屬性是指非對象的屬性,其能夠提供非對象目標的補充信息。當調用函數的[[Construct]]方法時,new.target被賦值爲new操做符的目標,一般是新建立對象的實例,也就是函數體內this的構造函數;若是調用[[Call]]方法,則new.target的值爲undefined

根據以上new.target的特色,咱們改寫一下上面的代碼:

在函數外使用new.target是一個語法錯誤。

function Person (name) {
  if (typeof new.target !== 'undefined') {
    this.name = name
  } else {
    throw new Error('必須經過new關鍵詞來調用Person')
  }
}
const person = new Person('why')
const notPerson = Person.call(person, 'why') // 拋出錯誤
複製代碼

塊級函數

ECMAScript 3和早期版本中,在代碼塊中聲明一個塊級函數嚴格來講是一個語法錯誤,可是全部的瀏覽器任然支持這個特性,卻又由於瀏覽器的差別致使支撐程度稍有不一樣,因此最好不要使用這個特性,若是要用可使用匿名函數表達式

// ES5嚴格模式下,在代碼塊中聲明一個函數會報錯
// 在ES6下,由於有了塊級做用域的概念,因此不管是否處於嚴格模式,都不會報錯。
// 但在ES6中,當處於嚴格模式時,會將函數聲明提高至當前塊級做用域的頂部
// 當處於非嚴格模式時,提高至外層做用域
'use strict'
if (true) {
  function doSomething () {
    console.log('do something')
  }
}
複製代碼

箭頭函數

ES6中,箭頭函數是其中最有趣的新增特性之一,箭頭函數是一種使用箭頭=>定義函數的新語法,但它和傳統的JavaScript函數有些許不一樣:

  • 沒有this、super、arguments和new.target綁定:箭頭函數中的thissuperargumentsnew.target這些值由外圍最近一層非箭頭函數所決定。
  • 不能經過new關鍵詞調用:由於箭頭函數沒有[[Construct]]函數,因此不能經過new關鍵詞進行調用,若是使用new進行調用會拋出錯誤。
  • 沒有原型:由於不會經過new關鍵詞進行調用,因此沒有構建原型的須要,也就沒有了prototype這個屬性。
  • 不能夠改變this的綁定:在箭頭函數的內部,this的值不可改變(即不能經過callapply或者bind等方法來改變)。
  • 不支持argument對象:箭頭函數沒有arguments綁定,因此必須使用命名參數或者不定參數這兩種形式訪問參數。
  • 不支持重複的命名參數:不管是否處於嚴格模式,箭頭函數都不支持重複的命名參數。

箭頭函數的語法

箭頭函數的語法多變,根據實際的使用場景有多種形式。全部變種都由函數參數、箭頭和函數體組成。

表現形式之一:

// 表現形式之一:沒有參數
let reflect = () => 5
// 至關於
let reflect = function () {
  return 5
}
複製代碼

表現形式之二:

// 表現形式之二:返回單一值
let reflect = value => value
// 至關於
let reflect = function (value) {
  return value
}
複製代碼

表現形式之三:

// 表現形式之三:多個參數
let reflect = (val1, val2) => val1 + val2
// 或者
let reflect = (val, val2) => {
  return val1 + val2
}
// 至關於
let reflect = function (val1, val2) {
  return val1 + val2
}
複製代碼

表現形式之四:

// 表現形式之四:返回字面量
let reflect = (id) => ({ id: id, name: 'why' })
// 至關於
let reflect = function (id) {
  return {
    id: id,
    name: 'why'
  }
}
複製代碼

箭頭函數和數組

箭頭函數的語法簡潔,很是適用於處理數組。

const arr = [1, 5, 3, 2]
// 非箭頭函數排序寫法
arr.sort(function(a, b) {
  return a -b
})
// 箭頭函數排序寫法
arr.sort((a, b) => a - b)
複製代碼

尾調用優化

尾調用指的是函數做爲另外一個函數的最後一條語句被調用。

尾調用示例:

function doSomethingElse () {
  console.log('do something else')
}
function doSomething () {
  return doSomethingElse()
}
複製代碼

ECMAScript 5的引擎中,尾調用的實現與其餘函數調用的實現相似:建立一個新的棧幀,將其推入調用棧來表示函數調用,即意味着:在循環調用中,每個未使用完的棧幀都會被保存在內存中,當調用棧變得過大時會形成程序問題。

針對以上可能會出現的問題,ES6縮減了嚴格模式下尾調用棧的大小,當所有知足如下條件,尾調用再也不建立新的棧幀,而是清除並重用當前棧幀:

  • 尾調用不訪問當前棧幀的變量(函數不是一個閉包。)
  • 尾調用是最後一條語句
  • 尾調用的結果做爲函數返回

知足以上條件的一個尾調用示例:

'use strict'
function doSomethingElse () {
  console.log('do something else')
}
function doSomething () {
  return doSomethingElse()
}
複製代碼

不知足以上條件的尾調用示例:

function doSomethingElse () {
  console.log('do something else')
}
function doSomething () {
  // 沒法優化,沒有返回
  doSomethingElse()
}
function doSomething () {
  // 沒法優化,返回值又添加了其它操做
  return 1 + doSomethingElse()
}
function doSomething () {
  // 可能沒法優化
  let result = doSomethingElse
  return result
}
function doSomething () {
  let number = 1
  let func = () => number
  // 沒法優化,該函數是一個閉包
  return func()
}
複製代碼

遞歸函數是其最主要的應用場景,當遞歸函數的計算量足夠大,尾調用優化能夠大幅提高程序的性能。

// 優化前
function factorial (n) {
  if (n <= 1) {
    return 1
  } else {
    // 沒法優化
    return n * factorial (n - 1)
  }
}

// 優化後
function factorial (n, p = 1) {
  if (n <= 1) {
    return 1 * p
  } else {
    let result = n * p
    return factorial(n -1, result)
  }
}
複製代碼

對象的擴展

對象字面量的擴展

對象字面量擴展包含兩部分:

  • 屬性初始值的簡寫:當對象的屬性和本地變量同名時,沒必要再寫冒號和值,簡單的只寫屬性便可。
  • 對象方法的簡寫: 消除了冒號和function關鍵字。
  • 可計算屬性名:在定義對象時,對象的屬性值可經過變量來計算。

經過對象方法簡寫語法建立的方法有一個name屬性,其值爲小括號前的名稱。

const name = 'why'
const firstName = 'first name'
const person = {
  name,
  [firstName]: 'ABC',
  sayName () {
    console.log(this.name)
  }
  
}
// 至關於
const name = 'why'
const person = {
  name: name,
  'first name': 'ABC',
  sayName: function () {
    console.log(this.name)
  }
}
複製代碼

新增方法

Object.is

在使用JavaScript比較兩個值的時候,咱們可能會習慣使用==或者===來進行判斷,使用全等===在比較時能夠避免觸發強制類型轉換,因此深受許多人的喜好。但全等===也並不是是徹底準確的,例如: +0===-0會返回trueNaN===NaN會返回false。針對以上狀況,ES6引入了Object.is方法來彌補。

// ===和Object.is大多數狀況下結果是相同的,只有極少數結果不一樣
console.log(+0 === -0)            // true
console.log(Object.is(+0, -0))    // false
console.log(NaN === NaN)          // false
console.log(Object.is(NaN, NaN))  // true
複製代碼

Object.assign

問:什麼是Mixin
答:混合MixinJavaScript中實現對象組合最流行的一種模式。在一個mixin中,一個對象接受來自另外一個對象的屬性和方法(mixin方法爲淺拷貝)。

// mixin方法
function mixin(receiver, supplier) {
  Object.keys(supplier).forEach(function(key) {
    receiver[key] = supplier[key]
  })
  return receiver
}
const person1 = {
  age: 23,
  name: 'why'
}
const person2 = mixin({}, person1)
console.log(person2) // { age: 23, name: 'why' }
複製代碼

因爲這種混合模式很是流行,因此ES6引入了Object.assign方法來實現相同的功能,這個方法接受一個接受對象和任意數量的源對象,最終返回接受對象。

若是源對象中有同名的屬性,後面的源對象會覆蓋前面源對象中的同名屬性。

const person1 = {
  age: 23,
  name: 'why'
}
const person2 = {
  age: 32,
  address: '廣東廣州'
}
const person3 = Object.assign({}, person1, person2)
console.log(person3) // { age: 32, name: 'why', address: '廣東廣州' }
複製代碼

Object.assign方法不能複製屬性的get和set。

let receiver = {}
let supplier = {
  get name () {
    return 'why'
  }
}
Object.assign(receiver, supplier)
const descriptor = Object.getOwnPropertyDescriptor(receiver, 'name')
console.log(descriptor.value) // why
console.log(descriptor.get)   // undefined
console.log(receiver)         // { name: 'why' }
複製代碼

重複的對象字面量屬性

ECMAScript 5嚴格模式下,給一個對象添加劇復的屬性會觸發錯誤:

'use strict'
const person = {
  name: 'AAA',
  name: 'BBB' // ES5環境觸發錯誤
}
複製代碼

但在ECMAScript 6中,不管當前是否處於嚴格模式,添加劇復的屬性都不會報錯,而是選取最後一個取值:

'use strict'
const person = {
  name: 'AAA',
  name: 'BBB' // ES6環境不報錯
}
console.log(person) // { name: 'BBB' }
複製代碼

自有屬性枚舉順序

ES5中未定義對象屬性的枚舉順序,由瀏覽器廠商自行決定。而在ES6中嚴格規定了對象自有屬性被枚舉時的返回順序。

規則

  • 全部數字鍵按升序排序。
  • 全部字符鍵按照它們被加入對象的順序排序。
  • 全部Symbol鍵按照它們被加入對象的順序排序。

根據以上規則,如下這些方法將受到影響:

  • Object.getOwnPropertyNames()
  • Reflect.keys()
  • Object.assign()

不肯定的狀況:

  • for-in循環依舊由廠商決定枚舉順序。
  • Object.keys()JSON.stringify()也同for-in循環同樣由廠商決定枚舉順序。
const obj = {
  a: 1,
  0: 1,
  c: 1,
  2: 1,
  b: 1,
  1: 1
}
obj.d = 1
console.log(Reflect.keys(obj).join('')) // 012acbd
複製代碼

加強對象原型

ES5中,對象原型一旦實例化以後保持不變。而在ES6中添加了Object.setPrototypeOf()方法來改變這種狀況。

const person = {
  sayHello () {
    return 'Hello'
  }
}
const dog = {
  sayHello () {
    return 'wang wang wang'
  }
}
let friend = Object.create(person)
console.log(friend.sayHello())                        // Hello
console.log(Object.getPrototypeOf(friend) === person) // true
Object.setPrototypeOf(friend, dog)
console.log(friend.sayHello())                        // wang wang wang
console.log(Object.getPrototypeOf(friend) === dog)    // true
複製代碼

簡化原型訪問的Super引用

ES5中,若是咱們想重寫對象實例的方法,又須要調用與它同名的原型方法,能夠像下面這樣:

const person = {
  sayHello () {
    return 'Hello'
  }
}
const dog = {
  sayHello () {
    return 'wang wang wang'
  }
}
const friend = {
  sayHello () {
    return Object.getPrototypeOf(this).sayHello.call(this) + '!!!'
  }
}
Object.setPrototypeOf(friend, person)
console.log(friend.sayHello())                        // Hello!!!
console.log(Object.getPrototypeOf(friend) === person) // true
Object.setPrototypeOf(friend, dog)
console.log(friend.sayHello())                        // wang wang wang!!!
console.log(Object.getPrototypeOf(friend) === dog)    // true
複製代碼

代碼分析:要準確記住如何使用Object.getPrototypeOf()xx.call(this)方法來調用原型上的方法實在是有點複雜。並且存在多繼承的狀況下,Object.getPrototypeOf()會出現問題。
根據以上問題,ES6引入了super關鍵字,其中super至關於指向對象原型的指針,因此以上代碼能夠修改以下:

super關鍵字只出如今對象簡寫方法裏,普通方法中使用會報錯。

const person = {
  sayHello () {
    return 'Hello'
  }
}
const dog = {
  sayHello () {
    return 'wang wang wang'
  }
}
const friend = {
  sayHello () {
    return super.sayHello.call(this) + '!!!'
  }
}
Object.setPrototypeOf(friend, person)
console.log(friend.sayHello())                        // Hello!!!
console.log(Object.getPrototypeOf(friend) === person) // true
Object.setPrototypeOf(friend, dog)
console.log(friend.sayHello())                        // wang wang wang!!!
console.log(Object.getPrototypeOf(friend) === dog)    // true
複製代碼

正式的方法定義

ES6以前從未正式定義過"方法"的概念,方法僅僅是一個具備功能而非數據的對象屬性。而在ES6中正式將方法定義爲一個函數,它會有一個內部[[HomeObject]]屬性來容納這個方法從屬的對象。

const person = {
  // 是方法 [[HomeObject]] = person
  sayHello () {
    return 'Hello'
  }
}
// 不是方法
function sayBye () {
  return 'goodbye'
}
複製代碼

根據以上[[HomeObject]]的規則,咱們能夠得出super是如何工做的:

  • [[HomeObject]]屬性上調用Object.getPrototypeOf()方法來檢索原型的引用。
  • 搜索原型找到同名函數。
  • 設置this綁定而且調用相應的方法。
const person = {
  sayHello () {
    return 'Hello'
  }
}
const friend = {
  sayHello () {
    return super.sayHello() + '!!!'
  }
}
Object.setPrototypeOf(friend, person)
console.log(friend.sayHello()) // Hello!!!
複製代碼

代碼分析:

  • friend.sayHello()方法的[[HomeObject]]屬性值爲friend
  • friend的原型是person
  • super.sayHello()至關於person.sayHello.call(this)

解構

解構是一種打破數據結構,將其拆分爲更小部分的過程。

爲什麼使用解構功能

ECMAScript 5及其早期版本中,爲了從對象或者數組中獲取特定數據並賦值給變量,編寫了許多看起來同質化的代碼:

const person = {
  name: 'AAA',
  age: 23
}
const name = person.name
const age = person.age
複製代碼

代碼分析:咱們必須從person對象中提取nameage的值,並把其值賦值給對應的同名變量,過程極其類似。假設咱們要提取許多變量,這種過程會重複更屢次,若是其中還包含嵌套結構,只靠遍歷是找不到真實信息的。

針對以上問題,ES6引入瞭解構的概念,按場景可分爲:

  • 對象解構
  • 數組解構
  • 混合解構
  • 解構參數

對象解構

咱們使用ES6中的對象結構,改寫以上示例:

const person = {
  name: 'AAA',
  age: 23
}
const { name, age } = person
console.log(name) // AAA
console.log(age)  // 23
複製代碼

必須爲解構賦值提供初始化程序,同時若是解構右側爲null或者undefined,解構會發生錯誤。

// 如下代碼爲錯誤示例,會報錯
var { name, age }
let { name, age }
const { name, age }
const { name, age } = null
const { name, age } = undefined
複製代碼

解構賦值

咱們不只能夠在解構時從新定義變量,還能夠解構賦值已存在的變量:

const person = {
  name: 'AAA',
  age: 23
}
let name, age
// 必須添加(),由於若是不加,{}表明是一個代碼塊,而語法規定代碼塊不能出如今賦值語句的左側。
({ name, age } = person)
console.log(name) // AAA
console.log(age)  // 23
複製代碼

解構默認值

使用解構賦值表達式時,若是指定的局部變量名稱在對象中不存在,那麼這個局部變量會被賦值爲undefined,此時能夠隨意指定一個默認值。

const person = {
  name: 'AAA',
  age: 23
}
let { name, age, sex = '男' } = person
console.log(sex) // 男
複製代碼

爲非同名變量賦值

目前爲止咱們解構賦值時,待解構的鍵和待賦值的變量是同名的,但如何爲非同名變量解構賦值呢?

const person = {
  name: 'AAA',
  age: 23
}
let { name, age } = person
// 至關於
let { name: name, age: age } = person
複製代碼

let { name: name, age: age } = person含義是:在person對象中取鍵爲nameage的值,並分別賦值給name變量和age變量。
那麼,咱們根據以上的思路,爲非同名變量賦值能夠改寫成以下形式:

const person = {
  name: 'AAA',
  age: 23
}
let { name: newName, age: newAge } = person
console.log(newName) // AAA
console.log(newAge)  // 23
複製代碼

嵌套對象結構

解構嵌套對象任然與對象字面量語法類似,只是咱們能夠將對象拆解成咱們想要的樣子。

const person = {
  name: 'AAA',
  age: 23,
  job: {
    name: 'FE',
    salary: 1000
  },
  department: {
    group: {
      number: 1000,
      isMain: true
    }
  }
}
let { job, department: { group } } = person
console.log(job)    // { name: 'FE', salary: 1000 }
console.log(group)  // { number: 1000, isMain: true }
複製代碼

let { job, department: { group } } = person含義是:在person中提取鍵爲job、在person的嵌套對象department中提取鍵爲group的值,並把其賦值給對應的變量。

數組解構

數組的解構賦值與對象解構的語法類似,但簡單許多,它使用的是數組字面量,且解構操做所有在數組內完成,解構的過程是按值在數組中的位置進行提取的。

const colors = ['red', 'green', 'blue']
let [firstColor, secondColor] = colors
// 按需解構
let [,,threeColor] = colors
console.log(firstColor)   // red
console.log(secondColor)  // green
console.log(threeColor)   // blue
複製代碼

與對象同樣,解構數組也能解構賦值給已經存在的變量,只是能夠不須要像對象同樣額外的添加括號:

const colors = ['red', 'green', 'blue']
let firstColor, secondColor
[firstColor, secondColor] = colors
console.log(firstColor)   // red
console.log(secondColor)  // green
複製代碼

按以上原理,咱們能夠輕鬆擴展一下解構賦值的功能(快速交換兩個變量的值):

let a = 1;
let b = 2;
[a, b] = [b, a];
console.log(a); // 2
console.log(b); // 1
複製代碼

與對象同樣,數組解構也能夠設置解構默認值:

const colors = ['red']
const [firstColor, secondColor = 'green'] = colors
console.log(firstColor)   // red
console.log(secondColor)  // green
複製代碼

當存在嵌套數組時,咱們也可使用和解構嵌套對象的思路來解決:

const colors = ['red', ['green', 'lightgreen'], 'blue']
const [firstColor, [secondColor]] = colors
console.log(firstColor)   // red
console.log(secondColor)  // green
複製代碼

不定參數

在解構數組時,不定元素只能放在最後一個,在後面繼續添加逗號會致使報錯。

在數組解構中,有一個和函數的不定參數類似的功能:在解構數組時,可使用...語法將數組中剩餘元素賦值給一個特定的變量:

let colors = ['red', 'green', 'blue']
let [firstColor, ...restColors] = colors
console.log(firstColor) // red
console.log(restColors) // ['green', 'blue']
複製代碼

根據以上解構數組中的不定元素的原理,咱們能夠實現同concat同樣的數組複製功能:

const colors = ['red', 'green', 'blue']
const concatColors = colors.concat()
const [...restColors] = colors
console.log(concatColors) // ['red', 'green', 'blue']
console.log(restColors)   // ['red', 'green', 'blue']
複製代碼

解構參數

當咱們定一個須要接受大量參數的函數時,一般咱們會建立能夠可選的對象,將額外的參數定義爲這個對象的屬性:

function setCookie (name, value, options) {
  options = options || {}
  let path = options.path,
      domain = options.domain,
      expires = options.expires
  // 其它代碼
}

// 使用解構參數
function setCookie (name, value, { path, domain, expires } = {}) {
  // 其它代碼
}
複製代碼

代碼分析:{ path, domain, expires } = {}必須提供一個默認值,若是不提供默認值,則不傳遞第三個參數會報錯:

function setCookie (name, value, { path, domain, expires }) {
  // 其它代碼
}
// 報錯
setCookie('type', 'js')
// 至關於解構了undefined,因此會報錯
{ path, domain, expires } = undefined
複製代碼

Symbol及其Symbol屬性

ES6以前,JavaScript語言只有五種原始類型:stringnumberbooleannullundefiend。在ES6中,添加了第六種原始類型:Symbol

可使用typeof來檢測Symbol類型:

const symbol = Symbol('Symbol Test')
console.log(typeof symbol) // symbol
複製代碼

建立Symbol

能夠經過全局的Symbol函數來建立一個Symbol

const firstName = Symbol()
const person = {}
person[firstName] = 'AAA'
console.log(person[firstName]) // AAA
複製代碼

能夠在Symbol()中傳遞一個可選的參數,可讓咱們添加一段文本描述咱們建立的Symbol,其中文本是存儲在內部屬性[[Description]]中,只有當調用SymboltoString()方法時才能夠讀取這個屬性。

const firstName = Symbol('Symbol Description')
const person = {}
person[firstName] = 'AAA'
console.log(person[firstName]) // AAA
console.log(firstName)         // Symbol('Symbol Description')
複製代碼

Symbol的使用方法

全部可使用可計算屬性名的地方,均可以使用Symbol

let firstName = Symbol('first name')
let lastName = Symbol('last name')
const person = {
  [firstName]: 'AAA'
}
Object.defineProperty(person, firstName, {
  writable: false
})
Object.defineProperties(person, {
  [lastName]: {
    value: 'BBB',
    writable: false
  }
})
console.log(person[firstName])  // AAA
console.log(person[lastName])   // BBB
複製代碼

Symbol共享體系

ES6提供了一個能夠隨時訪問的全局Symbol註冊表來讓咱們能夠建立共享Symbol的能力,可使用Symbol.for()方法來建立一個共享的Symbol

// Symbol.for方法的參數,也被用作Symbol的描述內容
const uid = Symbol.for('uid')
const object = {
  [uid]: 12345
}
console.log(person[uid]) // 12345
console.log(uid)         // Symbol(uid)
複製代碼

代碼分析:

  • Symbol.for()方法首先會在全局Symbol註冊變中搜索鍵爲uidSymbol是否存在。
  • 存在,直接返回已有的Symbol
  • 不存在,則建立一個新的Symbol,並使用這個鍵在Symbol全局註冊變中註冊,隨後返回新建立的Symbol

還有一個和Symbol共享有關的特性,可使用Symbol.keyFor()方法在Symbol全局註冊表中檢索與Symbol有關的鍵,若是存在則返回,不存在則返回undefined

const uid = Symbol.for('uid')
const uid1 = Symbol('uid1')
console.log(Symbol.keyFor(uid))   // uid
console.log(Symbol.keyFor(uid1))  // undefined
複製代碼

Symbol與類型強制轉換

其它原始類型沒有與Symbol邏輯相等的值,尤爲是不能將Symbol強制轉換爲字符串和數字。

const uid = Symbol.for('uid')
console.log(uid)
console.log(String(uid))
// 報錯
uid = uid + ''
uid = uid / 1
複製代碼

代碼分析:咱們使用console.log()方法打印Symbol,會調用SymbolString()方法,所以也能夠直接調用String()方法輸出Symbol。然而嘗試將Symbol和一個字符串拼接,會致使程序拋出異常,Symbol也不能和每個數學運算符混合使用,不然一樣會拋出錯誤。

Symbol屬性檢索

Object.keys()Object.getOwnPropertyNames()方法能夠檢索對象中全部的屬性名,其中Object.keys返回全部能夠枚舉的屬性,Object.getOwnPropertyNames()不管屬性是否能夠枚舉都返回,可是這兩個方法都沒法返回Symbol屬性。所以ES6引入了一個新的方法Object.getOwnPropertySymbols()方法。

const uid = Symbol.for('uid')
let object = {
  [uid]: 123
}
const symbols = Object.getOwnPropertySymbols(object)
console.log(symbols.length) // 1
console.log(symbols[0])     // Symbol(uid)
複製代碼

Symbol暴露內部的操做

ES6經過在原型鏈上定義與Symbol相關的屬性來暴露更多的語言內部邏輯,這些內部操做以下:

  • Symbol.hasInstance:一個在執行instanceof時調用的內部方法,用於檢測對象的繼承信息。
  • Symbol.isConcatSpreadable:一個布爾值,用於表示當傳遞一個集合做爲Array.prototype.concat()方法的參數時,是否應該將集合內的元素規整到同一層級。
  • Symbol.iterator:一個返回迭代器的方法。
  • Symbol.match:一個在調用String.prototype.match()方法時調用的方法,用於比較字符串。
  • Symbol.replace:一個在調用String.prototype.replace()方法時調用的方法,用於替換字符串中的子串。
  • Symbol.search:一個在調用String,prototype.search()方法時調用的方法,用於在字符串中定位子串。
  • Symbol.split:一個在調用String.prototype.split()方法時調用的方法,用於分割字符串。
  • Symbol.species:用於建立派生對象的構造函數。
  • Symbol.toPrimitive:一個返回對象原始值的方法。
  • Symbol.toStringTag:一個在調用Object.prototype.toString()方法時使用的字符串,用於建立對象描述。
  • Symbol.unscopables:一個定義了一些不可被with語句引用的對象屬性名稱的對象集合。

重寫一個由well-known Symbol定義的方法,會致使對象內部的默認行爲被改變,從而一個普通對象會變爲一個奇異對象。

Symbol.hasInstance

每個函數都有Symbol.hasInstance方法,用於肯定對象是否爲函數的實例,而且該方法不可被枚舉、不可被寫和不可被配置。

function MyObject () {
  // 空函數
}
Object.defineProperty(MyObject, Symbol.hasInstance, {
  value: function () {
    return false
  }
})
let obj = new MyObject()
console.log(obj instanceof MyObject) // false
複製代碼

代碼分析:使用Object.defineProperty方法,在MyObject函數上改寫Symbol.hasInstance,爲其定義一個老是返回false的新函數,即便obj確實是MyObject的實例,但依然在進行instanceof判斷時返回了false

注意若是要觸發Symbol.hasInstance調用,instanceof的左操做符必須是一個對象,若是爲非對象則會致使instanceof始終返回false。

Symbol.isConcatSpreadable

JavaScript數組中concat()方法被用於拼接兩個數組:

const colors1 = ['red', 'green']
const colors2 = ['blue']
console.log(colors1.concat(colors2, 'brown')) // ['red', 'green', 'blue', 'brown']
複製代碼

concat()方法中,咱們傳遞了第二個參數,它是一個非數組元素。若是Symbol.isConcatSpreadabletrue,那麼表示對象有length屬性和數字鍵,故它的數值型鍵會被獨立添加到concat調用的結果中,它是對象的可選屬性,用於加強做用於特定對象類型的concat方法的功能,有效簡化其默認特性:

const obj = {
  0: 'hello',
  1: 'world',
  length: 2,
  [Symbol.isConcatSpreadable]: true
}
const message = ['Hi'].concat(obj)
console.log(message) // ['Hi', 'hello', 'world']
複製代碼

Symbol.match,Symbol.replace,Symbol.search,Symbol.split

JavaScript中,字符串與正則表達式常常一塊兒出現,尤爲是字符串類型的幾個方法,能夠接受正則表達式做爲參數:

  • match:肯定給定字符串是否匹配正則表達式。
  • replace:將字符串中匹配正則表達式的部分替換爲給定的字符串。
  • search:在字符串中定位匹配正則表示位置的索引。
  • split:按照匹配正則表達式的元素將字符串進行分割,並將分割結果存入數組中。

ES6以前,以上幾個方法沒法使用咱們本身定義的對象來替代正則表達式進行字符串匹配,而在ES6以後,引入了與上述幾個方法相對應Symbol,將語言內建的Regex對象的原生特性徹底外包出來。

const hasLengthOf10 = {
  [Symbol.match] (value) {
    return value.length === 10 ? [value] : null
  },
  [Symbol.replace] (value, replacement) {
    return value.length === 10 ? replacement : value
  },
  [Symbol.search] (value) {
    return value.length === 10 ? 0 : -1
  },
  [Symbol.split] (value) {
    return value.length === 10 ? [,] : [value]
  }
}
const message1 = 'Hello world'
const message2 = 'Hello John'
const match1 = message1.match(hasLengthOf10)
const match2 = message2.match(hasLengthOf10)
const replace1 = message1.replace(hasLengthOf10)
const replace2 = message2.replace(hasLengthOf10, 'AAA')
const search1 = message1.search(hasLengthOf10)
const search2 = message2.search(hasLengthOf10)
const split1 = message1.split(hasLengthOf10)
const split2 = message2.split(hasLengthOf10)
console.log(match1)     // null
console.log(match2)     // [Hello John]
console.log(replace1)   // Hello world
console.log(replace2)   // AAA
console.log(search1)    // -1
console.log(search2)    // 0
console.log(split1)     // [Hello John]
console.log(split2)     // [,]
複製代碼

Symbol.toPrimitive

Symbol.toPrimitive方法被定義在每個標準類型的原型上,而且規定了當對象被轉換爲原始值時應該執行的操做,每當執行原始值轉換時,總會調用Symbol.toPrimitive方法並傳入一個值做爲參數。
對於大多數標準對象,數字模式有如下特性,根據優先級的順序排序以下:

  • 調用valueOf()方法,若是結果爲原始值,則返回。
  • 不然,調用toString()方法,若是結果爲原始值,則返回。
  • 若是再無可選值,則拋出錯誤。

一樣對於大多數標準對象,字符串模式有如下有限級順序:

  • 調用toString()方法,若是結果爲原始值,則返回。
  • 不然,調用valueOf()方法,若是結果爲原始值,則返回。
  • 若是再無可選值,則拋出錯誤。

在大多數狀況下,標準對象會將默認模式按數字模式處理(除Date對象,在這種狀況下,會將默認模式按字符串模式處理),若是自定義了Symbol.toPrimitive方法,則能夠覆蓋這些默認的強制轉換行爲。

function Temperature (degress) {
  this.degress = degress
}
Temperature.prototype[Symbol.toPrimitive] = function (hint) {
  switch (hint) {
    case 'string':
      return this.degress + '℃'
    case 'number':
      return this.degress
    case 'default':
      return this.deress + ' degress'
  }
}
const freezing = new Temperature(32)
console.log(freezing + '')      // 32 degress
console.log(freezing / 2)       // 16
console.log(String(freezing))   // 32℃
複製代碼

代碼分析:咱們在對象Temperature原型上重寫了Symbol.toPrimitive,新方法根據參數hint指定的模式返回不一樣的值,其中hint參數由JavaScript引擎傳入。其中+運算符觸發默認模式,hint被設置爲default;/運算符觸發數字模式,hint被設置爲number;String()函數觸發字符串模式,hint被設置爲string

Symbol.toStringTag

JavaScript中,若是咱們同時存在多個全局執行環境,例如在瀏覽器中一個頁面包含iframe標籤,由於iframe和它外層的頁面分別表明不一樣的領域,每個領域都有本身的全局做用域,有本身的全局對象,在任何領域中建立的數組,都是一個正規的數組。然而,若是將這個數字傳遞到另一個領域中,instanceof Array語句的檢測結果會返回false,此時Array已是另外一個領域的構造函數,顯然被檢測的數組不是由這個構造函數建立的。

針對以上問題,咱們很快找到了一個相對來講比較實用的解決方案:

function isArray(value) {
  return Object.prototype.toString.call(value) === '[object Array]'
}
console.log(isArray([])) // true
複製代碼

與上述問題有一個相似的案例,在ES5以前咱們可能會引入第三方庫來建立全局的JSON對象,而在瀏覽器開始實現JSON全局對象後,就有必要區分JSON對象是JavaScript環境自己提供的仍是由第三方庫提供的:

function supportsNativeJSON () {
  return typeof JSON !== 'undefined' && Object.prototype.toString.call(JSON) === '[object JSON]'
}
複製代碼

ES6中,經過Symbol.toStringTag這個Symbol改變了調用Object.prototype.toString()時返回的身份標識,其定義了調用對象的Object.prototype.toString.call()方法時返回的值:

function Person (name) {
  this.name = name
}
Person.prototype[Symbol.toStringTag] = 'Person'
const person = new Person('AAA')
console.log(person.toString())                        // [object Person]
console.log(Object.prototype.toString.call(person))   // [object Person]
複製代碼

Set和Map集合

Set集合是一種無重複元素的列表,一般用來檢測給定的值是否在某個集合中;Map集合內含多組鍵值對,集合中每一個元素分別存放着可訪問的鍵名和它對應的值,Map集合常常被用來緩存頻繁取用的數據。

ES5中的Set和Map集合

ES6尚未正式引入Set集合和Map集合以前,開發者們已經開始使用對象屬性來模擬這兩種集合了:

const set = Object.create(null)
const map = Object.create(null)
set.foo = true
map.bar = 'bar'
// set檢查
if (set.foo) {
  console.log('存在')
}
// map取值
console.log(map.bar) // bar
複製代碼

以上程序很簡單,確實可使用對象屬性來模擬Set集合和Map集合,但卻在實際使用的過程當中有諸多的不方便:

  • 對象屬性名必須爲字符串:
const map = Object.create(null)
map[5] = 'foo'
// 本意是使用數字5做爲鍵名,但被自動轉換爲了字符串
console.log(map['5']) // foo
複製代碼
  • 對象不能做爲屬性名:
const map = Object.create(null)
const key1 = {}
const key2 = {}
map[key1] = 'foo'
// 本意是使用key1對象做爲屬性名,但卻被自動轉換爲[object Object]
// 所以map[key1] = map[key2] = map['[object Object]']
console.log(map[key2]) // foo
複製代碼
  • 不可控制的強制類型轉換:
const map = Object.create(null)
map.count = 1
// 本意是檢查count屬性是否存在,實際檢查的確是map.count屬性的值是否爲真
if (map.count) {
  console.log(map.count)
}
複製代碼

ES6中的Set集合

Set集合是一種有序列表,其中含有一些相互獨立的非重複值,在Set集合中,不會對所存的值進行強制類型轉換。

其中Set集合涉及到的屬性和方法有:

  • Set構造函數:可使用此構造函數建立一個Set集合。
  • add方法:能夠向Set集合中添加一個元素。
  • delete方法:能夠移除Set集合中的某一個元素。
  • clear方法:能夠移除Set集合中全部的元素。
  • has方法:判斷給定的元素是否在Set集合中。
  • size屬性:Set集合的長度。

建立Set集合

Set集合的構造函數能夠接受任何可迭代對象做爲參數,例如:數組、Set集合或者Map集合。

const set = new Set()
set.add(5)
set.add('5')
// 重複添加的值會被忽略
set.add(5)
console.log(set.size) // 2
複製代碼

移除元素

使用delete()方法能夠移除集合中的某一個值,使用clear()方法能夠移除集合中全部的元素。

const set = new Set()
set.add(5)
set.add('5')
console.log(set.has(5)) // true
set.delete(5)
console.log(set.has(5)) // false
console.log(set.size)   // 1
set.clear()
console.log(set.size)   // 0
複製代碼

Set集合的forEach()方法

Set集合的forEach()方法和數組的forEach()方法是同樣的,惟一的區別在於Set集合在遍歷時,第一和第二個參數是同樣的。

const set = new Set([1, 2])
set.forEach((value, key, arr) => {
  console.log(`${value} ${key}`)
  console.log(arr === set)
})
// 1 1
// true
// 2 2
// true
複製代碼

Set集合轉換爲數組

由於Set集合不能夠像數組那樣經過索引去訪問元素,最好的作法是將Set集合轉換爲數組。

const set = new Set([1, 2, 3, 4])
// 方法一:展開運算符
const arr1 = [...set]
// 方法二:Array.from方法
const arr2 = Array.from(set)
console.log(arr1) // [1, 2, 3, 4]
console.log(arr2) // [1, 2, 3, 4]
複製代碼

Weak Set集合

經過以上對Set集合的梳理,咱們能夠發現:只要Set實例中的引用存在,垃圾回收機制就不能釋放該對象的內存空間,因此咱們把Set集合看做是一個強引用的集合。爲了更好的處理Set集合的垃圾回收,引入了一個叫Weak Set的集合:

Weak Set集合只支持三種方法:add、has和delete。

const weakSet = new WeakSet()
const key = {}
weakSet.add(key)
console.log(weakSet.has(key)) // true
weakSet.delete(key)
console.log(weakSet.has(key)) // false
複製代碼

Set集合和Weak Set集合有許多共同的特性,但它們之間仍是有必定的差異的:

  • Weak Set集合只能存儲對象元素,向其添加非對象元素會致使拋出錯誤,同理has()delete()傳遞非對象也一樣會報錯。
  • Weak Set集合不可迭代,也不暴露任何迭代器,所以也不支持forEach()方法。
  • Weak Set集合不支持size屬性。

ES6中的Map集合

ES6中的Map類型是一種存儲着許多鍵值對的有序列表,其中的鍵名和對應的值支持全部的數據類型,鍵名的等價性判斷是經過調用Object.is方法來實現的。

const map = new Map()
const key1 = {
  name: 'key1'
}
const key2 = {
  name: 'key2'
}
map.set(5, 5)
map.set('5', '5')
map.set(key1, key2)
console.log(map.get(5))     // 5
console.log(map.get('5'))   // '5'
console.log(map.get(key1))  // {name:'key2'}
複製代碼

Map集合支持的方法

Set集合相似,Map集合也支持如下幾種方法:

  • has:判斷指定的鍵名是否在Map集合中存在。
  • delete:在Map集合中移除指定鍵名及其對應的值。
  • clear:移除Map集合中全部的鍵值對。
const map = new Map()
map.set('name', 'AAA')
map.set('age', 23)
console.log(map.size)        // 2
console.log(map.has('name')) // true
console.log(map.get('name')) // AAA
map.delete('name')
console.log(map.has('name')) // false
map.clear()
console.log(map.size)        // 0
複製代碼

Map集合的初始化方法

在初始化Map集合的時候,也能夠像Set集合傳入數組,但此時數組中的每個元素都是一個子數組,子數組中包含一個鍵值對的鍵名和值兩個元素。

const map = new Map([['name', 'AAA'], ['age', 23]])
console.log(map.has('name'))  // true
console.log(map.has('age'))   // true
console.log(map.size)         // 2
console.log(map.get('name'))  // AAA
console.log(map.get('age'))   // 23
複製代碼

Map集合的forEach()方法

Map集合中的forEach()方法的回調參數和數組相似,每個參數的解釋以下:

  • 第一個參數是鍵名
  • 第二個參數是值
  • 第三個參數是Map集合自己
const map = new Map([['name', 'AAA'], ['age', 23]])
map.forEach((key, value, ownMap) => {
  console.log(`${key} ${value}`)
  console.log(ownMap === map)
})
// name AAA
// true
// age 23
// true
複製代碼

Weak Map集合

Weak Map它是一種存儲着許多鍵值對的無序列表,集合中的鍵名必須是一個對象,若是使用非對象鍵名會報錯。

Weak Map集合只支持set()、get()、has()和delete()。

const key1 = {}
const key2 = {}
const key3 = {}
const weakMap = new WeakMap([[key1, 'AAA'], [key2, 23]])
weakMap.set(key3, '廣東')

console.log(weakMap.has(key1)) // true
console.log(weakMap.get(key1)) // AAA
weakMap.delete(key1)
console.log(weakMap.has(key)) // false
複製代碼

Map集合和Weak Map集合有許多共同的特性,但它們之間仍是有必定的差異的:

  • Weak Map集合的鍵名必須爲對象,添加非對象會報錯。
  • Weak Map集合不可迭代,所以不支持forEach()方法。
  • Weak Map集合不支持clear方法。
  • Weak Map集合不支持size屬性。

迭代器(Iterator)和生成器(Generator)

循環語句的問題

咱們在平常的開發過程當中,極可能寫過下面這樣的代碼:

var colors = ['red', 'gree', 'blue']
for(var i = 0, len = colors.length; i < len; i++) {
  console.log(colors[i])
}
// red
// green
// blue
複製代碼

代碼分析:雖然循環語句的語法簡單,可是若是將多個循環嵌套則須要追蹤多個變量,代碼複雜度會大大增長,一不當心就會錯誤使用了其它for循環的跟蹤變量,從而形成程序出錯,而ES6引入迭代器的宗旨就是消除這種複雜性並減小循環中的錯誤。

什麼是迭代器

問:什麼是迭代器?
答:迭代器是一種特殊的對象,它具備一些專門爲迭代過程設計的專有接口,全部迭代器都有一個叫next的方法,每次調用都返回一個結果對象。結果對象有兩個屬性,一個是value表示下一次將要返回的值;另一個是done,它是一個布爾類型的值,當沒有更多可返回的數據時返回true。迭代器還會保存一個內部指針,用來指向當前集合中值的位置,每調用一次next方法,都會返回下一個可用的值。

在瞭解迭代器的概念後,咱們使用ES5語法來建立一個迭代器:

function createIterator (items) {
  var i = 0
  return {
    next: function () {
      var done = i >= items.length
      var value = !done ? items[i++] : undefined
      return {
        done: done,
        value: value
      }
    }
  }
}
var iterator = createIterator([1, 2, 3])
console.log(iterator.next())  // { value: 1, done: false }
console.log(iterator.next())  // { value: 2, done: false }
console.log(iterator.next())  // { value: 3, done: false }
console.log(iterator.next())  // { value: undefined, done: true }
複製代碼

正如上面那樣,咱們使用了ES5語法來建立咱們本身的迭代器,它的內部實現很複雜,而ES6除了引入了迭代器的概念還引入了一個叫生成器的概念,使用它咱們可讓建立迭代器的過程更加簡單一點。

什麼是生成器

問:什麼是生成器?
答:生成器是一種返回迭代器的函數,經過function關鍵字後的*號來表示,函數中會用到新的關鍵詞yield

function * createIterator () {
  yield 1
  yield 2
  yield 3
}
const iterator = createIterator()
console.log(iterator.next().value)  // 1
console.log(iterator.next().value)  // 2
console.log(iterator.next().value)  // 3
複製代碼

正如咱們上面的輸出結果同樣,它和咱們使用ES5語法建立的迭代器輸出結果是一致的。

生成器函數最重要的一點是:每執行完一條yield語句,函數就會自動終止:咱們在ES6以前,函數一旦開始執行,則一直會向下執行,一直到函數return語句都不會中斷,但生成器函數卻打破了這一慣例:當執行完一條yield語句時,函數會自動中止執行,除非代碼手動調用迭代器的next方法。

咱們也能夠在循環中使用生成器:

function * createIterator (items) {
  for(let i = 0, len = items.length; i < len; i++) {
    yield items[i]
  }
}
const it = createIterator([1, 2, 3])
console.log(it.next())  // { done: false, value: 1 }
console.log(it.next())  // { done: false, value: 2 }
console.log(it.next())  // { done: false, value: 3 }
console.log(it.next())  // { done: true, value: undefined }
複製代碼

yield關鍵字只能在生成器內部使用,在其餘地方使用會致使拋出錯誤,即便是在生成器內部的函數中使用也是如此。

function * createIterator (items) {
  items.forEach(item => {
    // 拋出錯誤
    yield item + 1
  })
}
複製代碼

可迭代對象和for-of循環

問:可迭代對象有什麼特色?
答:可迭代對象具備Symbol.iterator屬性,是一種與迭代器密切相關的對象。Symbol.iterator經過指定的函數能夠返回一個做用於附屬對象的迭代器。在ES6中,全部的集合對象(數組、Set集合以及Map集合)和字符串都是可迭代對象,這些對象中都有默認的迭代器。因爲生成器默認會爲Symbol.iterator屬性賦值,所以全部經過生成器建立的迭代器都是可迭代對象。

ES6新引入了for-of循環每執行一次都會調用可迭代對象的next方法,並將迭代器返回的結果對象的value屬性存儲在一個變量中,循環將持續執行這一過程直到返回對象的done屬性的值爲true

const value = [1, 2, 3]
for (let num of value) {
  console.log(num);
}
// 1
// 2
// 3
複製代碼

訪問默認的迭代器

能夠經過Symbol.iterator來訪問對象的默認迭代器

const values = [1, 2, 3]
const it = values[Symbol.iterator]()
console.log(it.next())  // {done:false, value:1}
console.log(it.next())  // {done:false, value:2}
console.log(it.next())  // {done:false, value:3}
console.log(it.next())  // {done:true, value:undefined}
複製代碼

因爲具備Symbol.iterator屬性的對象都有默認的迭代器對象,所以能夠用它來檢測對象是否爲可迭代對象:

function isIterator (object) {
  return typeof object[Symbol.iterator] === 'function'
}

console.log(isIterator([1, 2, 3]))  // true
console.log(isIterator('hello'))    // true,字符串也能夠迭代,原理等同於數組
console.log(isIterator(new Set()))  // true
console.log(isIterator(new Map))    // true
複製代碼

建立可迭代對象

默認狀況下,咱們本身定義的對象都是不可迭代對象,但若是給Symbol.iterator屬性添加一個生成器,則能夠將其變爲可迭代對象。

let collection = {
  items: [1, 2, 3],
  *[Symbol.iterator] () {
    for (let item of this.items) {
      yield item
    }
  }
}
for (let value of collection) {
  console.log(value)
}
// 1
// 2
// 3
複製代碼

內建迭代器

集合對象迭代器

ES6中有三種類型的集合對象:數組、Set集合和Map集合,它們都內建了以下三種迭代器:

  • entries:返回一個迭代器,其值爲多個鍵值對。
  • values:返回一個迭代器,其值爲集合的值。
  • keys:返回一個迭代器,其值爲集合中的全部鍵名。

entries()迭代器:

const colors = ['red', 'green', 'blue']
const set = new Set([1, 2, 3])
const map = new Map([['name', 'AAA'], ['age', 23], ['address', '廣東']])

for (let item of colors.entries()) {
  console.log(item)
  // [0, 'red']
  // [1, 'green']
  // [2, 'blue']
}
for (let item of set.entries()) {
  console.log(item)
  // [1, 1]
  // [2, 2]
  // [3, 3]
}
for (let item of map.entries()) {
  console.log(item)
  // ['name', 'AAA']
  // ['age', 23]
  // ['address', '廣東']
}
複製代碼

values迭代器:

const colors = ['red', 'green', 'blue']
const set = new Set([1, 2, 3])
const map = new Map([['name', 'AAA'], ['age', 23], ['address', '廣東']])

for (let item of colors.values()) {
  console.log(item)
  // red
  // green
  // blue
}
for (let item of set.values()) {
  console.log(item)
  // 1
  // 2
  // 3
}
for (let item of map.values()) {
  console.log(item)
  // AAA
  // 23
  // 廣東
}
複製代碼

keys迭代器:

const colors = ['red', 'green', 'blue']
const set = new Set([1, 2, 3])
const map = new Map([['name', 'AAA'], ['age', 23], ['address', '廣東']])

for (let item of colors.keys()) {
  console.log(item)
  // 0
  // 1
  // 2
}
for (let item of set.keys()) {
  console.log(item)
  // 1
  // 2
  // 3
}
for (let item of map.keys()) {
  console.log(item)
  // name
  // age
  // address
}
複製代碼

不一樣集合類型的默認迭代器:每個集合類型都有一個默認的迭代器,在for-of循環中,若是沒有顯示的指定則使用默認的迭代器:

  • 數組和Set集合:默認迭代器爲values
  • Map集合:默認爲entries
const colors = ['red', 'green', 'blue']
const set = new Set([1, 2, 3])
const map = new Map([['name', 'AAA'], ['age', 23], ['address', '廣東']])
for (let item of colors) {
  console.log(item)
  // red
  // green
  // blue
}
for (let item of set) {
  console.log(item)
  // 1
  // 2
  // 3
}
for (let item of map) {
  console.log(item)
  // ['name', 'AAA']
  // ['age', 23]
  // ['address', '廣東']
}
複製代碼

解構和for-of循環:若是要在for-of循環中使用解構語法,則能夠簡化編碼過程:

const map = new Map([['name', 'AAA'], ['age', 23], ['address', '廣東']])
for (let [key, value] of map.entries()) {
  console.log(key, value)
  // name AAA
  // age 23
  // address 廣東
}
複製代碼

字符串迭代器

ES6發佈以來,JavaScript字符串的行爲慢慢變得更像數組了:

let message = 'Hello'
for(let i = 0, len = message.length; i < len; i++) {
  console.log(message[i])
  // H
  // e
  // l
  // l
  // o
}
複製代碼

NodeList迭代器

DOM標準中有一個NodeList類型,表明頁面文檔中全部元素的集合。ES6爲其添加了默認的迭代器,其行爲和數組的默認迭代器一致:

let divs = document.getElementByTagNames('div')
for (let div of divs) {
  console.log(div)
}
複製代碼

展開運算符和非數組可迭代對象

咱們在前面的知識中已經知道,咱們可使用展開運算符把一個Set集合轉換成一個數組,像下面這樣:

let set = new Set([1, 2, 3, 4])
let array = [...set]
console.log(array) // [1, 2, 3, 4]
複製代碼

代碼分析:在咱們所用...展開運算符的過程當中,它操做的是Set集合的默承認迭代對象(values),從迭代器中讀取全部值,而後按照返回順序將他們依次插入到數組中。

const map = new Map([['name', 'AAA'], ['age', 23], ['address', '廣東']])
const array = [...map]
console.log(array) // [['name', 'AAA'], ['age', 23], ['address', '廣東']]
複製代碼

代碼分析:在咱們使用...展開運算符的過程當中,它操做的是Map集合的默承認迭代對象(entries),從迭代器中讀取多組鍵值對,依次插入數組中。

const arr1 = ['red', 'green', 'blue']
const arr2 = ['yellow', 'white', 'black']
const array = [...arr1, ...arr2]
console.log(array) // ['red', 'green', 'blue', 'yellow', 'white', 'black']
複製代碼

代碼分析:在使用...展開運算符的過程當中,同Set集合同樣使用的都是其默認迭代器(values),而後按照返回順序依次將他們插入到數組中。

高級迭代器功能

給迭代器傳遞參數

若是給迭代器next()方法傳遞參數,則這個參數的值就會替代生成器內部上一條yield語句的返回值。

function * createIterator () {
  let first = yield 1
  let second = yield first + 2
  yield second + 3
}
let it = createIterator()
console.log(it.next(11)) // {done: false, value: 1}
console.log(it.next(4))  // {done: false, value: 6}
console.log(it.next(5))  // {done: false, value: 8}
console.log(it.next())   // {done: true, value: undefined}
複製代碼

代碼分析:除了第一個迭代器,其它幾個迭代器咱們能很快計算出結果,但爲何第一個next()方法的參數無效呢?這是由於傳給next()方法的參數是替代上一次yield的返回值,而在第一次調用next()方法以前不會執行任何yield語句,所以給第一次調用的next()方法傳遞參數,不管傳遞任何值都將被捨棄。

在迭代器中拋出錯誤

除了給迭代器傳遞數據外,還能夠給他傳遞錯誤條件,讓其恢復執行時拋出一個錯誤。

function * createIterator () {
  let first = yield 1
  let second = yield first + 2
  // 不會被執行
  yield second + 3
}
let it = createIterator()
console.log(it.next())                    // {done: false, value: 1}
console.log(it.next(4))                   // {done: false, value: 6}
console.log(it.throw(new Error('break'))) // 拋出錯誤
複製代碼

正如咱們上面看到的那樣,咱們想生成器內部傳遞了一個錯誤對象,迭代器恢復執行時會拋出一個錯誤,咱們可使用try-catch語句來捕獲這種錯誤:

function * createIterator () {
  let first = yield 1
  let second
  try {
    second = yield first + 2
  } catch(ex) {
    second = 6
  }
  yield second + 3
}
let it = createIterator()
console.log(it.next())                    // {done: false, value: 1}
console.log(it.next(4))                   // {done: false, value: 6}
console.log(it.throw(new Error('break'))) // {done: false, value: 9}
console.log(it.next())                    // {done: true, value: undefined}
複製代碼

生成器返回語句

因爲生成器也是函數,所以能夠經過return語句提早退出函數執行,對於最後一次next()方法調用,能夠主動爲其指定一個返回值。

function * createIterator () {
  yield 1
  return 2
  // 不會被執行
  yield 3
  yield 4
}
let it = createIterator()
console.log(it.next())  // {done: false, value: 1}
console.log(it.next())  // {done: false, value: 2}
console.log(it.next())  // {done: true, value: undefined}
複製代碼

代碼分析:在生成器中,return語句表示全部的操做都已經完成,屬性值done會被設置成true,若是同時提供了響應的值,則屬性value會被設置爲這個值,而且return語句以後的yield不會被執行。

展開運算符和for-of循環會直接忽略經過return語句指定的任何返回值,由於只要done被設置爲true,就當即中止讀取其餘的值。

const obj = {
  items: [1, 2, 3, 4, 5],
  *[Symbol.iterator] () {
    for (let i = 0, len = this.items.length; i < len; i++) {
      if (i === 3) {
        return 300
      } else {
        yield this.items[i]
      }
    } 
  }
}
for (let value of obj) {
  console.log(value)
  // 1
  // 2
  // 3
}
console.log([...obj]) // [1, 2, 3]
複製代碼

委託生成器

咱們能夠將兩個迭代器合二爲一,這樣就能夠建立一個生成器,再給yield語句添加一個星號,以達到將生成數據的過程委託給其餘迭代器。

function * createColorIterator () {
  yield ['red', 'green', 'blue']
}
function * createNumberIterator () {
  yield [1, 2, 3, 4]
}
function * createCombineIterator () {
  yield * createColorIterator();
  yield * createNumberIterator();
}
let it = createCombineIterator()
console.log(it.next().value)  // ['red', 'green', 'blue']
console.log(it.next().value)  // [1, 2, 3, 4]
console.log(it.next().value)  // undefined
複製代碼
相關文章
相關標籤/搜索