ES2015簡(jiǎn)介和基本語(yǔ)法

ECMAScript 6(以下簡(jiǎn)稱(chēng)ES6)是JavaScript語(yǔ)言的下一代標(biāo)準(zhǔn)由捎。因?yàn)楫?dāng)前版本的ES6是在2015年發(fā)布的仿耽,所以又稱(chēng)ECMAScript 2015娄蔼。也就是說(shuō)赖条,ES6就是ES2015。

說(shuō)明:此文章根據(jù)《實(shí)戰(zhàn)ES2015:深入現(xiàn)代JavaScript+應(yīng)用開(kāi)發(fā)》這本書(shū)做的筆記宪哩,更多詳細(xì)內(nèi)容請(qǐng)查看書(shū)籍娩贷。電子版在文章底部。

一锁孟、ECMAScript的發(fā)展歷程

image.png

二彬祖、ES2015能為實(shí)際開(kāi)發(fā)帶來(lái)什么

ECMAScript的發(fā)展速度在不斷加快,影響范圍越來(lái)越大品抽,除了Web前端開(kāi)發(fā)以外储笑,借助著Node.js的力量在服務(wù)器、桌面端甚至硬件設(shè)備等領(lǐng)域中也發(fā)光發(fā)熱著圆恤。

ES2015概述:

ES2015標(biāo)注提供了許多新的語(yǔ)法和編程特性以提高ECMAScript的開(kāi)發(fā)效率并優(yōu)化ECMAScript的開(kāi)發(fā)體驗(yàn)南蓬。
ES2015的別名:Harmony(和諧)

語(yǔ)法糖:

ECMAScript帶來(lái)了可用性非常高的語(yǔ)法糖,這些語(yǔ)法糖的開(kāi)發(fā)初衷是方便開(kāi)發(fā)者使用哑了,使用語(yǔ)法糖能夠增加程序的可讀性赘方,從而減少程序代碼出錯(cuò)的幾率。
如ES2015中非常重要的箭頭函數(shù)弱左,大大地增強(qiáng)了ECMAScript在復(fù)雜業(yè)務(wù)邏輯中的處理能力窄陡。

使用ES2015前:

el.on('click',function(evt) {
    var self = this;
    fetch('/api').then(function (res) {
        return res.json();
    }).then(function (result) {
        self.something(result);
        //...
    })
})

使用ES2015后:

el.on('click',evt=>{
    fetch('/api').then(res=>res.json()).then(result=>this.something(result))
})

模塊化和組件化:

在程序代碼可以通過(guò)模塊化進(jìn)行解耦后,組件化開(kāi)發(fā)便能借此進(jìn)一步推進(jìn)項(xiàng)目程序的工程化進(jìn)度拆火。組件化開(kāi)發(fā)是模塊化開(kāi)發(fā)的高級(jí)體現(xiàn)跳夭,組件化更能表現(xiàn)出模塊化開(kāi)發(fā)的意義和重要性。
組件化開(kāi)發(fā)所重視的是組件之間的非耦合關(guān)系和組件的可重用性们镜,組件之間也可以存在依賴(lài)性币叹,可以利用模塊化來(lái)實(shí)現(xiàn)組件化開(kāi)發(fā)。同一類(lèi)內(nèi)容塊可以抽象化為一個(gè)組件模狭,并在生產(chǎn)中重復(fù)使用颈抚。

const和let:

const為ECMAScript帶來(lái)了定義常量的能力,let為ECMAScript修復(fù)了從前var因?yàn)榇a習(xí)慣不佳而導(dǎo)致的代碼作用域混亂等問(wèn)題嚼鹉,同時(shí)實(shí)現(xiàn)了塊狀作用域贩汉。
const可以實(shí)現(xiàn)變量名與內(nèi)存地址的強(qiáng)綁定驱富,讓變量不會(huì)因?yàn)槌硕x語(yǔ)句和刪除語(yǔ)句以外的代碼而丟失內(nèi)存地址的綁定,從而保證了變量與內(nèi)存之間的安全性匹舞。

總結(jié):語(yǔ)法糖褐鸥、模塊化、組件化等工程優(yōu)勢(shì)赐稽,可以在實(shí)際開(kāi)發(fā)中提升開(kāi)發(fā)效率和代碼質(zhì)量叫榕。

三、ES2015新語(yǔ)法

新語(yǔ)法:

  • let姊舵、const和塊級(jí)作用域
  • 箭頭函數(shù)(Arrow Function)
  • 模板字符串(Template String)
  • 對(duì)象字面量擴(kuò)展語(yǔ)法(Enhanced Object Literals)
  • 表達(dá)式結(jié)構(gòu)(Destructuring)
  • 函數(shù)參數(shù)表達(dá)晰绎、傳參
  • 新的數(shù)據(jù)結(jié)構(gòu)
  • 類(lèi)語(yǔ)法(Classes)
  • 生成器(Generator)
  • Promise
  • 代碼模塊化
  • Symbol
  • Proxy

let、const和作用域

let和const是繼var之后新的變量定義方法蠢莺,與let相比,const更容易被理解零如。const就是constant的縮寫(xiě)躏将,用于定義變量,即不可變量考蕾。const定義常量的原理是阻隔變量名所對(duì)應(yīng)的內(nèi)存地址被改變祸憋。
變量與內(nèi)存之間的關(guān)系由三個(gè)部分組成:變量名、內(nèi)存綁定和內(nèi)存(內(nèi)存地址)肖卧。


image.png

ECMAScript在對(duì)變量的引用進(jìn)行讀取時(shí)蚯窥,會(huì)從該變量對(duì)應(yīng)的內(nèi)存地址所指向的內(nèi)存空間中讀取內(nèi)容。當(dāng)用戶改變變量的值時(shí)塞帐,引擎會(huì)重新從內(nèi)存中分配一個(gè)新的內(nèi)存空間以存儲(chǔ)新的值拦赠,并將新的內(nèi)存地址與變量進(jìn)行綁定。const的原理便是在變量名與內(nèi)存地址之間建立不可變得綁定葵姥,當(dāng)后面的程序嘗試申請(qǐng)新的內(nèi)存空間時(shí)荷鼠,引擎便會(huì)拋出錯(cuò)誤。

在ES2015中榔幸,let可以說(shuō)是var的進(jìn)化版本允乐,var大部分情況下可以被let替代,let和var的異同點(diǎn)如下表:

image.png

變量的生命周期:

在ECMAScript中削咆,一個(gè)變量(或常量)的生命周期(Life Cycle)模式是固定的牍疏,由兩種因素決定,分別是作用域和對(duì)其的引用拨齐。

從工程化的角度鳞陨,應(yīng)該在ES2015中遵從以下三條原則:

(1)一般情況下,使用const來(lái)定義值的存儲(chǔ)容器(常量)瞻惋;
(2)只有在值容器明確地被確定將會(huì)被改變時(shí)才使用let來(lái)定義(變量)炊邦;
(3)不再使用var编矾。

循環(huán)語(yǔ)句:

ECMAScript引入了一種新的循環(huán)語(yǔ)句for...of,主要的用途是代替for...in循環(huán)語(yǔ)句馁害;為Array對(duì)象引入了Array.forEach方法以代替for循環(huán)窄俏,Array.forEach方法的特點(diǎn)是自帶閉包,以解決因?yàn)槿狈K級(jí)作用域?qū)е滦枰褂萌∏傻姆椒▉?lái)解決var的作用域問(wèn)題碘菜。
因?yàn)閴K級(jí)作用域的存在凹蜈,使得for循環(huán)中的每一個(gè)當(dāng)前值可以僅保留在所對(duì)應(yīng)的循環(huán)體中,配合for-of循環(huán)語(yǔ)句更是免去了回調(diào)函數(shù)的使用忍啸。

const arr=[1,2,3];
for(const item of arr){
     console.log(item);
}

配合ES2015中的解構(gòu)(Destructuring)特性仰坦,在處理JSON數(shù)據(jù)時(shí),更加得心應(yīng)手计雌。

const Zootopia=[
    {name:'Nick',gender:1,species:'Fox'},
    {name:'Judy',gender:0,species:'Bunny'}
];
for(const {name,species} of Zootopia){
    console.log(`hi,I am ${name},and I am a ${species}`);
}

forEach方法需要傳入一個(gè)回調(diào)函數(shù)來(lái)接收循環(huán)的每一個(gè)循環(huán)元素并作為循環(huán)體以執(zhí)行悄晃。同時(shí),這個(gè)回調(diào)函數(shù)在標(biāo)準(zhǔn)定義中會(huì)被傳入三個(gè)參數(shù)凿滤,分別為:當(dāng)前值妈橄,當(dāng)前值的下標(biāo)和循環(huán)數(shù)組自身。在ES2015標(biāo)準(zhǔn)中翁脆,數(shù)組類(lèi)型再次被賦予了一個(gè)名為entries的方法眷蚓,它可以返回對(duì)應(yīng)的數(shù)組中每一個(gè)元素與其下標(biāo)配對(duì)的一個(gè)新數(shù)組。

這個(gè)新特性可以與解構(gòu)和for-of循環(huán)配合使用反番。

