本系列博客爲ES6基礎語法的使用及總結,若有錯誤,歡迎指正。 重學ES6基礎語法(六)主要包括 Generator、Set/Map、Proxy等。面試
Javascript語言的執行環境是"單線程"(single thread)。ajax
所謂"單線程",就是指一次只能完成一件任務。若是有多個任務,就必須排隊,前面一個任務完成,再執行後面一個任務,以此類推。編程
Javascript語言將任務的執行模式分紅兩種:同步(Synchronous)和異步(Asynchronous)。數組
"同步模式"就是上一段的模式,後一個任務等待前一個任務結束,而後再執行,程序的執行順序與任務的排列順序是一致的、同步的;
"異步模式"則徹底不一樣,每個任務有一個或多個回調函數(callback),前一個任務結束後,不是執行後一個任務,而是執行回調函數,後一個任務則是不等前一個任務結束就執行,因此程序的執行順序與任務的排列順序是不一致的、異步的。瀏覽器
"異步模式"很是重要。在瀏覽器端,耗時很長的操做都應該異步執行,避免瀏覽器失去響應,最好的例子就是Ajax操做。在服務器端,"異步模式"甚至是惟一的模式,由於執行環境是單線程的,若是容許同步執行全部http請求,服務器性能會急劇降低,很快就會失去響應。bash
優勢是簡單,容易理解和部署;缺點是不利於代碼的閱讀和維護,各個部分之間高度耦合(Coupling),流程混亂,並且每一個任務只能指定一個回調函數。服務器
任務的執行不取決於代碼的順序,而取決於某個事件是否發生。數據結構
經過事件監聽,能夠綁定多個事件,每一個事件能夠指定多個回調函數,並且能夠"去耦合"(Decoupling),有利於實現模塊化。缺點是整個程序都要變成事件驅動型,運行流程會變得很不清晰。異步
咱們假定,存在一個"信號中心",某個任務執行完成,就向信號中心"發佈"(publish)一個信號,其餘任務能夠向信號中心"訂閱"(subscribe)這個信號,從而知道何時本身能夠開始執行。這就叫作"發佈/訂閱模式"(publish-subscribe pattern),又稱"觀察者模式"(observer pattern)。模塊化
這種方法的性質與"事件監聽"相似,可是明顯優於後者。由於咱們能夠經過查看"消息中心",瞭解存在多少信號、每一個信號有多少訂閱者,從而監控程序的運行。
Generator 函數是 ES6 提供的一種異步編程解決方案
顧名思義,它是一個生成器,它也是一個狀態機,內部擁有值及相關的狀態。生成器返回一個迭代器Iterator對象,咱們能夠經過這個迭代器,手動地遍歷相關的值、狀態,保證正確的執行順序。
Generator 函數不一樣於普通函數,是能夠暫停執行的,因此函數名以前要加星號,以示區別。
整個 Generator 函數就是一個封裝的異步任務,或者說是異步任務的容器。異步操做須要暫停的地方,都用 yield
關鍵字註明。
yield
關鍵字可讓 Generator內部的邏輯可以切割成多個部分。function* listNum() {
let i = 1;
yield i;
i++;
yield i;
i++;
yield i;
}
const num = listNum();
console.log(listNum());
複製代碼
next
方法執行一個部分代碼,執行哪一個部分就會返回哪一個部分定義的狀態const num = listNum();
console.log(num.next());
console.log(num.next());
console.log(num.next());
複製代碼
如上代碼,定義了一個listNum的生成器函數,調用以後返回了一個迭代器對象(即num)
調用next
方法後,函數內執行第一條yield
語句,輸出當前的狀態done(迭代器是否遍歷完成)以及相應值(通常爲yield關鍵字後面的運算結果)
每調用一次next
,則執行一次yield
語句,並在該處暫停。
2.1 能夠發現,若是不調用next
方法,那麼函數中封裝的代碼不會當即被執行(下例中的console.log(arr);
沒有運行)
let arr = [
{name: 'zs',age: 38,gender: 'male'},
{name: 'yw',age: 48,gender: 'male'},
{name: 'lc',age: 28,gender: 'male'},
];
function* loop(arr) {
console.log(arr);
for(let item of arr){
yield item;
}
}
let repoGen = loop(arr);
console.log(repoGen);
複製代碼
2.2 只有開始調用next
方法,生成器函數裏面的代碼纔開始執行
let repoGen = loop(arr);
console.log(repoGen.next());
console.log(repoGen.next());
console.log(repoGen.next());
複製代碼
2.3 當遍歷完以後,done標誌變爲true時,再打印生成器函數,能夠發現它的狀態已經變爲了closed
let repoGen = loop(arr);
console.log(repoGen);
console.log(repoGen.next());
console.log(repoGen.next());
console.log(repoGen.next());
console.log(repoGen.next());
console.log(repoGen);
複製代碼
3.1 調用Generator函數後,不管函數有沒有返回值,都會返回一個迭代器對象
3.2 調用Generator函數後,函數中封裝的代碼不會當即被執行
next()
調用中的傳參在調用next
方法的時候能夠傳遞一個參數, 這個參數會傳遞給上一個yield
注意:第一次調用next()
時是不能傳參的,只能從第二次開始
4.1 第一次調用next以後返回值one爲1,但在第二次調用next的時候one實際上是undefined的,由於generator不會自動保存相應變量值,咱們須要手動的指定,這時two值爲NaN,在第三次調用next的時候執行到yield 3 * two,經過傳參將上次yield返回值two設爲2,獲得結果
function* showNumbers() {
var one = yield 1;
var two = yield 2 * one;
yield 3 * two;
}
var show = showNumbers();
console.log(show.next().value);// 1
console.log(show.next().value);// NaN
console.log(show.next(2).value); // 6
複製代碼
4.2 解析我寫不出來(只能意會不能言傳...)
function* gen() {
console.log("123");
let res = yield "aaa";
console.log(res);
console.log("567");
yield 1 + 1;
console.log("789");
yield true;
}
let it = gen();
console.log(it.next()); //先輸出123,再輸出{value: "aaa", done: false}
console.log(it.next("666")); //傳遞參數給res,輸出666;再輸出567;再輸出{value: 2, done: false}
console.log(it.next()); //{value: true, done: false}
console.log(it.next()); //{value: undefined, done: true}
複製代碼
5.1 讓函數返回多個值
function* calculate(a, b) {
yield a + b;
yield a - b;
}
let it = calculate(10, 5);
console.log(it.next().value);
console.log(it.next().value);
複製代碼
5.2 用同步的流程來表示異步的操做(用來處理ajax請求的工做流)
5.3 因爲Generator函數就是遍歷器生成函數,所以能夠把Generator賦值給對象的Symbol.iterator屬性,從而使得該對象具備Iterator接口。
for...of循環能夠自動遍歷Generator函數時生成的Iterator對象,且此時再也不須要調用next方法。
function *foo() {
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
return 6;
}
for (let v of foo()) {
console.log(v);
}
// 1 2 3 4 5
複製代碼
上面代碼使用for...of
循環,依次顯示5個yield
語句的值。這裏須要注意,一旦next
方法的返回對象的done
屬性爲true
,for...of
循環就會停止,且不包含該返回對象,因此上面代碼的return
語句返回的6,不包括在for...of
循環之中。
ES6提供了新的數據結構Set。它相似於數組,可是成員的值都是惟一的,沒有重複的值。
與數組的不一樣之處:Set數據結構不能經過索引來獲取元素。
Set自己是一個構造函數,用來生成Set數據結構。
格式:new Set([iterable]);
let list = new Set();
console.log(list);
let color = new Set(['red', 'yellow', 'green']);
console.log(color);
複製代碼
2.1 add(value)
在Set對象尾部添加一個元素。返回該Set對象。
注意:添加相同的成員會被忽略
let list = new Set();
[1,2,3,4,5].map(item => list.add(item));
console.log(list);
複製代碼
2.2 .size
返回Set實例的成員總數。
let list = new Set();
[1,2,3,4,5].map(item => list.add(item));
console.log(list.size); //5
複製代碼
2.3 delete(value)
刪除某個值,返回一個布爾值,表示刪除是否成功。
let list = new Set();
[1,2,3,4,5].map(item => list.add(item));
console.log(list.delete(5)); //true
複製代碼
2.4 .clear
移除Set對象內的全部元素,沒有返回值。
let list = new Set();
[1,2,3,4,5].map(item => list.add(item));
list.clear();
console.log(list); //Set(0) {}
複製代碼
2.5 has(value)
返回一個布爾值,表示該值在Set中存在與否。
let list = new Set();
[1,2,3,4,5].map(item => list.add(item));
console.log(list.has(1)); //true
複製代碼
3.1 .values()
返回一個新的迭代器對象,該對象包含Set對象中的按插入順序排列的全部元素的值。
let list = new Set();
[1,2,3,4,5].map(item => list.add(item));
console.log(list.values());
複製代碼
3.2 調用遍歷器的next()
方法實現遍歷
let list = new Set();
[1,2,3,4,5].map(item => list.add(item));
let it = list.values();
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());
複製代碼
3.3 Set方法部署了Iterator接口,因此也可使用for of
來遍歷
for(let key of list){
console.log(key);
}
複製代碼
利用forEach遍歷
list.forEach((item,key,ownSet) => {
console.log(item, key, ownSet);
})
複製代碼
面試的時候常常有問:用ES5方法和ES6分別實現數組去重
先把數組轉爲Set結構實現去重,由於Set是能夠遍歷的,因此再使用擴展運算符將Set轉爲數組
let arr = [1,2,4,6,4,3,6,8];
let numberSet = new Set(arr); //數組轉Set
console.log(numberSet);
let uniqueArr = [...numberSet]; //Set轉數組
console.log(uniqueArr);
複製代碼
Array.prototype.unique = function () {
return [...new Set(this)];
};
複製代碼
WeakSet結構與Set相似,也是不重複的值的集合。
WeakSet結構有如下三個方法。
.add(value)
:向WeakSet實例添加一個新成員。
.delete(value)
:清除WeakSet實例的指定成員。
.has(value)
:返回一個布爾值,表示某個值是否在WeakSet實例之中。
let lucy = {region:'America',age: 18};
let lily = {region:'Canada',age: 20};
let person = new WeakSet([lucy,lily]);
person.add('lucas');
console.log(person); //Invalid value used in weak set
複製代碼
for(let key of person){
console.log(key); //person is not iterable
}
複製代碼
size
屬性,沒有辦法遍歷它的成員。clear()
方法,可是具備本身清除的做用,避免內存泄漏。let lucy = {region:'America',age: 18};
let lily = {region:'Canada',age: 20};
let person = new WeakSet([lucy,lily]);
console.log(person);
複製代碼
let lucy = {region:'America',age: 18};
let lily = {region:'Canada',age: 20};
let person = new WeakSet([lucy,lily]);
console.log(person);
lily = null;
console.log(person);
複製代碼
Map 對象保存鍵值對。任何值(對象或者原始值) 均可以做爲一個鍵或一個值。
ES6提供的Map數據結構。它相似於對象,也是鍵值對的集合,可是「鍵」的範圍不限於字符串,各類類型的值(包括對象)均可以看成鍵。也就是說,Object結構提供了「字符串—值」的對應,Map結構提供了「值—值」的對應,是一種更完善的Hash結構實現。若是你須要「鍵值對」的數據結構,Map比Object更合適。
new Map([iterable])
const people = new Map();
people.set('lucy',18);
people.set('lily',20);
console.log(people);
複製代碼
2.1 .size
size屬性返回Map結構的成員總數。
2.2 set(key, value)
set方法設置key所對應的鍵值,而後返回整個Map結構。若是key已經有值,則鍵值會被更新,不然就新生成該鍵。
2.3 get(key)
get方法讀取key對應的鍵值,若是找不到key,返回undefined。
2.4 has(key)
has方法返回一個布爾值,表示某個鍵是否在Map數據結構中。
2.5 delete(key)
delete方法刪除某個鍵,返回true。若是刪除失敗,返回false。
2.6 .clear()
clear方法清除全部成員,沒有返回值。
const people = new Map();
people.set('lucy',18);
people.set('lily',20);
people.set({},3);
for(let key of people){
console.log(key);
}
複製代碼
people.forEach((value,key,map) => {
console.log(value, key, map);
})
複製代碼
Map的鍵(即key)能夠是任意類型值,能夠是一個對象,能夠是一個函數等
const people = new Map();
people.set('lucy',18);
people.set('lily',20);
people.set({},3);
console.log(people);
複製代碼
5.1 Map轉爲數組
Map轉爲數組最方便的方法,就是使用擴展運算符(...)。
const people = new Map();
people.set('lucy',18);
people.set('lily',20);
people.set({},3);
let arr = [...people];
console.log(arr);
複製代碼
new Map([[true, 7], [{foo: 3}, ['abc']]])
複製代碼
5.3 Map轉爲對象
若是全部Map的鍵都是字符串,它能夠轉爲對象。
function strMapToObj(strMap) {
let obj = Object.create(null);
for (let [k,v] of strMap) {
obj[k] = v;
}
return obj;
}
複製代碼
5.4 對象轉爲Map
function objToStrMap(obj) {
let strMap = new Map();
for (let k of Object.keys(obj)) {
strMap.set(k, obj[k]);
}
return strMap;
}
複製代碼
WeakMap與Map的區別在於:
WeakMap與Map在API上的區別主要是兩個:
key()
、values()
和entries()
方法),也沒有size
屬性;clear
方法。這與WeakMap的鍵不被計入引用、被垃圾回收機制忽略有關。所以,WeakMap只有四個方法可用:get()
、set()
、has()
、delete()
。Proxy 對象用於定義基本操做的自定義行爲(如屬性查找,賦值,枚舉,函數調用等)。 幫助咱們重寫對象上的一些默認的方法,定義本身的業務邏輯。
1.用法
let p = new Proxy(target, handler);
target
用Proxy包裝的目標對象(能夠是任何類型的對象,包括原生數組,函數,甚至另外一個代理)。handler
一個對象,其屬性是當執行一個操做時定義代理的行爲的函數。Proxy 對象的全部用法,都是上面這種形式,不一樣的只是handler參數的寫法。其中,new Proxy()
表示生成一個Proxy實例,target參數表示所要攔截的目標對象,handler參數也是一個對象,用來定製攔截行爲。
2.Proxy實例的方法
get()
用於攔截某個屬性的讀取操做
const person = {name: 'ghk',age: 18};
const personProxy = new Proxy(person,{
get(target, key){
console.log(target, key); //{name: "zs", age: 18} "name"
return target[key].toUpperCase();
},
});
personProxy.name = 'zs';
console.log(personProxy); //Proxy {name: "zs", age: 18}
console.log(personProxy.name); //ZS
複製代碼
set()
set方法用來攔截某個屬性的賦值操做。
const person = {name: 'ghk',age: 18};
const personProxy = new Proxy(person,{
set(target, key, value){
if(typeof value === 'string'){
target[key] = value.trim();
}
}
});
personProxy.string = ' this is a test ';
console.log(personProxy.string); //THIS IS A TEST
複製代碼