ES2015簡介和基本語法


 

ECMAScript 6(以下簡稱ES6)是JavaScript語言的下一代標准。因為當前版本的ES6是在2015年發布的,所以又稱ECMAScript 2015。也就是說,ES6就是ES2015。

說明:此文章根據《實戰ES2015:深入現代JavaScript+應用開發》這本書做的筆記,更多詳細內容請查看書籍。電子版在文章底部。

一、ECMAScript的發展歷程

 
image.png

二、ES2015能為實際開發帶來什么

ECMAScript的發展速度在不斷加快,影響范圍越來越大,除了Web前端開發以外,借助着Node.js的力量在服務器、桌面端甚至硬件設備等領域中也發光發熱着。

ES2015概述:

ES2015標注提供了許多新的語法和編程特性以提高ECMAScript的開發效率並優化ECMAScript的開發體驗。
ES2015的別名:Harmony(和諧)

語法糖:

ECMAScript帶來了可用性非常高的語法糖,這些語法糖的開發初衷是方便開發者使用,使用語法糖能夠增加程序的可讀性,從而減少程序代碼出錯的幾率。
如ES2015中非常重要的箭頭函數,大大地增強了ECMAScript在復雜業務邏輯中的處理能力。

使用ES2015前:

el.on('click',function(evt) { var self = this; fecch('/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)) }) 

模塊化和組件化:

在程序代碼可以通過模塊化進行解耦后,組件化開發便能借此進一步推進項目程序的工程化進度。組件化開發是模塊化開發的高級體現,組件化更能表現出模塊化開發的意義和重要性。
組件化開發所重視的是組件之間的非耦合關系和組件的可重用性,組件之間也可以存在依賴性,可以利用模塊化來實現組件化開發。同一類內容塊可以抽象化為一個組件,並在生產中重復使用。

const和let:

const為ECMAScript帶來了定義常量的能力,let為ECMAScript修復了從前var因為代碼習慣不佳而導致的代碼作用域混亂等問題,同時實現了塊狀作用域。
const可以實現變量名與內存地址的強綁定,讓變量不會因為除了定義語句和刪除語句以外的代碼而丟失內存地址的綁定,從而保證了變量與內存之間的安全性。

總結:語法糖、模塊化、組件化等工程優勢,可以在實際開發中提升開發效率和代碼質量。

三、ES2015新語法

新語法:

  • let、const和塊級作用域
  • 箭頭函數(Arrow Function)
  • 模板字符串(Template String)
  • 對象字面量擴展語法(Enhanced Object Literals)
  • 表達式結構(Destructuring)
  • 函數參數表達、傳參
  • 新的數據結構
  • 類語法(Classes)
  • 生成器(Generator)
  • Promise
  • 代碼模塊化
  • Symbol
  • Proxy

let、const和作用域

let和const是繼var之后新的變量定義方法,與let相比,const更容易被理解。const就是constant的縮寫,用於定義變量,即不可變量。const定義常量的原理是阻隔變量名所對應的內存地址被改變。
變量與內存之間的關系由三個部分組成:變量名、內存綁定和內存(內存地址)。


 
image.png

ECMAScript在對變量的引用進行讀取時,會從該變量對應的內存地址所指向的內存空間中讀取內容。當用戶改變變量的值時,引擎會重新從內存中分配一個新的內存空間以存儲新的值,並將新的內存地址與變量進行綁定。const的原理便是在變量名與內存地址之間建立不可變得綁定,當后面的程序嘗試申請新的內存空間時,引擎便會拋出錯誤。

在ES2015中,let可以說是var的進化版本,var大部分情況下可以被let替代,let和var的異同點如下表:

 
image.png

變量的生命周期:

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

從工程化的角度,應該在ES2015中遵從以下三條原則:

(1)一般情況下,使用const來定義值的存儲容器(常量);
(2)只有在值容器明確地被確定將會被改變時才使用let來定義(變量);
(3)不再使用var。

循環語句:

ECMAScript引入了一種新的循環語句for...of,主要的用途是代替for...in循環語句;為Array對象引入了Array.forEach方法以代替for循環,Array.forEach方法的特點是自帶閉包,以解決因為缺乏塊級作用域導致需要使用取巧的方法來解決var的作用域問題。
因為塊級作用域的存在,使得for循環中的每一個當前值可以僅保留在所對應的循環體中,配合for-of循環語句更是免去了回調函數的使用。

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

配合ES2015中的解構(Destructuring)特性,在處理JSON數據時,更加得心應手。

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方法需要傳入一個回調函數來接收循環的每一個循環元素並作為循環體以執行。同時,這個回調函數在標准定義中會被傳入三個參數,分別為:當前值,當前值的下標和循環數組自身。在ES2015標准中,數組類型再次被賦予了一個名為entries的方法,它可以返回對應的數組中每一個元素與其下標配對的一個新數組。

這個新特性可以與解構和for-of循環配合使用。

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 

箭頭函數

箭頭函數,顧名思義便是使用箭頭(=>)進行定義的函數,屬於匿名函數(Anonymous Function)一類。
相對於傳統的function語句,箭頭函數在簡單函數使用中更為簡潔直觀。

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

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

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

這是箭頭函數最簡潔的形式,常見於用作簡單的處理函數,如過濾。

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

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

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

多參數的語法跟普通函數一樣,以括號來包裹參數列,這種形式常見於數組的處理,如排序。

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

(3)多行箭頭函數

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

(4)無參數箭頭函數
如果一個箭頭函數無參數傳入,需要用一對空的括號來表示空的參數列表。

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

模板字符串

當我們使用普通的字符串時,會使用單引號或雙引號來包裹字符串的內容,在ES2015的模板字符串中使用反勾號`。

//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; ` 

多行字符串無法像普通字符串使用雙引號嵌套單引號來表達字符串中的字符串,可以使用反斜杠將需要顯示的反勾號轉義為普通的字符。添加了\`用於打印`。

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\`` 

對象字面量擴展語法

在ES2015之前的ECMAScript的標准中,對象字面量只是一種用於表達對象的語法,只具有表達的功能,並不起到更大的作用。在ES2015中,為ECMASCript開發者開放了更多關於對象的操作權限,其中便有更多的對象字面量語法。

(1)函數類屬性的省略語法:
ES2015中引入了類機制(Class),普通的對象字面量也吸收了一些語法糖,可以讓方法屬性省略function,以一種直觀的語法來表達。

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

有了這個語法糖,對象字面量中的方法類屬性更像是一個方法,而不只是一個以函數為值得屬性。

(2)支持_proto_注入:
在ES2015中開放了向對象字面量注入_proto_的功能,這樣做的意義在於開發者可以得到更高的操作權限,從而更加靈活地創建和操作對象。
在ES2015標准中,開發者允許直接向一個對象字面量注入_proto_,使其直接成為指定類的一個實例,無須另外創建一個類來實現繼承。

//Syntax:{_proto_:...} import {EventEmitter} from 'events' const machine={ _proto_:new EventEmitter(), method(){...} } console.log(machine) //=>EventEmitter{} console.log(machine instanceof EventEmitter) //=>true 

(3)可動態計算的屬性名:
在ES2015標准對於對象字面量的處理中,引入了一個新語法,這個語法允許我們直接使用一個表達式來表達一個屬性名。

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

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

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

表達式結構

在ES2015之前工程師們一般使用對象字面量和數組來模擬函數多返回值,在ES2015中同樣可以使用類似的語法來實現函數多返回值,且語法上更加簡潔。

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

//Syntax:{arg1,arg2}={arg1:value1,arg2:value2} function getState(){ return { error:null, logined:true, user:{}, } } const {error,logined,user}=getState() 

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

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

跳過數組中某些元素,通過空開一個元素的方式來實現。

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

不定項的獲取后續元素,用...語句實現。

//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)使用場景

  • Promise與模式匹配
    注意:如果在Promise.then方法中傳入的是一個帶有解構參數的箭頭函數時,解構參數外必須要有一個括號包裹,否則會拋出語法錯誤。
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 //... }) 

如果參數過多但在某些場景下並不需要全部參數,或者文檔約定不完善的情況下,使用對象作為傳遞載體更佳。

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表示定義一個函數或一種語法來交換兩個變量的值。在ES2015中,可以使用模式匹配來實現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)高級用法

  • 解構別名
    如果不想使用其中的屬性名作為新的變量名(或常量名),可以使用別名獲得相應的返回值,只要在原來的返回值后面加上“:x”,其中x就是希望使用的變量名。
function fetchData(){ return{ response:['foo','bar'] } } const{response:data}=fetchData() console.log(data) //=>foo bar 
  • 無法匹配的缺省值
    如果在模式匹配中,存在無法匹配的缺省值(載體對象不存在相應的值或目標參數所對應下標超出了載體數組的下標范圍),默認情況下會返回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,可以為參數賦予一個默認值,當無法匹配到相應的值時,會使用該默認值。

const {foo=1}={bar:1} console.log(foo) //=>1 const [a,b=2]=[1] console.log(a,b) //=>1 2 
  • 深層匹配
    通過嵌套解構表達式來獲取深層的內容,可以在對象中嵌套數組來獲取對象中數組的某元素,反之亦然。
 //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 

函數參數表達、傳參

(1)默認參數值
使用語法:
ES2015中使用語法直接實現默認參數值語法顯得更加簡潔而直觀。

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

使用場景:
同時提供回調函數和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)) 