const Zootopia=[
    {name:'Nick',gender:1,species:'Fox'},
    {name:'Judy',gender:0,species:'Bunny'}
];
for(const [index,{name,species}] of Zootopia.entries){
    console.log(`${index}.Hi,I am ${name},and I am a ${species}`);
}
//0.Hi,I am Nick,and I am a Fox
//1.Hi,I am Judy,and I am a Bunny

箭頭函數(shù)

箭頭函數(shù)沙热,顧名思義便是使用箭頭(=>)進(jìn)行定義的函數(shù),屬于匿名函數(shù)(Anonymous Function)一類(lèi)罢缸。
相對(duì)于傳統(tǒng)的function語(yǔ)句篙贸,箭頭函數(shù)在簡(jiǎn)單函數(shù)使用中更為簡(jiǎn)潔直觀。

const arr=[1,2,3];
//箭頭函數(shù)
const squares=arr.map(x=>x*x);
//傳統(tǒng)語(yǔ)法
const squares=arr.map(function(x){return x*x});

箭頭函數(shù)有四種使用語(yǔ)法
(1)單一參數(shù)的單行箭頭函數(shù)

//Syntax:arg=>statement
const fn=foo=>`${foo} world`  //means return `foo +' world'` 

這是箭頭函數(shù)最簡(jiǎn)潔的形式枫疆,常見(jiàn)于用作簡(jiǎn)單的處理函數(shù)歉秫,如過(guò)濾。

let array=['a','bc','def','ghij'];
array=array.filter(item=>item.length>=2);  //bc,def,ghij

(2)多參數(shù)的單行箭頭函數(shù)

//Syntax:(arg1,arg2)=>statement
const fn=(foo,bar)=>foo+bar

多參數(shù)的語(yǔ)法跟普通函數(shù)一樣养铸,以括號(hào)來(lái)包裹參數(shù)列雁芙,這種形式常見(jiàn)于數(shù)組的處理,如排序钞螟。

let array=['a','bc','def','ghij'];
array=array.sort((a,b)=>a.length<b.length);  //ghij,def,bc,a

(3)多行箭頭函數(shù)

//Syntax:arg=>{...}
//單一參數(shù) 
foo=>{return `${foo} world`}
//Syntax:(arg1,arg2)=>{...}
//多參數(shù)
(foo+bar)=>{return foo+bar}

(4)無(wú)參數(shù)箭頭函數(shù)
如果一個(gè)箭頭函數(shù)無(wú)參數(shù)傳入兔甘,需要用一對(duì)空的括號(hào)來(lái)表示空的參數(shù)列表。

//Syntax:()=>statement
const greet=()=>'hello world'

模板字符串

當(dāng)我們使用普通的字符串時(shí)鳞滨,會(huì)使用單引號(hào)或雙引號(hào)來(lái)包裹字符串的內(nèi)容洞焙,在ES2015的模板字符串中使用反勾號(hào)`。

//Syntax:`string...`
const str=`something`

(1)支持元素注入:
可以將一些元素注入到ES2015的模板字符串中。

//Syntax:`before-${injectVariable}-after`
const str="hello world"
const num=1
const bool=true
const obj={foo:'bar'}
const arr=[1,2,3]

const str1=`String:${str}`         //=>String:hello world
const str2=`Number:${num}`         //=>Number:1
const str3=`Boolean:${bool}`      //=>Boolean:true
const str4=`Object:${obj}`        //=>Object:[object Object]
const str5=`Array:${arr}`        //=>Array:1,2,3

(2)支持換行:

/**
*Syntax:`
*content
*`
*/
const sql=`
select * from Users 
where FirstName='mike' 
limit 5;
`

多行字符串無(wú)法像普通字符串使用雙引號(hào)嵌套單引號(hào)來(lái)表達(dá)字符串中的字符串澡匪,可以使用反斜杠將需要顯示的反勾號(hào)轉(zhuǎn)義為普通的字符熔任。添加了\`用于打印`。

const str1="Here is the outer string.'This is a string in another string'"
const str2=`Here is the outer string.\`This is a string in another string\``

對(duì)象字面量擴(kuò)展語(yǔ)法

在ES2015之前的ECMAScript的標(biāo)準(zhǔn)中唁情,對(duì)象字面量只是一種用于表達(dá)對(duì)象的語(yǔ)法疑苔,只具有表達(dá)的功能,并不起到更大的作用甸鸟。在ES2015中惦费,為ECMASCript開(kāi)發(fā)者開(kāi)放了更多關(guān)于對(duì)象的操作權(quán)限,其中便有更多的對(duì)象字面量語(yǔ)法抢韭。

(1)函數(shù)類(lèi)屬性的省略語(yǔ)法:
ES2015中引入了類(lèi)機(jī)制(Class)薪贫,普通的對(duì)象字面量也吸收了一些語(yǔ)法糖,可以讓方法屬性省略function刻恭,以一種直觀的語(yǔ)法來(lái)表達(dá)瞧省。

//Syntax:{method(){...}}
const obj={
    //before
   foo:function(){
     return 'foo'
   },
   //after
  bar(){
    return 'bar'
   }
}

有了這個(gè)語(yǔ)法糖,對(duì)象字面量中的方法類(lèi)屬性更像是一個(gè)方法鳍贾,而不只是一個(gè)以函數(shù)為值得屬性鞍匾。

(2)支持_proto_注入:
在ES2015中開(kāi)放了向?qū)ο笞置媪孔⑷隷proto_的功能,這樣做的意義在于開(kāi)發(fā)者可以得到更高的操作權(quán)限贾漏,從而更加靈活地創(chuàng)建和操作對(duì)象候学。
在ES2015標(biāo)準(zhǔn)中藕筋,開(kāi)發(fā)者允許直接向一個(gè)對(duì)象字面量注入_proto_纵散,使其直接成為指定類(lèi)的一個(gè)實(shí)例,無(wú)須另外創(chuàng)建一個(gè)類(lèi)來(lái)實(shí)現(xiàn)繼承隐圾。

//Syntax:{_proto_:...}
import {EventEmitter} from 'events'

const machine={
    _proto_:new EventEmitter(),
    method(){...}
}

console.log(machine)   //=>EventEmitter{}
console.log(machine instanceof EventEmitter)  //=>true

(3)可動(dòng)態(tài)計(jì)算的屬性名:
在ES2015標(biāo)準(zhǔn)對(duì)于對(duì)象字面量的處理中伍掀,引入了一個(gè)新語(yǔ)法,這個(gè)語(yǔ)法允許我們直接使用一個(gè)表達(dá)式來(lái)表達(dá)一個(gè)屬性名暇藏。

//Syntax:{[statement]:value}
const prefix='es2015'
const obj={
     [prefix+'enhancedObject']:'foobar'
}

(4)將屬性名定義省略:
在某些場(chǎng)景中蜜笤,需要將一些已經(jīng)別定義的變量(或常量)作為其它對(duì)象字面量的屬性值進(jìn)行返回或傳入操作。

//Syntax:{injectVariable}
const foo=123
const bar =()=>foo

const obj={
      foo,
      bar
}
console.log(obj)  //=>{foo:123,bar:[Function:bar]}

表達(dá)式結(jié)構(gòu)

在ES2015之前工程師們一般使用對(duì)象字面量和數(shù)組來(lái)模擬函數(shù)多返回值盐碱,在ES2015中同樣可以使用類(lèi)似的語(yǔ)法來(lái)實(shí)現(xiàn)函數(shù)多返回值把兔,且語(yǔ)法上更加簡(jiǎn)潔。

(1)使用對(duì)象作為返回載體(帶有標(biāo)簽的多返回值)

//Syntax:{arg1,arg2}={arg1:value1,arg2:value2}

function getState(){
   return {
      error:null,
      logined:true,
      user:{},
  }
}

const {error,logined,user}=getState()

(2)使用數(shù)組作為返回載體
使用數(shù)組作為返回載體與使用對(duì)象作為返回載體的區(qū)別是:數(shù)組需要讓被賦予的變量(或常量)名按照數(shù)組的順序獲得值瓮顽。

//Syntax:[arg1,arg2]=[value1,value2]
const[foo,bar]=[1,2]
console.log(foo,bar)  //=>1  2

跳過(guò)數(shù)組中某些元素,通過(guò)空開(kāi)一個(gè)元素的方式來(lái)實(shí)現(xiàn)。

//Syntax:[arg1, ,bar]=[1,2,3]
console.log(foo,bar)   //=>1  3

不定項(xiàng)的獲取后續(xù)元素熬芜,用...語(yǔ)句實(shí)現(xiàn)参淫。

//Syntax:[arg1,arg2,...restArgs]=[value1,value2,value3,value4]
const [a,b,...rest]=[1,2,3,4,5]
console.log(a,b)     //=>1   2
console.log(rest)   //=>[3,4,5]

(3)使用場(chǎng)景

  • Promise與模式匹配
    注意:如果在Promise.then方法中傳入的是一個(gè)帶有解構(gòu)參數(shù)的箭頭函數(shù)時(shí),解構(gòu)參數(shù)外必須要有一個(gè)括號(hào)包裹,否則會(huì)拋出語(yǔ)法錯(cuò)誤晾咪。
