一 背景編程
JavaScript通過二十來年年的發展,由最初簡單的交互腳本語言,發展到今天的富客戶端交互,後端服務器處理,跨平臺(Native),以及小程序等等的應用。JS的角色愈來愈重要,處理場景愈來愈複雜。在這個背景下,JS最初的簡陋設計顯然是不太夠用的,其鬆散的語法規則,拗口的繼承機制(傳說中的6種繼承方法),無命名空間,模塊化,以及異步處理的回調地獄等等特性在開發過程當中容易成爲開發人員的各類痛點,各個JS框架好比jQuery,SeaJs,等等爲了這些問題也是操碎了心。不過隨着JS語言的發展,上面的各類問題也逐漸獲得了優化。json
然而JS又是一門神奇的語言,他的大部分場景寄存於瀏覽器的,不像PHP,ASP.NET,Java背後有一個專注的廠商支持,而是面對IE,Chrome,Firefox,Safari,Opera等各類互爲競爭對手廠商,面對的狀況略微複雜,其兼容性又能夠寫一本血淚史。因此本文提到的各類特性在使用以前須要考究下你的用戶羣體是否支持,降級處理,polyfill轉義等等。小程序
關於ES6的書如今已經有不少了,其中包括不少名著,好比阮一峯的ECMAScript 6 入門,本文從個人角度從新梳理了一下,力求由淺入深,而且重點介紹一些比較實用的特性,讓知識點更加系統化,清晰化。後端
二 ES6帶來了什麼api
由於任何一門語言都是在不斷的發展和進化的,好比C#語言依託.NetFramework由1.0發展到4.5語言能力不斷在加強,PHP由最初的1.0發展到7.0版本,一樣JS一樣也是在不斷的完善進步中的,使用ES6能夠給你帶來如下變化:數組
(1) 語法更加規範,語義化更強,不容易出錯:引入塊級做用域避免變量衝突,下降出錯概率。瀏覽器
(2) 語法更加簡潔,便於維護:函數默認參數,箭頭函數,解構賦值,擴展運算符,字符串變量等等讓以前不少冗餘代碼瞬間消失,可讀性更強;類的定義和繼承更接近常見的C++和PHP語法,更加便於維護。服務器
(3) 功能更增強大:新增了Map,Set,ArrayBuffer等數據結構,新增了Proxy,Reflect,Decorator等輔助函數,處理對象更加靈活,擴展了JS語言能力。網絡
(4) 異步編程更加直觀:擺脫回調地獄,以相似於同步的語法進行異步編程。數據結構
(5) 模塊化開發:JS終於內置支持了模塊,命名空間,變量衝突的問題獲得了原生支持。
二 總覽
本系列主要從複雜性和實用性兩個維度來逐步探討,主要包括:
(1) JS語言的加強:包括語法的規範,數據類型的擴展,新增數據結構,解構賦值等
(2) 函數的加強:包括針對編寫函數的優化語法,默認參數,擴展運算符,箭頭函數等
(3) 對象的加強:包括對對象定義和繼承的優化寫法等
(4) 異步編程:包括Promise,Generator,Async,Await等異步編程寫法
(5) 輔助模塊:包括Symbol,Proxy,Reflect,Decorator等輔助API,這塊由於應用很少,實用性略低一些,因此本文暫不討論,若是有興趣能夠查詢相關資料。
(6) 模塊化編程:包括針對JS模塊的語言層級的支持
三 本篇目錄
本篇做爲JS語言的加強篇,主要有如下內容:
(1) let/const/塊級做用域
(2) 解構賦值
(3) 字符串/數組的擴展
(4) Set/Map數據結構
(5) ArrayBuffer數據結構
四 開始
1. let/const/塊級做用域:
咱們知道JS語言var是來定義變量的,並且是動態變量很是方便,ES6爲什麼要發明let和const?
ES6以前沒有塊級做用域的說法,而只有函數做用域,這是JS跟C++,PHP相比一個很明顯的特性:
void main() { int num = 2; int index = 1; if(index > 0) { int test = 10; } printf("%d/n",test); }
運行時會test變量不存在,由於test變量只能在if的花括號內有效,出了花括號會銷燬,可是JS就不同了:
function main() { var num = 2; var index = 1; if(index > 0){ var test = 10; } document.write(test); }
妥妥的沒問題,由於定義的test變量在main函數內都是有效的,爲何呢,由於在JS裏面存在變量提高和函數聲明提高的過程,最終執行結果實際上是這樣子:
function main() { var num,index,test; num = 2; index = 1; if(index > 0){ test = 10; } document.write(test); }
因此在ES6以前,var聲明變量就有這些特性,變量提高,函數做用域,以及能夠重複命名,每次命名都是一次覆蓋,因此使用var變量容易出現坑:
(1) 由於變量能夠提高,變量能夠在任何位置聲明,回溯性很差,維護性很差。
(2) 容許同名,內部變量覆蓋外部變量 ,下面的例子本意是想先用外部變量,但實際上被內部變量覆蓋了。
var name = "michael"; function test(){ console.log(name); var name = "leo"; } test();//undefined
(3) for循環變量容易泄露爲全局變量,並且在數組賦值中會引發歧義
var func = [];
for (var index = 0; index < 10; index++) {
func[index] = function(){
console.log(index);
};
}
console.log(index); //10
func[0](); //10
上面的例子存在兩個問題:index變量在循環結束後暴露在全局污染外部函數,func數組的每一個成員打印出來的都是10。由於他們引用的是同一個全局變量index,而這個變量在調用func[0]的時候值是10,因此就須要使用閉包來建立變量的方式來解決。
而let和const聲明變量就能夠避免以上問題
(1) 不存在變量提高,變量必須先聲明再使用,更加規範嚴謹
(2) 變量名不能重複定義,避免歧義覆蓋
(3) 定義的變量引入塊級做用域,只有塊級做用域內有效,避免內外部變量相互污染,不再用使用匿名函數了。
(4) for循環的計算器內部使用,index用完即銷燬,並且再也不須要閉包來處理,由於let在每次循環都從新生成,並且會自動記錄上次的值,這是ES6在處理循環的一個特性。
let func = []; for (let index = 0; index < 10; index++) { func[index] = function(){ console.log(index); }; } func[0](); //0 console.log(index); //index is not defined
let和const的區別
let 用於可變值的變量,一旦聲明做用域就會被限定在本塊。
const用於定義常亮,好比PI等等,對於簡單類型的數據(數值、字符串、布爾值),const定義之後地址就會固定,因此值也不能改變,可是對於引用類型(數組,對象),雖然地址是不會變的,可是const對於地址指向的內存堆的值是能夠變的,也就是說const聲明的對象屬性是能夠修改的,這一點須要留意。
2. 解構賦值:
解構賦值是訪問數組和對象的一種便捷方式,可讓代碼更加簡潔優雅。好比訪問數組,之前咱們用的方式是:
let name = "mic", sex = "male", city = "深圳", height = 1.65;
用解構的方式,明顯代碼簡潔了不少。
let [name,sex,city,height] = ["mic","male","深圳",1.65];
(1) 數組解構賦值
數組的結構賦值就是把右邊數組按位置賦值到左邊的變量,能夠嵌套,能夠爲空。
let [name, [[city], sex]] = ["mic", [["深圳"], "male"]];
let [ , , name] = ["深圳", "male", "mic"];
右邊的結構不只僅是數組,只有是有Iterator接口的數據結構均可以。
解構賦值能夠帶有默認值,若是右邊對應的值是undefined,默認值生效。
let [name="mic"] = ["mic2"];
let [name= "mic"] = [undefined];
(2) 對象解構賦值,一樣對比之前的方式:
let obj = { name:"mic", city:"深圳", sex:"male" } //ES5 let name = obj.name, city = obj.city, sex = obj.sex; //ES6 let {name,city,sex} = obj;
還能夠帶上別名:
let {name:my_name,city:my_city,sex:my_sex} = obj; console.log(my_name);//mic console.log(my_city);//深圳 console.log(my_sex);//male
對象解構一樣能夠嵌套帶默認值。
(3) 函數參數解構,爲函數體內的參數賦值,也能夠帶默認值。
function reg([name,sex,city="深圳"]){ console.log(name); console.log(sex);
console.log(city); } reg(["mic","male"]); //mic //male
3.字符串/數組的擴展
3.1 字符串擴展比較實用的就是兩個特性:模板字符串
(1)模板字符串:還記得之前多行字符串的拼湊麼,如今實用`符號能夠定義多行文本,而且能夠嵌入變量。
//ES5 var name = "mic"; var str = "<div>" + "this is a test,my name = " + name + "</div>"; //ES6 let name = "mic; let str = `<div> this is a test,my name = ${name} </div>`;
使用模板字符串顯然方便不少。
3.2 數組擴展
(1) 擴展運算符:...符號能夠把數組解析爲逗號分隔的序列,也就是展開數組,經常使用語數組傳遞給函數參數。
(2) Array.from():將類數組轉換爲真正的數組,不再須要使用拗口的Array.prototype.slice.call了。、
(3) Array.of():將一組值轉爲數組,用來替代new Array,由於new Array(3) 存在歧義,3表明數組長度,實際上我也可能只想建立一個數組只包含3,因此使用Array.of的行爲更加規範。
(4) Array.copyWith/find/findIndex/includes 具體能夠參考api
(5) Array.keys/values/entries 分別用來遍歷下標,值以及下標和值組成的數組
4. Set/Map結構:
4.1 Set結構:Set結構是一種沒有重複值的數組,當你在業務中須要用到去重時,Set最合適不過了。
(1) 建立Set:傳遞數組或者具備iterable接口的結構來建立。
const set = new Set([1, 2, 3, 4, 4]);
(2) 方法:size/add/delete/has/clear 具體見api
(3) 遍歷:
keys():返回鍵名的遍歷器
values():返回鍵值的遍歷器
entries():返回鍵值對的遍歷器
forEach():使用回調函數遍歷每一個成員
(4) 小技巧,數組去重:
function uniqArray(array_list){ return [...new Set(array_list)]; }
4.2 Map結構:Map的出現是爲了Object鍵值只能是字符串的問題,Map出現之前,處理Hash值的數據結構只有對象,可是對象的鍵值只能是字符串,因此有侷限性,而Map結構在Object的字符串基礎上,能夠設置各類類型的值,包括數字,字符串,對象等等均可以做爲鍵值,極大的豐富了處理Hash值的功能。
(1) 建立/構造函數
二維數組,或者任何具備 Iterator 接口、且每一個成員都是一個雙元素的數組的數據結構(詳見《Iterator》一章)均可以看成Map構造函數的參數
const map = new Map([ ['name','mic'], ['city', 'shenzhen'] ]);
(2) 方法:
size
set(key,value)
get(key)
has(key)
delete(key)
clear()
(3) 遍歷:
keys():返回鍵名的遍歷器
values():返回鍵值的遍歷器
entries():返回鍵值對的遍歷器
forEach():使用回調函數遍歷每一個成員
5. ArrayBuffer結構:
5.1 什麼是ArrayBuffer結構:
ArrayBuffer是ES6新增的一種數據結構,對於之前瀏覽器場景而言,最經常使用的數據結構就是字符串,json字符串等文本格式,對於小數據量的業務已經足夠了,可是在大數據量場景,使用字符串就會比較吃力,好比WebGL的處理,須要處理大量的數據,或者小程序藍牙通訊,須要傳輸聲音數據等等,這種狀況下字符串轉換是須要大量時間的,而ArrayBuffer是一種二進制數據,可讓JS和設備之間之間進行二進制數據傳輸,無需轉換。
二進制數據的操做主要由ArrayBuffer對象來存儲,一個保存了內存某塊區域的對象,經過TypeArray或DataView來訪問,TypedArray是固定格式來訪問,而DataView能夠混合格式來訪問。
5.2 ArrayBuffer對象:
(1)建立:ArrayBuffer對象是二進制數據的存儲媒介,至關於數據源,獲取和設置二進制數據最終是存儲在這個對象裏面,可是不能直接對他讀取和保存,只能經過TypedArray或DataView來讀寫。
let buf = new ArrayBuffer(128);//建立一個128字節的內存區域,每一個字節默認值0 if(buf.byteLength == 128){ //分配成功 }
else{
//異常處理
}
(2)屬性和方法:
ArrayBuffer.prototype.byteLength: 返回內存字節長度
ArrayBuffer.prototype.slice(start,end): 拷貝出一個新的ArrayBuffer對象,從start開始到end
ArrayBuffer.isView():靜態方法,判斷一個對象是不是TypedArray
實例或DataView
實例
5.3 TypedArray視圖
ArrayBuffer存儲的數據能夠有不一樣的視角來讀取和保存,TypedArray視圖就是來訪問ArrayBuffer對象的方法之一,他經過某個固定的數據類型來訪問,主要有如下幾種類型:
Int8Array:8位有符號整數,長度1個字節。
Uint8Array:8位無符號整數,長度1個字節。
Uint8ClampedArray:8位無符號整數,長度1個字節,溢出處理不一樣。
Int16Array:16位有符號整數,長度2個字節。
Uint16Array:16位無符號整數,長度2個字節。
Int32Array:32位有符號整數,長度4個字節。
Uint32Array:32位無符號整數,長度4個字節。
Float32Array:32位浮點數,長度4個字節。
Float64Array:64位浮點數,長度8個字節。
構造函數的靜態屬性和實例的屬性BYTES_PER_ELEMENT均可以獲取該類型的字節數:
Int32Array.BYTES_PER_ELEMENT // 4 let view = new Int32Array(8); view.BYTES_PER_ELEMENT;// 4
(1) 建立:
TypedArray(ArrayBuffer buffer, [int start], [int length]]);//buffer爲ArrayBuffer對象,start可選值,視圖的開始字節,默認0,length結束的字節,默認所有
var buff = new ArrayBuffer(128);// 建立一個128字節的ArrayBuffer var view = new Int32Array(buff);//建立一個指向b的Int32視圖,開始於字節0,直到緩衝區的末尾
TypedArray(length);//直接經過指定長度來建立ArrayBuffer和View
let view = new Int32Array(8); //等同於 let buf = new ArrayBuffer(4*8); let view = new Int32Array(buf); view[0] = 10; view[1] = 20; view[2] = view[0] + view[1];
TypedArray(typedArray);//經過其餘視圖拷貝新視圖,新的視圖會從新建立內存
TypedArray(arrayLikeObject);//經過數組來建立
TypedArray相似於數組結構,數組的各類方法均可以用於TypedArray,也能夠被遍歷。
(2) 屬性和方法:
TypedArray.prototype.buffer:返回整段內存區域對應的ArrayBuffer對象,屬性爲只讀。
TypedArray.prototype.byteLength:返回TypedArray數組佔據的內存長度,單位爲字節,屬性爲只讀。
TypedArray.prototype.byteOffset:返回TypedArray數組從底層ArrayBuffer對象的哪一個字節開始,屬性爲只讀。
TypedArray.prototype.length:TypedArray數組含有多少個成員。
TypedArray.prototype.set():用於複製數組(普通數組或TypedArray數組)。
TypedArray.prototype.subarray():對於TypedArray數組的一部分,再創建一個新的視圖。
TypedArray.prototype.slice():返回一個指定位置的新的TypedArray實例。
TypedArray.of():用於將參數轉爲一個TypedArray實例。
TypedArray.from():返回一個基於這個結構的TypedArray實例。
(3)字符串和ArrayBuffer的轉換
// ArrayBuffer轉爲字符串 function ab2str(buf) { return String.fromCharCode.apply(null, new Uint16Array(buf)); } // 字符串轉爲ArrayBuffer對象 function str2ab(str) { var buf = new ArrayBuffer(str.length * 2); // 每一個字符佔用2個字節 var bufView = new Uint16Array(buf); for (var i = 0, strLen = str.length; i < strLen; i++) { bufView[i] = str.charCodeAt(i); } return buf; }
5.4 DataView視圖:要了解DataView視圖的使用場景,須要瞭解計算機存儲二進制數據的大端序和小段序問題,具體能夠百度一下,總的來講,對於我的電腦和服務器存儲二進制的格式順序稍微有點差異。
對於跟網卡,聲卡等本機設備進行通訊,TypedArray視圖是徹底夠用的,可是對於服務器獲取其餘網絡設備傳來的大端序數據,使用TypedArray就會出現問題,DataView就是爲了解決這個問題的方法。
(1) 建立:
DataView(ArrayBuffer buffer,[int start],[length]);//同TypedArray
(2) 屬性和方法
DataView實例有如下屬性,含義與TypedArray實例的同名方法相同。
DataView.prototype.buffer:返回對應的ArrayBuffer對象
DataView.prototype.byteLength:返回佔據的內存字節長度
DataView.prototype.byteOffset:返回當前視圖從對應的ArrayBuffer對象的哪一個字節開始
DataView實例提供8個方法讀取內存。
getInt8:讀取1個字節,返回一個8位整數。
getUint8:讀取1個字節,返回一個無符號的8位整數。
getInt16:讀取2個字節,返回一個16位整數。
getUint16:讀取2個字節,返回一個無符號的16位整數。
getInt32:讀取4個字節,返回一個32位整數。
getUint32:讀取4個字節,返回一個無符號的32位整數。
getFloat32:讀取4個字節,返回一個32位浮點數。
getFloat64:讀取8個字節,返回一個64位浮點數。
DataView視圖提供8個方法寫入內存。
setInt8:寫入1個字節的8位整數。
setUint8:寫入1個字節的8位無符號整數。
setInt16:寫入2個字節的16位整數。
setUint16:寫入2個字節的16位無符號整數。
setInt32:寫入4個字節的32位整數。
setUint32:寫入4個字節的32位無符號整數。
setFloat32:寫入4個字節的32位浮點數。
setFloat64:寫入8個字節的64位浮點數。
var buffer = new ArrayBuffer(128); var data_view = new DataView(buffer); var view1 = data_view.getUint8(0); var view2 = data_view.getUint16(1,true);
view1.setUint8(0,10);
View2.setUint16(1,20,true);
get和set方法都有第二個可選參數,用於指定是大端序仍是小端序訪問,默認是大端序,設置爲true可指定爲小端序。