函數的默認參數特性用在某一個對象的方法中,所指定的默認參數還可以被定為該對象的某一個屬性

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

(2)剩余參數
使用語法
ES2015中對剩余參數有了更為優雅和標准的語法,直接將需要獲取的參數列表轉換為一個正常數組,以便使用。

//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 

使用場景
十分常用的merge和mixin函數(合並對象)就會需要使用到剩余函數這個特性來實線。

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} 

注意事項
注意:一旦一個函數的參數列表中使用了剩余參數的語法糖,便不可以再添加任何參數,否則會拋出錯誤。

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

arguments與剩余函數
雖然從語言角度看,arguments和...args是可以同時使用的,但有一種情況除外,arguments在箭頭函數中,會跟隨上下文綁定到上層,所以在不確定上下文綁定結果的情況下,盡可能不要在箭頭函數中使用arguments,而要使用..args。
(3)解構傳參
ES2015中的解構傳參是使用數組作為傳入參數以控制函數的調用情況,不同的是解構傳參不會替換函數調用中的上下文。
與剩余參數一樣,解構傳參使用...作為語法糖標識符。

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

新的數據解構

在ECMAScript中定義了以下幾種基本的數據結構,分為值類型(Primitive Types)和引用類型(Reference
Types)。

值類型數據結構:

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