function fetchData(){
    return new Promise((resolve,reject)=>{
          resolve(['foo','bar'])
    })
}

fetchData().then(([value1,value2])=>{
        console.log(value1,value2)   //=>foo  bar
})

fetchData().then([value1,value2]=>{   //=>SyntaxError
        //...
})

如果參數(shù)過(guò)多但在某些場(chǎng)景下并不需要全部參數(shù)收擦,或者文檔約定不完善的情況下,使用對(duì)象作為傳遞載體更佳谍倦。

function fetchData(){
    return new Promise((resolve,reject)=>{
          resolve({
             code:200,
             message:ok,
             data:['foo','bar']
           })
    })
}

fetchData().then(({data})=>{
        console.log(data)   //=>foo  bar ...
})
  • Swap(變量值交換)
    Swap表示定義一個(gè)函數(shù)或一種語(yǔ)法來(lái)交換兩個(gè)變量的值塞赂。在ES2015中,可以使用模式匹配來(lái)實(shí)現(xiàn)Swap剂跟。
 function swap(a,b){
    var tmp=a
     a=b
     b=tmp
 }
 let foo=1
 let bar=2

//Before Swap
console.log(foo,bar)  //=>1   2
//Swap
[foo,bar]=[bar,foo]
//After Swap
console.log(foo,bar)  //=>2  1

(4)高級(jí)用法

  • 解構(gòu)別名
    如果不想使用其中的屬性名作為新的變量名(或常量名)减途,可以使用別名獲得相應(yīng)的返回值,只要在原來(lái)的返回值后面加上“:x”曹洽,其中x就是希望使用的變量名鳍置。
function fetchData(){
    return{
       response:['foo','bar']
    }
}

const{response:data}=fetchData()
console.log(data)   //=>foo bar
  • 無(wú)法匹配的缺省值
    如果在模式匹配中送淆,存在無(wú)法匹配的缺省值(載體對(duì)象不存在相應(yīng)的值或目標(biāo)參數(shù)所對(duì)應(yīng)下標(biāo)超出了載體數(shù)組的下標(biāo)范圍),默認(rèn)情況下會(huì)返回undefined。
//Object
const {foo,bar}={foo:1}
console.log(foo,bar)   //=>1  undefined
//Array
const [a,b,c]=[1,2]
console.log(a,b,c)   //=>1   2   undefined

如果不希望得到undefined,可以為參數(shù)賦予一個(gè)默認(rèn)值,當(dāng)無(wú)法匹配到相應(yīng)的值時(shí),會(huì)使用該默認(rèn)值。

const {foo=1}={bar:1}
console.log(foo)   //=>1

const [a,b=2]=[1]
console.log(a,b)   //=>1   2
  • 深層匹配
    通過(guò)嵌套解構(gòu)表達(dá)式來(lái)獲取深層的內(nèi)容,可以在對(duì)象中嵌套數(shù)組來(lái)獲取對(duì)象中數(shù)組的某元素祟绊,反之亦然嘉熊。
 //Object in Object
const  {a,b:{c}}={a:1,b:{c:2}}
console.log(a,c)   //=>1  2

//Array in Object
const  {d,e:[f]}={d:1,e:[2,3]}
console.log(d,f)  //=>1  2

//Object in Array
consot [g,{h}]=[1,{h:2}]
console.log(g,h)  //=>1  2

//Array in Array
const [i,[j]]=[1,[2,3]]
console.log(i,j)  //=>1   2

函數(shù)參數(shù)表達(dá)、傳參

(1)默認(rèn)參數(shù)值
使用語(yǔ)法:
ES2015中使用語(yǔ)法直接實(shí)現(xiàn)默認(rèn)參數(shù)值語(yǔ)法顯得更加簡(jiǎn)潔而直觀晨炕。

//Syntax:function name(arg=defaultValue){...}
function fn(arg='foo'){
  console.log(arg)
}
fn()  //=>foo
fn('bar')   //=>bar

使用場(chǎng)景:
同時(shí)提供回調(diào)函數(shù)和Promise返回方式的接口

const noop=()=>{}

function api(callback=noop){
   return new Promise((resolve,reject)=>{
       const value='footbar'
           resolve(value)
               callback(null,value)
       })
}
//Callback
api((err,data)=>{
   if(err) return console.error(err)
})
//Promise
api().then(value=>{
   //...
})
.catch(err=>console.error(err))

函數(shù)的默認(rèn)參數(shù)特性用在某一個(gè)對(duì)象的方法中费奸,所指定的默認(rèn)參數(shù)還可以被定為該對(duì)象的某一個(gè)屬性

const obj={
    msg:'World',
    greet(message=this.msg){
        console.log(`Hello ${message}`)
    }
}

obj.greet()   //=>Hello World
obj.greet('ES2015')   //=>Hello ES2015

(2)剩余參數(shù)
使用語(yǔ)法
ES2015中對(duì)剩余參數(shù)有了更為優(yōu)雅和標(biāo)準(zhǔn)的語(yǔ)法,直接將需要獲取的參數(shù)列表轉(zhuǎn)換為一個(gè)正常數(shù)組,以便使用戈二。

//Syntax:function fn([arg,]...restArgs){}
function fn(foo, ...rest){
     console.log(`foo:${foo}`)
     console.log(`Rest Arguments:${rest.join(',')}`)
}
fn(1,2,3,4,5)
//=>foo:1
//Rest Arguments:2,3,4,5

使用場(chǎng)景
十分常用的merge和mixin函數(shù)(合并對(duì)象)就會(huì)需要使用到剩余函數(shù)這個(gè)特性來(lái)實(shí)線觉吭。

function merge(target={},...objs){
    for(const obj of objs){
        const keys=Object.keys(obj)
        for(const key of keys){
            target[key]=obj(key)
        }
    }
    return target
}
console.log(merge({a:1},{b:2},{c:3}))   //=>{a:1,b:2,c:3}

注意事項(xiàng)
注意:一旦一個(gè)函數(shù)的參數(shù)列表中使用了剩余參數(shù)的語(yǔ)法糖仆邓,便不可以再添加任何參數(shù)鲜滩,否則會(huì)拋出錯(cuò)誤节值。

function fn1(...rest){}    //Correct
function fn1(...rest,foo){}  //Syntax Error

arguments與剩余函數(shù)
雖然從語(yǔ)言角度看,arguments和...args是可以同時(shí)使用的豌汇,但有一種情況除外,arguments在箭頭函數(shù)中佛嬉,會(huì)跟隨上下文綁定到上層逻澳,所以在不確定上下文綁定結(jié)果的情況下,盡可能不要在箭頭函數(shù)中使用arguments暖呕,而要使用..args斜做。
(3)解構(gòu)傳參
ES2015中的解構(gòu)傳參是使用數(shù)組作為傳入?yún)?shù)以控制函數(shù)的調(diào)用情況,不同的是解構(gòu)傳參不會(huì)替換函數(shù)調(diào)用中的上下文湾揽。
與剩余參數(shù)一樣陨享,解構(gòu)傳參使用...作為語(yǔ)法糖標(biāo)識(shí)符。

//Syntax:fn(...[arg1,arg2])
function sum(...numbers){
return numbers.reduce((a,b)=>a+b)
}
sum(...[1,2,3])  //=>6

新的數(shù)據(jù)解構(gòu)

在ECMAScript中定義了以下幾種基本的數(shù)據(jù)結(jié)構(gòu)钝腺,分為值類(lèi)型(Primitive Types)和引用類(lèi)型(Reference
Types)抛姑。

值類(lèi)型數(shù)據(jù)結(jié)構(gòu):

  • String 字符串
  • Number 數(shù)值
  • Boolean 布爾型(true與false)
  • Null 空值
  • Undefined 未定義值

引用類(lèi)型數(shù)據(jù)結(jié)構(gòu):

  • Object 對(duì)象
  • Array 數(shù)組
  • RegExp(Regular Expression with pattern)正則表達(dá)式
  • Date 日期
  • Error 錯(cuò)誤

(1)Set有序集合
ECMAScript中,Array表示一系列元素的有序集合艳狐,其中每一個(gè)元素都會(huì)帶有自身處在這個(gè)集合內(nèi)的位置并以自然數(shù)作為標(biāo)記定硝,即帶有下標(biāo)。無(wú)序集合可以把它當(dāng)成沒(méi)有排序概念的數(shù)組毫目,并且元素不可重復(fù)蔬啡。

使用語(yǔ)法
在ES2015中,集合與數(shù)組不一樣的是镀虐,集合無(wú)法像數(shù)組那樣使用[]語(yǔ)法來(lái)直接生成箱蟆,而需要用新建對(duì)象的方法來(lái)創(chuàng)建一個(gè)新的集合對(duì)象。

//Syntax:new Set([iterable]):Set
const set=new Set()

可以使用一個(gè)現(xiàn)成的數(shù)組作為集合對(duì)象的初始元素

const set=new Set([1,2,3])

集合對(duì)象的操作方法


image.png

