ES2015簡介和基本語法

 

ECMAScript 6(如下簡稱ES6)是JavaScript語言的下一代標準。由於當前版本的ES6是在2015年發佈的,因此又稱ECMAScript 2015。也就是說,ES6就是ES2015。javascript

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

1、ECMAScript的發展歷程

 
image.png

2、ES2015能爲實際開發帶來什麼

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

ES2015概述:

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

語法糖:

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

使用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)) }) 

模塊化和組件化:

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

const和let:

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

總結:語法糖、模塊化、組件化等工程優點,能夠在實際開發中提高開發效率和代碼質量。json

3、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 來源:簡書 簡書著做權歸做者全部,任何形式的轉載都請聯繫做者得到受權並註明出處。
相關文章
相關標籤/搜索