引用類型數據結構:

  • Object 對象
  • Array 數組
  • RegExp(Regular Expression with pattern)正則表達式
  • Date 日期
  • Error 錯誤

(1)Set有序集合
ECMAScript中,Array表示一系列元素的有序集合,其中每一個元素都會帶有自身處在這個集合內的位置並以自然數作為標記,即帶有下標。無序集合可以把它當成沒有排序概念的數組,並且元素不可重復。

使用語法
在ES2015中,集合與數組不一樣的是,集合無法像數組那樣使用[]語法來直接生成,而需要用新建對象的方法來創建一個新的集合對象。

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

可以使用一個現成的數組作為集合對象的初始元素

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

集合對象的操作方法


 
image.png

增刪元素
可以通過add、delete和clear方法來添加,刪除,清空集合內的元素。

const set =new Set() //添加元素 set.add(1) .add(2) .add(3) .add(3) //這一句不會起到任何作用,因為元素3已存在於集合內 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 

遍歷元素
集合對象自身定義了forEach方法,跟數組類型中的forEach一樣,傳入一個回調函數以接受集合內的元素,並且可以為這個回調函數指定一個上下文。

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的引入,數組等類型有了一個新屬性Symbol.iterator(迭代子),這些類型的新名稱--可迭代對象(Iterable Object),其中包括數組類型、字符串類型、集合類型、字典類型(Map)、生成器類型(Generator),for-of循環語句可以對可迭代對象進行迭代,配合const或let使用,從而解決forEach方法不可中斷的問題。

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