增刪元素
可以通過(guò)add刮便、delete和clear方法來(lái)添加空猜,刪除,清空集合內(nèi)的元素恨旱。

const set =new Set()
//添加元素
set.add(1)
.add(2)
.add(3)
.add(3)  //這一句不會(huì)起到任何作用辈毯,因?yàn)樵?已存在于集合內(nèi)
console.log(set)     //Set{1,2,3}

//刪除元素
set.delete(2)
console.log(set)   //Set{1,3}

//清空集合
set.clear()
console.log(set)    //set{}

檢查元素

const set=new Set([1,2,3])
//檢查元素
set.has(2)   //=>true
set.has(4)  //=>false

遍歷元素
集合對(duì)象自身定義了forEach方法,跟數(shù)組類(lèi)型中的forEach一樣搜贤,傳入一個(gè)回調(diào)函數(shù)以接受集合內(nèi)的元素谆沃,并且可以為這個(gè)回調(diào)函數(shù)指定一個(gè)上下文。

const set=new Set([1,2,3,4])
set.forEach(item=>{
    console.log(item)
})
//=>1    2    3    4

set.forEach(item=>{
    console.log(item*this.foo)
},{foo:2})
//=>2    4    6    8

在ES2015中仪芒,由于Symbol的引入唁影,數(shù)組等類(lèi)型有了一個(gè)新屬性Symbol.iterator(迭代子)耕陷,這些類(lèi)型的新名稱(chēng)--可迭代對(duì)象(Iterable Object),其中包括數(shù)組類(lèi)型据沈、字符串類(lèi)型、集合類(lèi)型卓舵、字典類(lèi)型(Map)南用、生成器類(lèi)型(Generator),for-of循環(huán)語(yǔ)句可以對(duì)可迭代對(duì)象進(jìn)行迭代掏湾,配合const或let使用裹虫,從而解決forEach方法不可中斷的問(wèn)題。

const set=new Set([1,2,3,4])
for(const val of set){
   console.log(val)
}
//=>1   2    3   4

(2)WeakSet
WeakSet最大的應(yīng)用意義在于融击,可以直接對(duì)引擎中垃圾收集器的運(yùn)行情況有程序化的探知方式筑公,開(kāi)發(fā)者可以利用WeakSet的特性以更高的定制化方案來(lái)優(yōu)化程序的內(nèi)存使用方案。

WeakSet與Set的區(qū)別:
a.WeakSet不能包含值類(lèi)型元素尊浪,否則會(huì)拋出一個(gè)TypeError匣屡;
b.WeakSet不能包含無(wú)引用的對(duì)象,否則會(huì)自動(dòng)清除出集合拇涤;
c.WeakSet無(wú)法被探知其大小捣作,也無(wú)法被探知其中所包含的元素。

(3)Map映射類(lèi)型
映射類(lèi)型在計(jì)算機(jī)科學(xué)中的定義屬于關(guān)聯(lián)數(shù)組(Associative Array)鹅士,關(guān)聯(lián)數(shù)組的定義為若干個(gè)鍵值對(duì)(Key/Value Pair)組成的集合券躁,其中每一個(gè)鍵都只能出現(xiàn)一次。

使用語(yǔ)法
映射類(lèi)型需要?jiǎng)?chuàng)建一個(gè)相應(yīng)的實(shí)例來(lái)使用掉盅。

//Syntax:new Map([iterable]):Map
const map=new Map()

在創(chuàng)建映射對(duì)象時(shí)也拜,可以將一個(gè)以二元數(shù)組(鍵值對(duì))作為元素的數(shù)組傳入到構(gòu)建函數(shù)中,其中每一個(gè)鍵值對(duì)都會(huì)加入到該映射對(duì)象中。該數(shù)組內(nèi)的元素會(huì)以數(shù)組順序進(jìn)行處理趾痘,如果存在相同的鍵慢哈,則會(huì)按照FIFO(First In First Out,先進(jìn)先出)原則永票,以該鍵最后一個(gè)處理的對(duì)應(yīng)值為最終值卵贱。

const map = new Map([['foo', 1 ], [ 'foo', 2 ]])
console.log(map.get('foo'))   //=> 2

與對(duì)象字面量一樣,映射對(duì)象可以對(duì)其中的鍵值對(duì)進(jìn)行添加瓦侮、檢查艰赞、獲取佣谐、刪除等操作肚吏。
當(dāng)然,作為新特性的映射對(duì)象也擁有一些Object沒(méi)有的方法狭魂。


image.png

增刪鍵值對(duì)
與集合對(duì)象類(lèi)似罚攀,可以通過(guò)set党觅、delete和clear方法對(duì)映射對(duì)象內(nèi)的鍵值對(duì)進(jìn)行操作。

const map=new Map()
// 添加鍵值對(duì)
map.set('foo','hello')
map.set('bar','es2015')
map.set('bar','world')   //=>將覆蓋之前加入的值

//刪除指定的鍵值對(duì)
map.delete('foo')
//清空映射對(duì)象
map.clear()

獲取鍵值對(duì)
映射對(duì)象由鍵值對(duì)組成斋泄,所以可以利用鍵來(lái)獲取相應(yīng)的值杯瞻。

const map=new Map()
map.set('foo','bar')
console.log(map.get('foo'))    //=> bar

檢查鍵值對(duì)
映射對(duì)象可以通過(guò)has (key)方法來(lái)檢査其中是否包含某一個(gè)鍵值對(duì)

const map=new Map([ 'foo', 1 ])
console.log(map.has('foo')) //=> true
console.log(map.has('bar')) //=>false

遍歷鍵值對(duì)
映射對(duì)象是關(guān)聯(lián)數(shù)組的一種實(shí)現(xiàn),所以映射對(duì)象在設(shè)計(jì)上同樣是一種可迭代對(duì)象炫掐,可以通過(guò)for-of循環(huán)語(yǔ)句對(duì)其中的鍵值對(duì)進(jìn)行歷遍魁莉。也可以使用己實(shí)現(xiàn)在映射對(duì)象中的forEach方法來(lái)進(jìn)行歷遍。

映射對(duì)象帶有entries ()方法募胃,這個(gè)與集合對(duì)象中的entries()類(lèi)似旗唁,用于返回一個(gè)包
含所有鍵值對(duì)的可迭代對(duì)象,而for-of循環(huán)語(yǔ)句和forEach便是先利用entries ()方法先
將映射對(duì)象轉(zhuǎn)換為一個(gè)類(lèi)數(shù)組對(duì)象痹束,然后再進(jìn)行迭代检疫。

const map=new Map([['foo',1],['bar',2]])
console.log(Array.from(map.entries()))     //=>[['bar',1],['bar',2]]

for(const [key,value] of map){
    console.log(`${key}:${value}`)
}
//=>foo:1     bar:2

map.forEach((value,key,map)=>{
  console.log(`${key}:${value}`)
})

(4)WeakMap
WeakMap的鍵會(huì)檢查變量引用,只要其中任意一個(gè)引用被解除祷嘶,該值對(duì)就會(huì)被刪除屎媳。

//Syntax:new WeakMap([iterable]):WeakMap
const weakm=new WeakMap()
let keyObject={id:1}
const valObject={score:100}

weakm.set(keyObject,valObject)
weakm.get(keyObject)    //=>{score:100}
keyObject=null
console.log(weakm.has(keyObject))   //=>false

類(lèi)語(yǔ)法

ES2015中的類(lèi)語(yǔ)法與其他C語(yǔ)言家族成員的類(lèi)語(yǔ)法有許多相同之處,如果開(kāi)發(fā)者有在
JavaScript中使用過(guò)基于原型的類(lèi)機(jī)制论巍,那么也可以很容易接受ES2015的語(yǔ)法烛谊。

基本定義語(yǔ)法

