ES6快速入門
一、解構
1. 對象解構
let person = { name: 'Tang', age: 28 }; //必須同名,必須初始化 let {name, age} = person; console.log(`Name: ${name} Age: ${age}`); //Name: Tang Age: 28
let person = { name: 'Tang', age: 28 }, name = 'Mao', age = '22'; //let {name, age} = person; 此時報錯Identifier 'name' has already been declared //必須在圓括號內使用解構表達式,因為暴露的花括號會被解析為塊聲明語句。 ({name, age} = person); console.log(`Name: ${name} Age: ${age}`); //Name: Tang Age: 28 //可以在任何期望傳值的位置使用解構表達式 function see(value) { for (let key in value) { console.log(value[key]); } } see({name, age} = person);//Tang 28
默認值:
let person = { name: 'Tang', age: 28 }; //默認值 /* let {name, age, phone} = person; console.log(phone); //undefined*/ //設置默認值 let {name, age, phone = 110} = person; console.log(phone); //110
賦值給不同名字的變量:
let person = { name: 'Tang', age: 28 }; //賦值給不同名的變量 let {name: realName, age: realAge, phone: realPhone = 119} = person; //尋找name屬性並賦值給realName console.log(`Name: ${realName} Age: ${realAge} Phone: ${realPhone}`); //Name: Tang Age: 28 Phone: 119
2.數組解構
let colors = ['white','yellow','blue', 'red']; //值的選擇和它們在數組中的位置有關,實際變量名稱可以是任意的 let [ , ,first, second, third = 'green'] = colors; //可以忽略一些項 console.log(`First: ${first} Second: ${second} Third: ${third}`); //First: blue Second: red Third: green //和解構對象不同,解構賦值表達式只需要 let fruit = ['apple', 'orange', 'banana'], a = 'pear', b = 'grape'; [a, b] = fruit; console.log(`${a} ${b}`); //apple orange
// 在 ECMAScript 6 中交換變量的值 let a = 1, b = 2; [ a, b ] = [ b, a ]; console.log(a); // 2 console.log(b); // 1
嵌套:
let num = [1, 2, [3, 4, 5], 6, 7]; let [a, b, [c, d], e] = num; console.log(`${c} ${d} ${e}`);//3 4 6
剩余項:
let colors = [ "red", "green", "blue" ]; //剩余項必須是解構語句中的最后項並且不能在后面添加逗號 let [ firstColor, ...restColors ] = colors; console.log(firstColor); // "red" console.log(restColors.length); // 2 console.log(restColors[0]); // "green" console.log(restColors[1]); // "blue" //使用剩余項clone數組 let [...cloneColors] = colors; console.log(cloneColors); //[ 'red', 'green', 'blue' ]
3.混合解構
混合使用數組和對象解構。
let node = { type: "Identifier", name: "foo", loc: { start: { line: 1, column: 1 }, end: { line: 1, column: 4 } }, range: [0, 3] }; let { loc: { start }, range: [ startIndex ] } = node; console.log(start.line); // 1 console.log(start.column); // 1 console.log(startIndex); // 0
4.參數解構
使用對象或數組解構的使用形式取代了命名參數
function setCookie(name, value, { secure, path, domain, expires }) { // code to set the cookie } setCookie("type", "js", { secure: true, expires: 60000 });
必選的參數解構:
調用函數時未給參數解構傳值會拋出錯誤。例如:
setCookie("type", "js");//出錯
js引擎的實際做法
function setCookie(name, value, options) { let { secure, path, domain, expires } = options; //=的右側為undefined肯定會拋出錯誤 }
//可以通過設置默認值解決該問題 function setCookie(name, value, {secure, path, domain, expires} = {}) {}
//當然也可以對每個解構參數提供默認值 function setCookie3(name, value, { secure = false, path = '/tang', domain = 'jia.com', expires = new Date() } = {}) { }
二、符號與符號屬性
在JS已有的基本類型上,ES6又新增了一種新增了一種基本類型:符號(Symbol)。
1.創建符號值
符號沒有字面量,可以使用Symbol函數創建符號值。
三、Set與Map
1.ES6中的Set
一種無重復值的有序列表,Set允許對它包含的數據進行快速訪問。
let set = new Set(); //不會使用強制類型轉換來判斷值是否重復 set.add(8); set.add('8'); //添加多個對象也不會被合並為一項,因為key1,key2不會被 //轉換為字符串,所以不會被認為為一項 let key1 = {}; let key2 = {}; set.add(key1); set.add(key2); console.log(set.size);//4 //set構造器可以接收任意可迭代對象作為參數,包括數組 //並會自動除去數組中的重復值 let set2 = new Set([1, 1, 2, 3, 'tang', 'tang']); console.log(set2.size); //4 //使用has()方法測試值是否存在set中 console.log(set2.has(3)); //true //移除值 set2.delete(3); console.log(set2.has(3));//false //移除所以值 set.clear(); console.log(set.size); //0 //set上的forEach()方法 //為了與數組的forEach()方法中回調函數參數個數 //保持一致該方法也有三個參數,但前兩參數都是當前set值 set2.forEach(function (key, value, thisSet) { console.log(`Key = ${key} Value = ${value} ${thisSet === set2}`); }); /*Key = 1 Value = 1 true Key = 2 Value = 2 true Key = tang Value = tang true*/ let p = { output(value) { console.log(value); }, //如果想在回調函數中使用this,可以給forEach第二個參數傳入this doForEach(data) { data.forEach(function (value) { this.output(value); }, this) }, //或者使用箭頭函數 doForEach2(data) { data.forEach((v) => this.output(v)); } };
Set的不能使用索引訪問值,所以有時候我們需要將它轉換為數組
let set = new Set([1, 2, 2, 3, 4, 5]), //使用擴展運算符將set轉換為數組 arry = [...set]; console.log(arry);//[ 1, 2, 3, 4, 5 ]
WeakSet
set 類型根據它存儲對象的方式,也被稱為 strong set。一個對象存儲在 set 內部或存儲於一
個變量在效果上是等同的。只要對該 Set 實例的引用存在,那么存儲的對象在垃圾回收以釋
放內存的時候無法被銷毀 。
let set = new Set(), key = {}; set.add(key); console.log(set.size); // 1 // 銷毀引用 key = null; console.log(set.size); // 1 // 重新獲得了引用 key = [...set][0];
ES6中同時引進了weakset,該類型不允許存儲原始值(非對象的值都不允許)而專門存儲弱對象引用。
let set = new WeakSet(), key = {name: 'Tang', age: 22}; set.add(key); // console.log(set.size); undefined console.log(set.has(key)); //true key = null; console.log(set.has(key)); //false
另外,不是可迭代類型且沒有size屬性。
2. ES6中的map
ES6中的 map 類型包含一組有序的鍵值對,其中鍵和值可以是任何類型。鍵的比
較結果由 Object.is() 來決定,所以你可以同時使用 5 和 "5" 做為鍵來存儲,因為它們是不同
的類型。這和使用對象屬性做為值的方法大相徑庭,因為對象的屬性會被強制轉換為字符串類型。
let map = new Map(), key1 = {}, //可以使用對象作為鍵 key2 = {}; //因為這些對象不會被強制轉換為其他類型,所以是唯一的 map.set(key1, 'Guns'); map.set(key2, 'Flowers'); map.set('name', 'tang'); console.log(`key1 = ${map.get(key1)}; key2 = ${map.get(key2)}; name = ${map.get('name')}`); //key1 = Guns; key2 = Flowers; name = tang
map的方法有:has(), delete(), clear()
和 set 類似,你可以將數據存入數組並傳給 Map 構造函數來初始化它。數組中的每一項必須
也是數組,后者包含的兩項中前者作為鍵,后者為對應值。因此整個 map 被帶有兩個項的數組所填充。
let map = new Map([['name', 'Tom'], ['age', 2]]); console.log(`It's name is ${map.get('name')} and age is ${map.get('age')}`);// It's name is Tom and age is 2
//map中的forEach方法,同樣接收帶有三個參數的回調函數 //1.map中的下一位置的值 2.該值對應的鍵 3,map本身 map.forEach((v, k, m) => { console.log(`key:${k} value:${v} size:${m.size}`); }); /*key:name value:Tom size:2 key:age value:2 size:2*/
WeakMap
無序鍵值對的集合,其中鍵必須是非 null 的對象,值可以是任意類型,其他與weakset類似。
無clear()方法。
四、迭代器與生成器
1. ES6中迭代器的定義
迭代器只是帶有特殊接口的對象。所有迭代器對象都帶有 next() 方法並返回一個包含兩個屬
性的結果對象。這些屬性分別是 value 和 done,前者代表下一個位置的值,后者在沒有更多
值可供迭代的時候為 true 。迭代器帶有一個內部指針,來指向集合中某個值的位置。
2. ES6中的生成器
生成器是返回迭代器的函數。生成器函數由 function 關鍵字和之后的星號(*) 標識,同時還
能使用新的 yield 關鍵字。星號的位置不能論是放在 function 關鍵字的后面還是在它們插入空
格都是隨意的 。
// 生成器 function *createIterator() { //ECMAScript 6 新引入的 yield 關鍵字指 //定迭代器調用 next() 時按順序返回的值 yield 1; yield 2; yield 3; } // 調用生成器類似於調用函數,但是前者返回一個迭代器 let iterator = createIterator(); console.log(iterator.next().value); // 1 console.log(iterator.next().value); // 2 console.log(iterator.next().value); // 3 /* 或許生成器函數中最有意思的部分是,當執行流遇到 yield 語句時,該生成器就停止運轉了。 例如,當 yield 1 執行之后,該生成器函數就不會執行其它任何部分的代碼直到迭代器再次調 用 next() 。在那時,yield 2 會被執行。生成器函數在運行時能被中斷執行的能力非常強大而 且引出了很多有意思用法*/
function* mkIterator(item = [1, 2, 3]) { for (let i = 0, len = item.length; i < len; i++) { yield item[i]; } } let iterator = mkIterator(); let nextObj = iterator.next(); while (!nextObj.done) { console.log(`The current value is ${nextObj.value}`); nextObj = iterator.next(); } /*The current value is 1 The current value is 2 The current value is 3*/
注意:yield關鍵字的使用范圍
function *createIterator(items) { items.forEach(function(item) { // 語法錯誤:Unexpected identifier yield item + 1; }); }
對象中的生成器:
var o = { /*createIterator: function *(items) { for (let i = 0; i < items.length; i++) { yield items[i]; } }*/ //簡寫: *createIterator(items) { for (let i = 0; i < items.length; i++) { yield items[i]; } } }; let iterator = o.createIterator([1, 2, 3]);
3. 可迭代類型與for-of
可迭代類型是指那些包含 Symbol.iterator 屬性的對象。該知名的symbol 類型
定義了返回迭代器的函數。在 ECMAScript 6 中,所有的集合對象(數組,set 和
map) 與字符串都是可迭代類型,因此它們都有默認的迭代器。可迭代類型是為了
ECMAScript 新添加的 for-of 循環而設計的。
for-lof 循環會在可迭代類型每次迭代執行后調用 next() 並將結果對象存儲在變量中。
循環會持續進行直到結果對象的 done 屬性為 true。
let map = new Map(); map.set('name', 'Jimmy'); map.set('age', 22); map.set('phone', 123); for(let v of map) { console.log(v); } /* * [ 'name', 'Jimmy' ] * [ 'age', 22 ] * [ 'phone', 123 ] * */
可以使用 Symbol.iterator 來訪問對象默認的迭代器:
let iterator = map[Symbol.iterator](); console.log(iterator.next().value); //[ 'name', 'Jimmy' ]
確認一個對象是否可以迭代:
function isIterable(object) { return typeof object[Symbol.iterator] === "function"; }
創建可迭代類型:
let collection = { items: [], *[Symbol.iterator]() { for (let item of this.items) { yield item; } } }; collection.items.push(1); collection.items.push(2); collection.items.push(3); for (let x of collection) { console.log(x); }
4.內置的迭代器
1.集合迭代器
數組,map,set三種類型都有如下迭代器:
1)entries():每次調用時返回一個雙項數組,對於數組來說該數組第一項為
數組索引值,對於set來說第一項是值,map第一項是鍵。
let arry = [2, 3, 5, 8]; let set = new Set([1, 6, 9]); let map = new Map(); map.set("one", 1); map.set("tow", 2); for (let entry of arry.entries()) { console.log(entry); } /*[ 0, 2 ] [ 1, 3 ] [ 2, 5 ] [ 3, 8 ]*/ for (let v of arry) { console.log(v); } /*2 3 5 8*/ for (let entry of set.entries()) { console.log(entry); } /*[ 1, 1 ] [ 6, 6 ] [ 9, 9 ]*/ for (let v of set) { console.log(v); } /*1 6 9*/ for (let entry of map.entries()) { console.log(entry); } /*[ 'one', 1 ] [ 'tow', 2 ]*/ for (let v of map) { console.log(v); } /*[ 'one', 1 ] [ 'tow', 2 ]*/
2)values迭代器:僅能返回集合內的值
for (let value of map.values()) { console.log(value); // 1 2 }
3)keys()迭代器:返回集合中的鍵
2.NodeList的迭代器
var divs = document.getElementsByTagName("div"); for (let div of divs) { console.log(div.id); }
5.擴展運算符與非數組可迭代類型
let map = new Map([ ["name", "Nicholas"], ["age", 25]]), array = [...map]; console.log(array); // [ ["name", "Nicholas"], ["age", 25]]
擴展運算符可以和任意的可迭代類型搭配並使用默認的迭代器來決定包含哪些值。
迭代器會按順序返回數據集中所有的項並依次插入到數組當中。
6.迭代器高級用法
1.向迭代器傳入參數
傳遞給next()的參數會作為已返回yield語句的值(因此首次調用next()傳給它的任何參數都會被忽略)。
function *createIterator() { let first = yield 1; let second = yield first + 2; // 4 + 2 yield second + 3; // 5 + 3 } let iterator = createIterator(); console.log(iterator.next()); // "{ value: 1, done: false }" console.log(iterator.next(4)); // "{ value: 6, done: false }" console.log(iterator.next(5)); // "{ value: 8, done: false }" console.log(iterator.next()); // "{ value: undefined, done: true }"
2.在迭代器中拋出錯誤
通過使用 throw() 方法,迭代器可以選擇在某一次迭代拋出錯誤。
function *createIterator() { let first = yield 1; let second = yield first + 2; // yield 4 + 2, 之后拋出錯誤 yield second + 3; // 不會執行 } let iterator = createIterator(); console.log(iterator.next()); // "{ value: 1, done: false }" console.log(iterator.next(4)); // "{ value: 6, done: false }" console.log(iterator.throw(new Error("Boom"))); // 由生成器拋出錯誤
3.包含return的生成器
既然生成器本質上是函數,你可以使用 return 語句來讓它提前執行完畢並針對 next() 的調用
來指定一個返回值。在本章的大部分實例中,最后一次在迭代器上調用 next() 會返回
undefined,不過你可以像在普通函數中那樣使用 return 語句來指定另外的返回值。生成器會
將 return 語句的出現判斷為所有的任務已處理完畢,所以 done 屬性會被賦值為 true,如果
指定了返回值那么它會被賦給 value 屬性。
function *createIterator() { yield 1; return 42; } let iterator = createIterator(); console.log(iterator.next()); // "{ value: 1, done: false }" console.log(iterator.next()); // "{ value: 42, done: true }" console.log(iterator.next()); // "{ value: undefined, done: true }"
function *createNumberIterator() { yield 1; yield 2; } function *createColorIterator() { yield "red"; yield "green"; } function *createCombinedIterator() { yield *createNumberIterator(); yield *createColorIterator(); yield true; } var iterator = createCombinedIterator(); console.log(iterator.next()); // "{ value: 1, done: false }" console.log(iterator.next()); // "{ value: 2, done: false }" console.log(iterator.next()); // "{ value: "red", done: false }" console.log(iterator.next()); // "{ value: "green", done: false }" console.log(iterator.next()); // "{ value: true, done: false }" console.log(iterator.next()); // "{ value: undefined, done: true }"
7.異步任務
異步操作的傳統做法是在它結束之后調用回調函數
let fs = require("fs"); //當該操作完成后,回調函數開始執行 fs.readFile("config.json", function(err, contents) { if (err) { throw err; } doSomethingWith(contents); console.log("Done"); });
一旦需要嵌套多個回調函數或者處理一大批異步任務時,代碼會變得極其復雜。
1.任務運行器
原理:因為 yield 可以中斷執行,並在繼續運行之前等待 next() 方法的調用,
你可以不使用回調函數來實現異步調用,首先,你需要一個函數來調用生成器以便讓迭代器開始運行:
function run(taskDef) { // 創建迭代器,使它們可以在別處使用 let task = taskDef(); // 任務開始執行 let result = task.next(); // 遞歸函數持續調用 next() function step() { // 如果任務未完成 if (!result.done) { if (typeof result.value === "function") { result.value(function(err, data) { if (err) { result = task.throw(err); return; } result = task.next(data); step(); }); } else { result = task.next(result.value); step(); } } } // 開始遞歸 step(); }