(2)WeakSet
WeakSet最大的應用意義在於,可以直接對引擎中垃圾收集器的運行情況有程序化的探知方式,開發者可以利用WeakSet的特性以更高的定制化方案來優化程序的內存使用方案。

WeakSet與Set的區別:
a.WeakSet不能包含值類型元素,否則會拋出一個TypeError;
b.WeakSet不能包含無引用的對象,否則會自動清除出集合;
c.WeakSet無法被探知其大小,也無法被探知其中所包含的元素。

(3)Map映射類型
映射類型在計算機科學中的定義屬於關聯數組(Associative Array),關聯數組的定義為若干個鍵值對(Key/Value Pair)組成的集合,其中每一個鍵都只能出現一次。

使用語法
映射類型需要創建一個相應的實例來使用。

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

在創建映射對象時,可以將一個以二元數組(鍵值對)作為元素的數組傳入到構建函數中,其中每一個鍵值對都會加入到該映射對象中。該數組內的元素會以數組順序進行處理,如果存在相同的鍵,則會按照FIFO(First In First Out,先進先出)原則,以該鍵最后一個處理的對應值為最終值。

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

與對象字面量一樣,映射對象可以對其中的鍵值對進行添加、檢查、獲取、刪除等操作。
當然,作為新特性的映射對象也擁有一些Object沒有的方法。


 
image.png

增刪鍵值對
與集合對象類似,可以通過set、delete和clear方法對映射對象內的鍵值對進行操作。

const map=new Map() // 添加鍵值對 map.set('foo','hello') map.set('bar','es2015') map.set('bar','world') //=>將覆蓋之前加入的值 //刪除指定的鍵值對 map.delete('foo') //清空映射對象 map.clear() 

獲取鍵值對
映射對象由鍵值對組成,所以可以利用鍵來獲取相應的值。

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

檢查鍵值對
映射對象可以通過has (key)方法來檢査其中是否包含某一個鍵值對

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

遍歷鍵值對
映射對象是關聯數組的一種實現,所以映射對象在設計上同樣是一種可迭代對象,可以通過for-of循環語句對其中的鍵值對進行歷遍。也可以使用己實現在映射對象中的forEach方法來進行歷遍。

映射對象帶有entries ()方法,這個與集合對象中的entries()類似,用於返回一個包
含所有鍵值對的可迭代對象,而for-of循環語句和forEach便是先利用entries ()方法先
將映射對象轉換為一個類數組對象,然后再進行迭代。

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的鍵會檢查變量引用,只要其中任意一個引用被解除,該值對就會被刪除。

//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 

類語法

ES2015中的類語法與其他C語言家族成員的類語法有許多相同之處,如果開發者有在
JavaScript中使用過基於原型的類機制,那么也可以很容易接受ES2015的語法。

基本定義語法

// 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 

這里需要注意的是,在類中定義的方法,都是帶有作用域的普通函數,而不是箭頭函數,方法內第一層所引用的this都指向當前實例,如果實例方法內包含箭頭函數,則引擎就會根據包含層級把箭頭函數內引用的this所指向的實際對象一直向上層搜索,直到到達一個函數作用域或塊級作用域為止。如果一直搜索到達了運行環境的最上層,就會被指向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) 

繼承語法

//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的繼承語法可以將以前使用構建函數模擬的類作為父類來繼承,並非只由class語法定義的類才可以使用。

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 

需要注意的是,如果一個子類繼承了父類,那么在子類的constructor構造函數中必須使用super函數調用父類的構造函數后才能在子類的constructor構造函數中使用this,否則會報出this is defined的錯誤。

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

這個問題在除constructor構造函數以外的方法中並不會出現,即便在子類的構造
函數中並沒有調用super函數,在其他方法中依然可以調用this來指向當前實例。