// Syntax: class name { ... }
class Animal {
    constructor(family, specie, hue) {
        this.family =family
        this.specie = specie
        this.hue = hue

    yell() {
      console.log(this.hue)
   }
}

const doge = new Animal('Canidae', 'Canis lupus’, 'Woug')
doge.yell()   //=> Woug

這里需要注意的是嘉汰,在類(lèi)中定義的方法晒来,都是帶有作用域的普通函數(shù),而不是箭頭函數(shù)郑现,方法內(nèi)第一層所引用的this都指向當(dāng)前實(shí)例湃崩,如果實(shí)例方法內(nèi)包含箭頭函數(shù),則引擎就會(huì)根據(jù)包含層級(jí)把箭頭函數(shù)內(nèi)引用的this所指向的實(shí)際對(duì)象一直向上層搜索接箫,直到到達(dá)一個(gè)函數(shù)作用域或塊級(jí)作用域?yàn)橹乖芏痢H绻恢彼阉鞯竭_(dá)了運(yùn)行環(huán)境的最上層,就會(huì)被指向undefined辛友。

class Point{
    constructor(x,y){
        this.x=x
        this.y=y
    }
    moveRight(step){
        return new Promise(resolve=>resolve({
            x:this.x+step,
            y:this.y
            }))
    }
}
const p=new Point(2,5)
p.moveRight(3)
    .thien(({x,y})=>console.log(`(${x},${y}`))   //=>(5,5)

繼承語(yǔ)法

//Syntax:class SubClass extends  SuperClass{}
class Point2D{
    constructor(x,y){
        this.x=x
        this.y=y
    }
    toString(){
        return `(${this.x},${this.y})`
    }
}
class Point3D extends Point2D{
    constructor(x,y,z){
        super(x,y)
        this.x=x
    }
    toString(){
        return `(${this.x},${this.y},${this.z})`
    }
}

ES2015的繼承語(yǔ)法可以將以前使用構(gòu)建函數(shù)模擬的類(lèi)作為父類(lèi)來(lái)繼承薄扁,并非只由class語(yǔ)法定義的類(lèi)才可以使用。

function Cat() {}
Cat.prototype.climb = function () {
    return "I can climb"
}
Cat.prototype.yell = function () {
    return "Meow"
}
class Tiger extends Cat{
    yell(){
        return "Aoh"
    }
}

const tiger=new Tiger()
console.log(tiger.yell())    //=>Aoh
console.log(tiger.climb())  //=>I can climb

需要注意的是废累,如果一個(gè)子類(lèi)繼承了父類(lèi)邓梅,那么在子類(lèi)的constructor構(gòu)造函數(shù)中必須使用super函數(shù)調(diào)用父類(lèi)的構(gòu)造函數(shù)后才能在子類(lèi)的constructor構(gòu)造函數(shù)中使用this,否則會(huì)報(bào)出this is defined的錯(cuò)誤邑滨。

class Foo{}
class Bar extends Foo{
   constructor(){
      this.property=1
   }
}
new Bar()  //=>RerenceError:this is defined

這個(gè)問(wèn)題在除constructor構(gòu)造函數(shù)以外的方法中并不會(huì)出現(xiàn)日缨,即便在子類(lèi)的構(gòu)造
函數(shù)中并沒(méi)有調(diào)用super函數(shù),在其他方法中依然可以調(diào)用this來(lái)指向當(dāng)前實(shí)例掖看。

Getter/Setter
Getter/Setter是一種元編程(Meta-programming)的概念匣距,元編程的特點(diǎn)在于面哥,允許程序可以對(duì)運(yùn)行時(shí)(Runtime)的對(duì)象進(jìn)行讀取和操作,從而使程序可以脫離代碼從字面上為程序定義的一些限制毅待,有了對(duì)對(duì)象的更高操作權(quán)限尚卫。

 const List={
    _array:[],
     set new(value){
        this._array.push(value)
     },
     get last(){
         return this._array[0]
     },
     get value(){
         return this._array
     }
 }

List.new=1
List.new=2
List.new=3
console.log(List.last)     //=>1
console.log(List.value)    //=>[1,2,3]

ES2015的類(lèi)機(jī)制同樣支持Getter/Setter在類(lèi)中的使用,配合元編程的概念尸红,類(lèi)的能力會(huì)變得更加強(qiáng)大吱涉。

class Point{
    constructor(x,y){
        this.x=x
        this.y=y
    }
    get d(){
        return Math.sqrt(Math.pow(this.x,2)+Math.pow(this.y,2))
    }
}
const p=new Point(3,4)
console.log(p.d)   //=>5

靜態(tài)方法
可以通過(guò)實(shí)現(xiàn)一個(gè)靜態(tài)方法來(lái)擴(kuò)展類(lèi)

// Syntax: class Name { static fn() { ... } }
class Animal {
    constructor(family, specie, hue) {
        this.family = family
        this.specie = specie
        this.hue = hue
    }
    yell() {
        console.log(this.hue)
    }
    static extend(constructor, ..._args) {
      return class extends Animal {
        constructor{...args) {
        super(..._args)
        constructor.call(this, ...args)
          }
       }
    }
}

const Dog = Animal.extend(function(name) {
    this.name = name
}, 'Canidae', 'Canis lupus', 'Woug')

 const doge=new Dog('Doge')
 doge.yell(> //=> Woug
 console.log(doge.name) //=> Doge

高級(jí)技巧
在Object類(lèi)及其所有子類(lèi)(在ECMAScript中,除了null外里、undefined以外邑飒,一切類(lèi)型和類(lèi)都可以看做是Object的子類(lèi))的實(shí)例中,有一個(gè)利用Symbol.toStringTag作為鍵的屬性级乐,定義著當(dāng)這個(gè)對(duì)象的toString()方法被調(diào)用時(shí)疙咸,所返回的Tag的內(nèi)容是什么。這就意味著可以進(jìn)行一些自定義操作风科,通過(guò)[]語(yǔ)法和Getter特性為一個(gè)類(lèi)自定義toString標(biāo)簽撒轮。

class Foo{
     get [Symbol.toStringTag](){
         return 'Bar'
    }
}
const obj=new Foo()
console.log(obj.toString())   //=>[object  Bar]

注意事項(xiàng)
類(lèi)的繼承必須是單項(xiàng)的,不可能出現(xiàn)A類(lèi)繼承于B類(lèi)的同時(shí)B類(lèi)也繼承A類(lèi)的現(xiàn)象贼穆,這就意味著题山,父類(lèi)必須在子類(lèi)定義之前被定義。

生成器(Generator)

生成器的主要功能是:通過(guò)一段程序故痊,持續(xù)迭代或枚舉出符合某個(gè)公式或算法的有序數(shù)列中的元素顶瞳,這個(gè)程序便是用于實(shí)現(xiàn)這個(gè)公式或算法的,而不需要將目標(biāo)數(shù)列完整寫(xiě)出愕秫。
生成器是ES2015中同時(shí)包含語(yǔ)法和底層支持的一個(gè)新特性慨菱。

(1)基本概念
生成器函數(shù)
生成器函數(shù)是ES2015中生成器的最主要表現(xiàn)方式,它與普通函數(shù)的語(yǔ)法差別在于戴甩,在function語(yǔ)句之后和函數(shù)名之前符喝,有一個(gè)“*”作為生成器函數(shù)的標(biāo)示符。

function* fibo(){
//...
}

生成器函數(shù)并不是強(qiáng)制性使用聲明式進(jìn)行定義的甜孤,與普通函數(shù)—樣也可以使用表達(dá)式進(jìn)行定義协饲。

const fnName = function*() {/*...*/}

生成器函數(shù)的函數(shù)體內(nèi)容將會(huì)是所生成的生成器的執(zhí)行內(nèi)容,在這些內(nèi)容之中缴川,yield語(yǔ)句的引入使得生成器函數(shù)與普通函數(shù)有了區(qū)別茉稠。yield語(yǔ)句的作用與return語(yǔ)句冇些相似,但并非退出函數(shù)體把夸,而是切出當(dāng)前函數(shù)的運(yùn)行時(shí)(此處為一個(gè)類(lèi)協(xié)程而线,Semi-coroutine),與此同時(shí)可以將一個(gè)值(可以是任何類(lèi)型)帶到主線程中。

我們以一個(gè)比較形象的例子來(lái)做比喻,你可以把整個(gè)生成器運(yùn)行時(shí)看成一條長(zhǎng)長(zhǎng)的瑞士卷吞获,while (true)是無(wú)限長(zhǎng)的况凉,ECMAScript引擎每一次遇到y(tǒng)ield語(yǔ)句時(shí)谚鄙,就好比在瑞士卷上切一刀各拷,而切面所呈現(xiàn)的“紋路”則是yield語(yǔ)句所得的值。

生成器
從計(jì)算機(jī)科學(xué)角度上看闷营,生成器是—種類(lèi)協(xié)程或半?yún)f(xié)程(Semi-coroutine),它提供了一種可以通過(guò)特定語(yǔ)句或方法使其執(zhí)行對(duì)象(Execution)暫停的功能烤黍,而這語(yǔ)句一般都是yield語(yǔ)句。上面的斐波那契數(shù)列生成器便是通過(guò)yield語(yǔ)句將每一次的公式計(jì)算結(jié)果切出執(zhí)行對(duì)象傻盟,并帶到主線程上來(lái)的速蕊。

在ES2015中,yield語(yǔ)句可以將一個(gè)值帶出協(xié)程娘赴,向主線程也可以通過(guò)生成器對(duì)象的方法將一個(gè)值帶回生成器的執(zhí)行對(duì)象中去规哲。

const inputValue =yield outputValue

生成器切出執(zhí)行對(duì)象并帶出outputValue,主線程經(jīng)過(guò)同步或異步處理后,通過(guò).next (val)方法將inputValue帶回生成器的執(zhí)行對(duì)象中诽表。

(2)使用方法
構(gòu)建生成器函數(shù)
使用生成器的第一步自然是要構(gòu)建一個(gè)生成器函數(shù)唉锌,以生成相對(duì)應(yīng)的生成器對(duì)象。

啟動(dòng)生成器
生成器函數(shù)不能直接作為普通的函數(shù)來(lái)使用竿奏,因?yàn)樵谡{(diào)用時(shí)無(wú)法直接執(zhí)行其中的邏輯代碼袄简。執(zhí)行生成器函數(shù)會(huì)返回一個(gè)生成器對(duì)象,用于運(yùn)行生成器內(nèi)容和接受其中的值泛啸。

運(yùn)行生成器內(nèi)容
因?yàn)樯善鲗?duì)象自身也是一種可迭代對(duì)象绿语,所以直接使用for-of循環(huán)將其中輸出的值打印出來(lái)。

Promise

Promise意在讓異步代碼變得干凈和直觀候址,讓異步代碼變得井然有序吕粹。
Promise在設(shè)計(jì)上具有原子性,即只有三種狀態(tài):等待(Pending)岗仑、成功(Fulfilled)昂芜、失敗(Rejected)赔蒲。在調(diào)用支持Promise的異步方法時(shí)泌神,邏輯變得非常簡(jiǎn)單,在大規(guī)模的軟件工程開(kāi)發(fā)中具有良好的健壯性舞虱。

(1)基本語(yǔ)法
創(chuàng)建Promise對(duì)象:
要想給一個(gè)函數(shù)賦予Promise能力欢际,就要先創(chuàng)建一個(gè)Promise對(duì)象,并將其作為函數(shù)值返回矾兜。Promise對(duì)象要求傳入一個(gè)函數(shù)损趋,并帶有resolve和reject參數(shù)。這是兩個(gè)用于結(jié)束Promise等待的函數(shù)椅寺,對(duì)應(yīng)的狀態(tài)分別是成功和失敗浑槽。

//Syntax:
//new Promise(executor):Promise
//new Promise((resolve,reject)=>statements):Promise

function asyncMethod(...args){
    return new Promise((resolve,reject)=>{
        //...
    })
}

將新創(chuàng)建的Promise對(duì)象作為異步方法的返回值蒋失,所有的狀態(tài)就可以使用它所提供的方法進(jìn)行控制了。

進(jìn)行異步操作:
創(chuàng)建了 Promise對(duì)象后桐玻,就可以進(jìn)行異步操作篙挽,并通過(guò)resolve (value)和
reject (reason)方法來(lái)控制Promise的原子狀態(tài)。

  1. resolve(value)方法控制的是當(dāng)前Promise對(duì)象是否進(jìn)入成功狀態(tài)镊靴,一旦執(zhí)行該方法并傳入有且只有一個(gè)返回值铣卡,Promise便會(huì)從等待狀態(tài)(Pending)進(jìn)入成功狀態(tài)(Fulfilled),Promise也不會(huì)再接收任何狀態(tài)的變偏竟。
  2. reject (reason)方法控制的是當(dāng)前Promise對(duì)象是否進(jìn)入失敗階段煮落,與resolve方法相冋,一旦進(jìn)入失敗階段就無(wú)法再改變踊谋。
//Syntax:
//resolve(value)
//reject(reason)

new Promise((resolve,reject)=>{
    api.call('fetch-data',(err,data)=>{
        if(err) return reject(err)
        resolve(data)
    })
})

其中在Promise的首層函數(shù)作用域中一旦出現(xiàn)throw語(yǔ)句蝉仇,Promise對(duì)象便會(huì)直接進(jìn)入失敗狀態(tài),并以throw語(yǔ)句的拋出值作為錯(cuò)誤值進(jìn)行錯(cuò)誤處理殖蚕。

(new Promise(function() {
    throw new Error ('test')
)))
.catch(err =>console.error(err))

但是相對(duì)的return語(yǔ)句并不會(huì)使Promise對(duì)象進(jìn)入成功狀態(tài)轿衔,而會(huì)使Promise停留在等待狀態(tài)。所以在Promise對(duì)象的執(zhí)行器(executor)內(nèi)需要謹(jǐn)慎使用return語(yǔ)句來(lái)控制代碼流程嫌褪。

處理Promise的狀態(tài)
與resolve(value)和reject(reason)方法對(duì)應(yīng)的是呀枢,Promise對(duì)象有兩個(gè)用于處理Promise對(duì)象狀態(tài)變化的方法。


image.png

這兩個(gè)方法都會(huì)返回一個(gè)Promise對(duì)象笼痛,Promise對(duì)象的組合便會(huì)成為一個(gè)Promise對(duì)象鏈裙秋,呈流水線的模式作業(yè)。

//Syntax:promise.then(onFulfilled).catch(onRejected):Promise
 
 asyncMethod()
 .then((...args)=>args  /*...*/)
 .catch(err=>console.error(err))

Promise鏈?zhǔn)教幚砟J(rèn)被實(shí)現(xiàn)缨伊,即.then(onFulfilled)或.catch(onRejected)會(huì)處理在onFulfilled和onRejected中所返回或拋出的值摘刑。

  1. 如果onFulfilled或onRejected中所返回的值是一個(gè)Promise對(duì)象,則該P(yáng)romise對(duì)象會(huì)被加入到Promise的處理鏈中刻坊。

  2. 如果onFulfilled或onRejected中返回的值并不是一個(gè)Promise對(duì)象枷恕,則會(huì)返回一個(gè)己經(jīng)進(jìn)入成功狀態(tài)的Promise對(duì)象。

  3. 如果onFulfilled或onRejected中因?yàn)閠hrow語(yǔ)句而拋出一個(gè)錯(cuò)誤err谭胚,則會(huì)返回一個(gè)已經(jīng)進(jìn)入失敗狀態(tài)的Promise對(duì)象徐块。

之所以說(shuō)Promise對(duì)象鏈呈流水線的模式進(jìn)行作業(yè),是因?yàn)樵赑romise對(duì)象對(duì)自身的onFulfilled和onRejected響應(yīng)器的處理中灾而,會(huì)對(duì)其中返回的Promise對(duì)象進(jìn)行處理胡控。其內(nèi)部會(huì)將這個(gè)新的Promise對(duì)象加入到Promise對(duì)象鏈中,并將其暴露出來(lái)旁趟,使其繼續(xù)接受新的Promise對(duì)象的加入昼激。只有當(dāng)Promise對(duì)象鏈中的上一個(gè)Promise對(duì)象進(jìn)入成功或失畋階段,下一個(gè)Promise對(duì)象才會(huì)被激活,這就形成了流水線的作業(yè)模式橙困。

Promise對(duì)象鏈還有一個(gè)十分實(shí)用的特性--Promise對(duì)象的狀態(tài)是具有傳遞性的瞧掺。

如果Promise對(duì)象鏈中的某一環(huán)出現(xiàn)錯(cuò)誤,Premise對(duì)象鏈便會(huì)從出錯(cuò)的環(huán)節(jié)開(kāi)始凡傅,不斷向下傳遞辟狈,直到出現(xiàn)任何一環(huán)的Promise對(duì)象對(duì)錯(cuò)誤進(jìn)行響應(yīng)為止。

(2)高級(jí)使用方法
Promise.all(iterable)
該方法可以傳入一個(gè)可迭代對(duì)象(如數(shù)組)像捶,并返回一個(gè)Promise對(duì)象上陕,該P(yáng)romise對(duì)象會(huì)
在當(dāng)可迭代對(duì)象中的所冇Promise對(duì)象都進(jìn)入完成狀態(tài)(包括成功和失畋)后被激活桩砰。

1.如果可迭代對(duì)象中的所有Promise對(duì)象都進(jìn)入了成功狀態(tài)拓春,那么該方法返回的Promise
對(duì)象也會(huì)進(jìn)入成功狀態(tài),并以一個(gè)可迭代對(duì)象來(lái)承載其中的所有返回值亚隅。

2.如果可迭代對(duì)象中Promise對(duì)象的其中一個(gè)進(jìn)入了失敗狀態(tài)硼莽,那么該方法返回的Promise
對(duì)象也會(huì)進(jìn)入失敗狀態(tài),并以那個(gè)進(jìn)入失敗狀態(tài)的錯(cuò)誤信息作為自己的錯(cuò)誤信息煮纵。

//Syntax:Promise.all(iterable):Promise
const promises=[async(1),async(2),async(3),async(4)]

Promise.all(promises)
.then(values=>{
    //...
})
.catch(err=>console.error(err))

Promise.race(iterable)
Promise .race (iterable)方法同樣也接受一個(gè)包含若干個(gè)Promise對(duì)象的可迭代對(duì)象懂鸵,但不同的是這個(gè)方法會(huì)監(jiān)聽(tīng)所有的Promise對(duì)象,并等待其中的第一個(gè)進(jìn)入完成狀態(tài)的Promise對(duì)象行疏,一旦有第一個(gè)Promise對(duì)象進(jìn)入了完成狀態(tài)匆光,該方法返回的Promise對(duì)象便會(huì)根據(jù)這第一個(gè)完成的Promise對(duì)象的狀態(tài)而改變。

//Syntax:Promise.race(iterable):Promise
const promises=[async(1),async(2),async(3),async(4)]

Promise.race(promises)
.then(values=>{
    //...
})
.catch(err=>console.error(err))

代碼模塊化

ECMAScript包含了以往模塊加載庫(kù)的主要功能酿联,還添加了一些非常使用的設(shè)計(jì)终息,以提高ECMAScript的模塊化管理功能。

(1)引入模塊
ES Module中有很多種引入模塊的方法贞让,最基本的便是import語(yǔ)句周崭。