Getter/Setter
Getter/Setter是一種元編程(Meta-programming)的概念,元編程的特點在於,允許程序可以對運行時(Runtime)的對象進行讀取和操作,從而使程序可以脫離代碼從字面上為程序定義的一些限制,有了對對象的更高操作權限。

 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的類機制同樣支持Getter/Setter在類中的使用,配合元編程的概念,類的能力會變得更加強大。

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 

靜態方法
可以通過實現一個靜態方法來擴展類

// 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 

高級技巧
在Object類及其所有子類(在ECMAScript中,除了null、undefined以外,一切類型和類都可以看做是Object的子類)的實例中,有一個利用Symbol.toStringTag作為鍵的屬性,定義着當這個對象的toString()方法被調用時,所返回的Tag的內容是什么。這就意味着可以進行一些自定義操作,通過[]語法和Getter特性為一個類自定義toString標簽。

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

注意事項
類的繼承必須是單項的,不可能出現A類繼承於B類的同時B類也繼承A類的現象,這就意味着,父類必須在子類定義之前被定義。

生成器(Generator)

生成器的主要功能是:通過一段程序,持續迭代或枚舉出符合某個公式或算法的有序數列中的元素,這個程序便是用於實現這個公式或算法的,而不需要將目標數列完整寫出。
生成器是ES2015中同時包含語法和底層支持的一個新特性。

(1)基本概念
生成器函數
生成器函數是ES2015中生成器的最主要表現方式,它與普通函數的語法差別在於,在function語句之后和函數名之前,有一個“*”作為生成器函數的標示符。

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

生成器函數並不是強制性使用聲明式進行定義的,與普通函數—樣也可以使用表達式進行定義。

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

生成器函數的函數體內容將會是所生成的生成器的執行內容,在這些內容之中,yield語句的引入使得生成器函數與普通函數有了區別。yield語句的作用與return語句冇些相似,但並非退出函數體,而是切出當前函數的運行時(此處為一個類協程,Semi-coroutine),與此同時可以將一個值(可以是任何類型)帶到主線程中。

我們以一個比較形象的例子來做比喻,你可以把整個生成器運行時看成一條長長的瑞士卷,while (true)是無限長的,ECMAScript引擎每一次遇到yield語句時,就好比在瑞士卷上切一刀,而切面所呈現的“紋路”則是yield語句所得的值。

生成器
從計算機科學角度上看,生成器是—種類協程或半協程(Semi-coroutine),它提供了一種可以通過特定語句或方法使其執行對象(Execution)暫停的功能,而這語句一般都是yield語句。上面的斐波那契數列生成器便是通過yield語句將每一次的公式計算結果切出執行對象,並帶到主線程上來的。

在ES2015中,yield語句可以將一個值帶出協程,向主線程也可以通過生成器對象的方法將一個值帶回生成器的執行對象中去。

const inputValue =yield outputValue 

生成器切出執行對象並帶出outputValue,主線程經過同步或異步處理后,通過.next (val)方法將inputValue帶回生成器的執行對象中。

(2)使用方法
構建生成器函數
使用生成器的第一步自然是要構建一個生成器函數,以生成相對應的生成器對象。

啟動生成器
生成器函數不能直接作為普通的函數來使用,因為在調用時無法直接執行其中的邏輯代碼。執行生成器函數會返回一個生成器對象,用於運行生成器內容和接受其中的值。

運行生成器內容
因為生成器對象自身也是一種可迭代對象,所以直接使用for-of循環將其中輸出的值打印出來。

Promise

Promise意在讓異步代碼變得干凈和直觀,讓異步代碼變得井然有序。
Promise在設計上具有原子性,即只有三種狀態:等待(Pending)、成功(Fulfilled)、失敗(Rejected)。在調用支持Promise的異步方法時,邏輯變得非常簡單,在大規模的軟件工程開發中具有良好的健壯性。