import name form 'module-name'
import * as name from 'module-name'
import {member} from 'module-name'
import {meber as alias} from 'module-name'
import 'module-name'

引入默認(rèn)模塊

//Syntax:import namespace from 'module-name'

import http from 'http'
import url from 'url'
import fs from 'fs'

引入模塊部分接口
ES2015中的 模塊化機(jī)制支持引入一個(gè)模塊的部分接口

//Syntax:import {meber1,meber2} from 'module-name'

import {isEmpty} from 'lodash'
import {EventEmitter} from 'events'

console.log(isEmpty({}))  //=>true

從模塊中局部引用的接口定義一個(gè)別名,以避免指代不明或接口重名的情況出現(xiàn)喳张。

//Syntax:import {meber as alias} from 'module-name'
import {createServer as createHTTPServer} from 'http'
import {createServer as createHTTPSServer} from 'https'

引入全部局部接口到指定命名空間
有的模塊不會(huì)定義默認(rèn)接口续镇,只是定義了若干個(gè)命名接口,將其中的所有接口定義到一個(gè)命名空間中销部,使用以下語(yǔ)法摸航。

//Syntax:import * as namespace from 'module-name'
import * as lib from 'module'
lib.method1()
lib.method2()

混入引入默認(rèn)接口和命名接口
同時(shí)引入默認(rèn)接口和其它命名接口,可以通過(guò)混合語(yǔ)句來(lái)實(shí)現(xiàn)舅桩。

//Syntax:import {default as  <default name>,method1} from 'module-name'
import {default as Client,utils} from 'module'

注意:引入的默認(rèn)接口必須要使用as語(yǔ)句被賦予一個(gè)別名酱虎,因?yàn)樵诔K引入語(yǔ)句以外的地方default是一個(gè)保留關(guān)鍵字,所以無(wú)法使用江咳。

import {default ,utils} from 'module'  //Wrong

簡(jiǎn)潔的語(yǔ)法

//Syntax:import  <default name>,{<named modules>} from 'module-name'
import Client,{utils} from 'module'
import Client,* as lib from 'module'

不引入接口逢净,僅運(yùn)行模塊代碼
在某些場(chǎng)景下,一些模塊并不需要向外暴露任何接口,只需要執(zhí)行內(nèi)容的代碼(如系統(tǒng)初始化)爹土。

//Syntax:import 'module-name'
import 'system-apply'

(2)定義模塊
ES Module中以文件名及其相對(duì)或絕對(duì)路徑作為該模塊被引用時(shí)的標(biāo)識(shí)甥雕。

(3)暴露模塊
暴露單一接口
如果需要定義一個(gè)項(xiàng)目?jī)?nèi)的工具集模塊,需要將其中定義的函數(shù)或者對(duì)象暴露到該文件所定義的模塊上胀茵。

//Syntax:export <statement>

//module.js
export const apiRoot='http://example.com/api'
export function method(){
    //...
}
export class foo{
    //...
}

//app.js
import {method,foo} from 'module.js'

export 語(yǔ)句后所跟著的語(yǔ)句需要具有生命部分和賦值部分
1.聲明部分(Statement)為export語(yǔ)句提供了所暴露接口的標(biāo)識(shí)社露;
2.賦值部分(Assignment)為export語(yǔ)句提供了接口的值。

那些不符合這兩個(gè)條件的語(yǔ)句無(wú)法被暴露在當(dāng)前文件所定義的模塊上琼娘,以下代碼被視為非法代碼峭弟。

//1
export 'foo'
//2
const foo='bar'
export foo
//3
export function(){}

暴露模塊默認(rèn)接口
在某些時(shí)候,一個(gè)模塊只需要暴露一個(gè)接口脱拼,比如需要使用模塊機(jī)制定義一個(gè)只含有一個(gè)單一工具類(lèi)的模塊時(shí)瞒瘸,就沒(méi)有必要讓這個(gè)工具類(lèi)成為該模塊的一部分,而是讓這個(gè)類(lèi)成為這個(gè)模塊熄浓。

//Syntax:export default <value>
//client.js
export default class Client{
   //...
}
//app.js
import Client from 'client.js'

混合使用暴露接口語(yǔ)句
開(kāi)發(fā)者可以為一個(gè)模塊同時(shí)定義默認(rèn)接口和其它命名接口情臭。

//module.js
export default class  Client{
    //...
}
export const foo='bar'

//app.js
import Client,{foo} from 'module'

暴露一個(gè)模塊的所有接口
在第三方類(lèi)庫(kù)的開(kāi)發(fā)中,不免需要將各種不同的功能塊分成若干個(gè)模塊來(lái)進(jìn)行開(kāi)發(fā)赌蔑,以便管理俯在。ES Module可以將import語(yǔ)句和export組合,直接將一個(gè)模塊的接口暴露到另外一個(gè)模塊上娃惯。

//Syntax:export * from  'other-module'
//module-1.js
export function foo(){/*....*/}
//module.js
export * from 'module-1'
//app.js
import {foo} from 'module'

暴露一個(gè)模塊的部分接口

//Syntax:export {member} from 'module-name'
export {member} from 'module'
export {default as ModuleDefault} from 'module'

暴露一個(gè)模塊的默認(rèn)接口
可以將一個(gè)模塊的默認(rèn)接口作為另一個(gè)模塊的默認(rèn)接口跷乐。

export {default} from  'module'

Symbol

Symbol的值具有互不等價(jià)的特性,開(kāi)發(fā)者同時(shí)可以為Symbol值添加一個(gè)描述趾浅。
(1)基本語(yǔ)法

  • 生成唯一的Symbol值
    執(zhí)行Symbol({description})函數(shù)可以生成一個(gè)與其它Symbol值互不等價(jià)的Symbol值愕提,其中Symbol()函數(shù)可以接受一個(gè)除Symbol值以外的值作為該Symbol值的描述,以便通過(guò)開(kāi)發(fā)者的辨認(rèn)判斷其為可選的潮孽。
//Syntax:Symbol([description]):Symbol

const symbol=Symbol()    //=>Symbol()
const symbolForSomething=Symbol('something')   //=>Symbol(something)
const symbolWithNumber=Symbol(3.14)    //=>Symbol(3.14)
const symbolWidthObject=Symbol({'foo':'bar'})  //=>Symbol([object Object])

//Don't use a symbol to be another symbol's description
const anotherSymbol=Symbol(symbol)  //=>TypeError:Cannot convert a Symbol value to a string

描述值僅僅是起到描述的作用揪荣,不會(huì)對(duì)Symbol值本身起到任何改變的作用。即便是兩個(gè)具有相同描述值的Symbol值也不具有等價(jià)性往史。

const symbol1=Symbol('footer')
const symbol2=Symbol('footer')
symbol1==symbol2   //=>false

注意:Symbol函數(shù)并不是一個(gè)構(gòu)造函數(shù)仗颈,不能使用new語(yǔ)句來(lái)生成Symbol“對(duì)象”,否則會(huì)拋出TypeError錯(cuò)誤椎例。

new Symbol()   //=>TypeError:Symbol is not  a constructor

由此可知挨决,Symbol是一種值類(lèi)型而非引用類(lèi)型。這就意味著如果將Symbol值作為函數(shù)形參進(jìn)行傳遞订歪,將會(huì)進(jìn)行復(fù)制值傳遞而非引用傳遞务热,這跟其它值類(lèi)型(字符串级及,數(shù)字等)的行為是一致的掌实。

const symbol=Symbol('hello')
function fn1(_symbol){
   return _symbol==symbol
}
console.log(fn1(symbol))   //=>true
function fn2(_symbol){
   _symbol=null
  console.log(_symbol)
}
fn2(symbol)  //=>null 

如果希望得到一個(gè)Symbol“對(duì)象”矢否,可以使用Object()函數(shù)實(shí)現(xiàn)慎陵。

const symbol=Symbol('foo')
typeof symbol   //=>symbol
const symbolObj=Object(symbol)
typeof symbolObj   //=>object
  • 注冊(cè)全局可重用Symbol
    ES2015標(biāo)準(zhǔn)除了提供具有唯一性的Symbol值以外,同樣還允許開(kāi)發(fā)者在當(dāng)前運(yùn)行時(shí)中定義一些全局有效性的Symbol喻奥。開(kāi)發(fā)者可以通過(guò)一個(gè)key向當(dāng)前運(yùn)行時(shí)注冊(cè)一個(gè)需要在其他程序中使用的Symbol席纽。
//Syntax:Symbol.for([key]):Symbol
const symbol=Symbol.for('footer')

Symbol. for ()與Symbol ()的區(qū)別是,Symbol . for ()會(huì)根據(jù)傳入的key在全局作用域中注冊(cè)一個(gè)Symbol值撞蚕,如果某一個(gè)key從未被注冊(cè)到全局作用域中润梯,便會(huì)創(chuàng)建一個(gè)Symbol值并根據(jù)key注冊(cè)到全局環(huán)境中。如果該key己被注冊(cè)甥厦,就會(huì)返冋一個(gè)與第一次使用所創(chuàng)建的Symbol值等價(jià)的Symbol值纺铭。

const symbol = Symbol.for('foo')
const obj ={}
obj[symbol] = 'bar'