(1)基本語法
創建Promise對象:
要想給一個函數賦予Promise能力,就要先創建一個Promise對象,並將其作為函數值返回。Promise對象要求傳入一個函數,並帶有resolve和reject參數。這是兩個用於結束Promise等待的函數,對應的狀態分別是成功和失敗。

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

將新創建的Promise對象作為異步方法的返回值,所有的狀態就可以使用它所提供的方法進行控制了。

進行異步操作:
創建了 Promise對象后,就可以進行異步操作,並通過resolve (value)和
reject (reason)方法來控制Promise的原子狀態。

  1. resolve(value)方法控制的是當前Promise對象是否進入成功狀態,一旦執行該方法並傳入有且只有一個返回值,Promise便會從等待狀態(Pending)進入成功狀態(Fulfilled),Promise也不會再接收任何狀態的變。
  2. reject (reason)方法控制的是當前Promise對象是否進入失敗階段,與resolve方法相冋,一旦進入失敗階段就無法再改變。
//Syntax: //resolve(value) //reject(reason) new Promise((resolve,reject)=>{ api.call('fetch-data',(err,data)=>{ if(err) return reject(err) resolve(data) }) }) 

其中在Promise的首層函數作用域中一旦出現throw語句,Promise對象便會直接進入失敗狀態,並以throw語句的拋出值作為錯誤值進行錯誤處理。

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

但是相對的return語句並不會使Promise對象進入成功狀態,而會使Promise停留在等待狀態。所以在Promise對象的執行器(executor)內需要謹慎使用return語句來控制代碼流程。

處理Promise的狀態
與resolve(value)和reject(reason)方法對應的是,Promise對象有兩個用於處理Promise對象狀態變化的方法。


 
image.png

這兩個方法都會返回一個Promise對象,Promise對象的組合便會成為一個Promise對象鏈,呈流水線的模式作業。

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

Promise鏈式處理默認被實現,即.then(onFulfilled)或.catch(onRejected)會處理在onFulfilled和onRejected中所返回或拋出的值。

  1. 如果onFulfilled或onRejected中所返回的值是一個Promise對象,則該Promise對象會被加入到Promise的處理鏈中。

  2. 如果onFulfilled或onRejected中返回的值並不是一個Promise對象,則會返回一個己經進入成功狀態的Promise對象。

  3. 如果onFulfilled或onRejected中因為throw語句而拋出一個錯誤err,則會返回一個已經進入失敗狀態的Promise對象。

之所以說Promise對象鏈呈流水線的模式進行作業,是因為在Promise對象對自身的onFulfilled和onRejected響應器的處理中,會對其中返回的Promise對象進行處理。其內部會將這個新的Promise對象加入到Promise對象鏈中,並將其暴露出來,使其繼續接受新的Promise對象的加入。只有當Promise對象鏈中的上一個Promise對象進入成功或失畋階段,下一個Promise對象才會被激活,這就形成了流水線的作業模式。

Promise對象鏈還有一個十分實用的特性--Promise對象的狀態是具有傳遞性的。

如果Promise對象鏈中的某一環出現錯誤,Premise對象鏈便會從出錯的環節開始,不斷向下傳遞,直到出現任何一環的Promise對象對錯誤進行響應為止。

(2)高級使用方法
Promise.all(iterable)
該方法可以傳入一個可迭代對象(如數組),並返回一個Promise對象,該Promise對象會
在當可迭代對象中的所冇Promise對象都進入完成狀態(包括成功和失畋)后被激活。

1.如果可迭代對象中的所有Promise對象都進入了成功狀態,那么該方法返回的Promise
對象也會進入成功狀態,並以一個可迭代對象來承載其中的所有返回值。

2.如果可迭代對象中Promise對象的其中一個進入了失敗狀態,那么該方法返回的Promise
對象也會進入失敗狀態,並以那個進入失敗狀態的錯誤信息作為自己的錯誤信息。

//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)方法同樣也接受一個包含若干個Promise對象的可迭代對象,但不同的是這個方法會監聽所有的Promise對象,並等待其中的第一個進入完成狀態的Promise對象,一旦有第一個Promise對象進入了完成狀態,該方法返回的Promise對象便會根據這第一個完成的Promise對象的狀態而改變。