const anotherSymbol = Symbol.for('foo')

console.log(symbol === anotherSymbol) //=> true
console.log (obj [anotherSymbol])    //=> jbar

這在大型系統(tǒng)的開(kāi)發(fā)中可以用于一些全局的配罝數(shù)據(jù)中或者用于需要多處使用的數(shù)據(jù)中。

  • 獲取全局Symbol的key
    既然可以通過(guò)字符串的key在全局環(huán)境中注冊(cè)一個(gè)全局Symbol刀疙,那么同樣也可以根據(jù)這些全局的Symbol獲取到它們所對(duì)應(yīng)的key舶赔。
//Syntax:Symbol kefFor(<global symbol>):String
const  symbol=Symbol.for('foobar')
console.log(Symbol.keyFor(symbol))   //=>foobar

(2)常用Symbol值
ES2015標(biāo)準(zhǔn)定義了一些內(nèi)置的常用Symbol值,這些Symbol值的應(yīng)用深入到了 ECMAScript引擎運(yùn)行中的各個(gè)角落庙洼。開(kāi)發(fā)者可以運(yùn)用這些常用Symbol值對(duì)代碼的內(nèi)部運(yùn)行邏輯進(jìn)行修改或拓展顿痪,以實(shí)現(xiàn)更高級(jí)的需求镊辕。


image.png

(3)Symbol.iterator
在ES2015標(biāo)準(zhǔn)中定義了可迭代對(duì)象(Iterable Object)和新的for-of循環(huán)語(yǔ)句油够,其中可迭代對(duì)象并不是一種類(lèi)型,而是帶有@@iterator屬性和可以被for-of循環(huán)語(yǔ)句所遍歷的對(duì)象的統(tǒng)稱(chēng)征懈。

for-of循環(huán)語(yǔ)句與可迭代對(duì)象
for-of循環(huán)語(yǔ)句是ES2015中新增的循環(huán)語(yǔ)句石咬,它可以對(duì)所有可迭代對(duì)象進(jìn)行遍歷,而不僅僅是數(shù)組卖哎。在ES2015中鬼悠,默認(rèn)的可迭代對(duì)象有:數(shù)組(Array)、字符串(String)亏娜、類(lèi)型數(shù)組(TypedArray)焕窝、映射對(duì)象(Map)、集合對(duì)象(Set)和生成器實(shí)例(Generator)维贺。

// Array
for (const el of [ 1, 2, 3 ]) console.log(el)
// String
for (const word of 'Hello World') console.log(word)
// TypedArray
for (const value of new Uint8Array([ 0x00, Oxff J)) console.log(value)
//Map
for (const entry of new Map ([ [' a', 1],   [ 'b', 2]])   console.log (entry)
//Set
for (const el of new Set([ 1, 2, 3, 3, 3 ]))    console.log (el)
// Generator
function* fn() { yield 1 }
for (const value of fn ()) console.log(value)

(4)Symbol.hasInstance
Symbol.haslnstance為開(kāi)發(fā)者提供了可以用于擴(kuò)展instanceof語(yǔ)句內(nèi)部邏輯的權(quán)限它掂,開(kāi)發(fā)者可以將其作為屬性
鍵,用于為一個(gè)類(lèi)定義靜態(tài)方法溯泣,該方法的第一個(gè)形參便是被檢測(cè)的對(duì)象虐秋,而該方法的返回值便是決定了當(dāng)次instanceof語(yǔ)句的返回結(jié)果。

class Foo (
   static [Symbol.haslnstance](obj) {
   console.log(obj)      //=>{}
   return true
  }
}
console.log({} instanceof Foo)    //=>true

(5)Symbol.match
Symbol.match是正則表達(dá)式(或者對(duì)象)在作為字符串使用match ()方法時(shí)垃沦,內(nèi)部運(yùn)行邏輯的自定義邏輯入口客给。開(kāi)發(fā)者可以通過(guò)Symbol.match來(lái)自行實(shí)現(xiàn)match ()方法的運(yùn)行邏輯,比如利用strcmp (在ECMAScript中為String.prototype.localeCompare())來(lái)實(shí)現(xiàn)肢簿。

const  re = /foo/
re[Symbol.match]=function(str){
      const regexp=this
      console.log(str)   //=>bar
     //...
     return true
}
'bar'.match(re)   //=>true 

(6)Symbol.toPrimitive
Symbol.toPrimitive為開(kāi)發(fā)者提供了更高級(jí)的控制權(quán)力靶剑,使得引用類(lèi)型的對(duì)象在轉(zhuǎn)換為值類(lèi)型時(shí)可以進(jìn)行自定義處理蜻拨,無(wú)論是轉(zhuǎn)換為字符串還是數(shù)字。

開(kāi)發(fā)者可以使用Symbol.toPrimitive作為屬性鍵為對(duì)象定義一個(gè)方法桩引,這個(gè)方法接受一個(gè)參數(shù)官觅,這個(gè)參數(shù)用于判斷當(dāng)前隱式轉(zhuǎn)換的目標(biāo)類(lèi)型。


image.png

需要注意的是阐污,這里的default并不是因?yàn)槟繕?biāo)類(lèi)型無(wú)法被轉(zhuǎn)換休涤,而是因?yàn)檎Z(yǔ)法上容易造成混亂。

(7)Symbol.toStringTag
常用Symbol的值在前面己經(jīng)提到過(guò)笛辟,它的作用是可以決定這個(gè)類(lèi)的實(shí)例在調(diào)用toString()方法時(shí)的標(biāo)簽內(nèi)容功氨。

在Object類(lèi)及其所有的子類(lèi)的實(shí)例中,有一個(gè)利用Symbol .toStringTag作為鍵的屬性手幢,該屬性定義著當(dāng)這個(gè)對(duì)象的toString()方法被調(diào)用時(shí)捷凄,所返回的Tag的內(nèi)容是什么。

比如在開(kāi)發(fā)者定義的類(lèi)中围来,就可以通過(guò)Symbol. toStringTag來(lái)修改toString()屮的標(biāo)簽內(nèi)容跺涤,利用它作為屬性鍵為類(lèi)型定義一個(gè)Getter。

class Bar {}
class Foo{
    get  [Symbol.toStringTagl() { return  'Bar'}
}

const obj =new  Foo()
console.log(obj .toString() )   //=> [object Bar]



電子書(shū)鏈接: 《實(shí)戰(zhàn)ES2015:深入現(xiàn)代JavaScript+應(yīng)用開(kāi)發(fā)》 密碼: uetw

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末监透,一起剝皮案震驚了整個(gè)濱河市桶错,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌胀蛮,老刑警劉巖院刁,帶你破解...
    沈念sama閱讀 219,589評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異粪狼,居然都是意外死亡退腥,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,615評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門(mén)再榄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)狡刘,“玉大人,你說(shuō)我怎么就攤上這事困鸥⌒崾撸” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,933評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵窝革,是天一觀的道長(zhǎng)购城。 經(jīng)常有香客問(wèn)我,道長(zhǎng)虐译,這世上最難降的妖魔是什么瘪板? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,976評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮漆诽,結(jié)果婚禮上侮攀,老公的妹妹穿的比我還像新娘锣枝。我一直安慰自己,他們只是感情好兰英,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,999評(píng)論 6 393
  • 文/花漫 我一把揭開(kāi)白布撇叁。 她就那樣靜靜地躺著,像睡著了一般畦贸。 火紅的嫁衣襯著肌膚如雪陨闹。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,775評(píng)論 1 307
  • 那天薄坏,我揣著相機(jī)與錄音趋厉,去河邊找鬼。 笑死胶坠,一個(gè)胖子當(dāng)著我的面吹牛君账,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播沈善,決...
    沈念sama閱讀 40,474評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼乡数,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了闻牡?” 一聲冷哼從身側(cè)響起净赴,我...
    開(kāi)封第一講書(shū)人閱讀 39,359評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎澈侠,沒(méi)想到半個(gè)月后劫侧,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,854評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡哨啃,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,007評(píng)論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了写妥。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片拳球。...
    茶點(diǎn)故事閱讀 40,146評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖珍特,靈堂內(nèi)的尸體忽然破棺而出祝峻,到底是詐尸還是另有隱情,我是刑警寧澤扎筒,帶...
    沈念sama閱讀 35,826評(píng)論 5 346
  • 正文 年R本政府宣布莱找,位于F島的核電站,受9級(jí)特大地震影響嗜桌,放射性物質(zhì)發(fā)生泄漏奥溺。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,484評(píng)論 3 331
  • 文/蒙蒙 一骨宠、第九天 我趴在偏房一處隱蔽的房頂上張望浮定。 院中可真熱鬧相满,春花似錦、人聲如沸桦卒。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,029評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)方灾。三九已至建蹄,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間裕偿,已是汗流浹背躲撰。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,153評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留击费,地道東北人拢蛋。 一個(gè)月前我還...
    沈念sama閱讀 48,420評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像蔫巩,于是被迫代替她去往敵國(guó)和親谆棱。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,107評(píng)論 2 356

推薦閱讀更多精彩內(nèi)容