//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包含了以往模塊加載庫的主要功能,還添加了一些非常使用的設計,以提高ECMAScript的模塊化管理功能。

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

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' 

引入默認模塊

//Syntax:import namespace from 'module-name' import http from 'http' import url from 'url' import fs from 'fs' 

引入模塊部分接口
ES2015中的 模塊化機制支持引入一個模塊的部分接口

//Syntax:import {meber1,meber2} from 'module-name' import {isEmpty} from 'lodash' import {EventEmitter} from 'events' console.log(isEmpty({})) //=>true 

從模塊中局部引用的接口定義一個別名,以避免指代不明或接口重名的情況出現。

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

引入全部局部接口到指定命名空間
有的模塊不會定義默認接口,只是定義了若干個命名接口,將其中的所有接口定義到一個命名空間中,使用以下語法。

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

混入引入默認接口和命名接口
同時引入默認接口和其它命名接口,可以通過混合語句來實現。

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

注意:引入的默認接口必須要使用as語句被賦予一個別名,因為在除模塊引入語句以外的地方default是一個保留關鍵字,所以無法使用。

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

簡潔的語法

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

不引入接口,僅運行模塊代碼
在某些場景下,一些模塊並不需要向外暴露任何接口,只需要執行內容的代碼(如系統初始化)。

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

(2)定義模塊
ES Module中以文件名及其相對或絕對路徑作為該模塊被引用時的標識。

(3)暴露模塊
暴露單一接口
如果需要定義一個項目內的工具集模塊,需要將其中定義的函數或者對象暴露到該文件所定義的模塊上。

//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 語句后所跟着的語句需要具有生命部分和賦值部分
1.聲明部分(Statement)為export語句提供了所暴露接口的標識;
2.賦值部分(Assignment)為export語句提供了接口的值。

那些不符合這兩個條件的語句無法被暴露在當前文件所定義的模塊上,以下代碼被視為非法代碼。

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

暴露模塊默認接口
在某些時候,一個模塊只需要暴露一個接口,比如需要使用模塊機制定義一個只含有一個單一工具類的模塊時,就沒有必要讓這個工具類成為該模塊的一部分,而是讓這個類成為這個模塊。

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

混合使用暴露接口語句
開發者可以為一個模塊同時定義默認接口和其它命名接口。

//module.js export default class Client{ //... } export const foo='bar' //app.js import Client,{foo} from 'module' 

暴露一個模塊的所有接口
在第三方類庫的開發中,不免需要將各種不同的功能塊分成若干個模塊來進行開發,以便管理。ES Module可以將import語句和export組合,直接將一個模塊的接口暴露到另外一個模塊上。

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

暴露一個模塊的部分接口

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

暴露一個模塊的默認接口
可以將一個模塊的默認接口作為另一個模塊的默認接口。

export {default} from 'module' 

Symbol

Symbol的值具有互不等價的特性,開發者同時可以為Symbol值添加一個描述。
(1)基本語法

  • 生成唯一的Symbol值
    執行Symbol({description})函數可以生成一個與其它Symbol值互不等價的Symbol值,其中Symbol()函數可以接受一個除Symbol值以外的值作為該Symbol值的描述,以便通過開發者的辨認判斷其為可選的。
//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 

描述值僅僅是起到描述的作用,不會對Symbol值本身起到任何改變的作用。即便是兩個具有相同描述值的Symbol值也不具有等價性。

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

注意:Symbol函數並不是一個構造函數,不能使用new語句來生成Symbol“對象”,否則會拋出TypeError錯誤。

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

由此可知,Symbol是一種值類型而非引用類型。這就意味着如果將Symbol值作為函數形參進行傳遞,將會進行復制值傳遞而非引用傳遞,這跟其它值類型(字符串,數字等)的行為是一致的。

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 

如果希望得到一個Symbol“對象”,可以使用Object()函數實現。

const symbol=Symbol('foo') typeof symbol //=>symbol const symbolObj=Object(symbol) typeof symbolObj //=>object 
  • 注冊全局可重用Symbol
    ES2015標准除了提供具有唯一性的Symbol值以外,同樣還允許開發者在當前運行時中定義一些全局有效性的Symbol。開發者可以通過一個key向當前運行時注冊一個需要在其他程序中使用的Symbol。
//Syntax:Symbol.for([key]):Symbol const symbol=Symbol.for('footer') 

Symbol. for ()與Symbol ()的區別是,Symbol . for ()會根據傳入的key在全局作用域中注冊一個Symbol值,如果某一個key從未被注冊到全局作用域中,便會創建一個Symbol值並根據key注冊到全局環境中。如果該key己被注冊,就會返冋一個與第一次使用所創建的Symbol值等價的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 

這在大型系統的開發中可以用於一些全局的配罝數據中或者用於需要多處使用的數據中。

  • 獲取全局Symbol的key
    既然可以通過字符串的key在全局環境中注冊一個全局Symbol,那么同樣也可以根據這些全局的Symbol獲取到它們所對應的key。
//Syntax:Symbol kefFor(<global symbol>):String const symbol=Symbol.for('foobar') console.log(Symbol.keyFor(symbol)) //=>foobar 

(2)常用Symbol值
ES2015標准定義了一些內置的常用Symbol值,這些Symbol值的應用深入到了 ECMAScript引擎運行中的各個角落。開發者可以運用這些常用Symbol值對代碼的內部運行邏輯進行修改或拓展,以實現更高級的需求。


 
image.png

(3)Symbol.iterator
在ES2015標准中定義了可迭代對象(Iterable Object)和新的for-of循環語句,其中可迭代對象並不是一種類型,而是帶有@@iterator屬性和可以被for-of循環語句所遍歷的對象的統稱。

for-of循環語句與可迭代對象
for-of循環語句是ES2015中新增的循環語句,它可以對所有可迭代對象進行遍歷,而不僅僅是數組。在ES2015中,默認的可迭代對象有:數組(Array)、字符串(String)、類型數組(TypedArray)、映射對象(Map)、集合對象(Set)和生成器實例(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為開發者提供了可以用於擴展instanceof語句內部邏輯的權限,開發者可以將其作為屬性
鍵,用於為一個類定義靜態方法,該方法的第一個形參便是被檢測的對象,而該方法的返回值便是決定了當次instanceof語句的返回結果。

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

(5)Symbol.match
Symbol.match是正則表達式(或者對象)在作為字符串使用match ()方法時,內部運行邏輯的自定義邏輯入口。開發者可以通過Symbol.match來自行實現match ()方法的運行邏輯,比如利用strcmp (在ECMAScript中為String.prototype.localeCompare())來實現。

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為開發者提供了更高級的控制權力,使得引用類型的對象在轉換為值類型時可以進行自定義處理,無論是轉換為字符串還是數字。

開發者可以使用Symbol.toPrimitive作為屬性鍵為對象定義一個方法,這個方法接受一個參數,這個參數用於判斷當前隱式轉換的目標類型。


 
image.png

需要注意的是,這里的default並不是因為目標類型無法被轉換,而是因為語法上容易造成混亂。

(7)Symbol.toStringTag
常用Symbol的值在前面己經提到過,它的作用是可以決定這個類的實例在調用toString()方法時的標簽內容。

在Object類及其所有的子類的實例中,有一個利用Symbol .toStringTag作為鍵的屬性,該屬性定義着當這個對象的toString()方法被調用時,所返回的Tag的內容是什么。

比如在開發者定義的類中,就可以通過Symbol. toStringTag來修改toString()屮的標簽內容,利用它作為屬性鍵為類型定義一個Getter。

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

 



電子書鏈接: 《實戰ES2015:深入現代JavaScript+應用開發》 密碼: uetw


作者:ywyan
鏈接:https://www.jianshu.com/p/220a54f7adce
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯系作者獲得授權並注明出處。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM