重學ES6之出來混早晚要還的(六)

本系列博客爲ES6基礎語法的使用及總結,若有錯誤,歡迎指正。
重學ES6之出來混早晚要還的(六)主要包括 GeneratorSet/MapProxy等。面試

其餘筆記:
重學ES6之出來混早晚要還的(一)
重學ES6之出來混早晚要還的(二)
重學ES6之出來混早晚要還的(三)
重學ES6之出來混早晚要還的(四)
重學ES6之出來混早晚要還的(五)
數據類型的轉換/判斷/比較ajax

異步編程

Javascript語言的執行環境是"單線程"(single thread)。編程

所謂"單線程",就是指一次只能完成一件任務。若是有多個任務,就必須排隊,前面一個任務完成,再執行後面一個任務,以此類推。

Javascript語言將任務的執行模式分紅兩種:同步(Synchronous)和異步(Asynchronous)。segmentfault

"同步模式"就是上一段的模式,後一個任務等待前一個任務結束,而後再執行,程序的執行順序與任務的排列順序是一致的、同步的;
"異步模式"則徹底不一樣,每個任務有一個或多個回調函數(callback),前一個任務結束後,不是執行後一個任務,而是執行回調函數,後一個任務則是不等前一個任務結束就執行,因此程序的執行順序與任務的排列順序是不一致的、異步的。

"異步模式"很是重要。在瀏覽器端,耗時很長的操做都應該異步執行,避免瀏覽器失去響應,最好的例子就是Ajax操做。在服務器端,"異步模式"甚至是惟一的模式,由於執行環境是單線程的,若是容許同步執行全部http請求,服務器性能會急劇降低,很快就會失去響應。數組

異步編程的方法

經過回調函數

優勢是簡單,容易理解和部署;缺點是不利於代碼的閱讀和維護,各個部分之間高度耦合(Coupling),流程混亂,並且每一個任務只能指定一個回調函數。瀏覽器

經過事件監聽

任務的執行不取決於代碼的順序,而取決於某個事件是否發生。 服務器

經過事件監聽,能夠綁定多個事件,每一個事件能夠指定多個回調函數,並且能夠"去耦合"(Decoupling),有利於實現模塊化。缺點是整個程序都要變成事件驅動型,運行流程會變得很不清晰。數據結構

採用發佈/訂閱模式

咱們假定,存在一個"信號中心",某個任務執行完成,就向信號中心"發佈"(publish)一個信號,其餘任務能夠向信號中心"訂閱"(subscribe)這個信號,從而知道何時本身能夠開始執行。這就叫作"發佈/訂閱模式"(publish-subscribe pattern),又稱"觀察者模式"(observer pattern)。異步

這種方法的性質與"事件監聽"相似,可是明顯優於後者。由於咱們能夠經過查看"消息中心",瞭解存在多少信號、每一個信號有多少訂閱者,從而監控程序的運行。模塊化

Promise

很少贅述,這裏有說

Generator

Generator 函數是 ES6 提供的一種異步編程解決方案

顧名思義,它是一個生成器,它也是一個狀態機,內部擁有值及相關的狀態。生成器返回一個迭代器Iterator對象,咱們能夠經過這個迭代器,手動地遍歷相關的值、狀態,保證正確的執行順序。

1.用法

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.Generator 函數遍歷數組

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.Generator函數和普通函數區別

3.1 調用Generator函數後,不管函數有沒有返回值,都會返回一個迭代器對象
3.2 調用Generator函數後,函數中封裝的代碼不會當即被執行

4.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.Generator函數應用場景

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接口。

6.for...of

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屬性爲truefor...of循環就會停止,且不包含該返回對象,因此上面代碼的return語句返回的6,不包括在for...of循環之中。

Set數據結構

ES6提供了新的數據結構Set。它相似於數組,可是成員的值都是惟一的,沒有重複的值。

與數組的不一樣之處:Set數據結構不能經過索引來獲取元素。

1.用法

Set自己是一個構造函數,用來生成Set數據結構。

格式:new Set([iterable]);

let list = new Set();
console.log(list);

let color = new Set(['red', 'yellow', 'green']);
console.log(color);

2.Set數據結構經常使用方法

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.Set是能夠遍歷的

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);
})

4.利用Set進行數組去重

面試的時候常常有問:用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

WeakSet結構與Set相似,也是不重複的值的集合。

WeakSet結構有如下三個方法。

.add(value):向WeakSet實例添加一個新成員。
.delete(value):清除WeakSet實例的指定成員。
.has(value):返回一個布爾值,表示某個值是否在WeakSet實例之中。

WeakSet與Set的區別

  • 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
  • WeakSet是不可遍歷的。
for(let key of person){
  console.log(key); //person is not iterable
}
  • WeakSet沒有size屬性,沒有辦法遍歷它的成員。
  • WeakSet沒有clear()方法,可是具備本身清除的做用,避免內存泄漏。
let lucy = {region:'America',age: 18};
let lily = {region:'Canada',age: 20};

let person = new WeakSet([lucy,lily]);
console.log(person);


將lily對象置爲null以後,先後兩次打印的都是隻有lucy對象

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數據結構

Map 對象保存鍵值對。任何值(對象或者原始值) 均可以做爲一個鍵或一個值。

ES6提供的Map數據結構。它相似於對象,也是鍵值對的集合,可是「鍵」的範圍不限於字符串,各類類型的值(包括對象)均可以看成鍵。也就是說,Object結構提供了「字符串—值」的對應,Map結構提供了「值—值」的對應,是一種更完善的Hash結構實現。若是你須要「鍵值對」的數據結構,Map比Object更合適。

1.用法

  • 語法:new Map([iterable])
  • 參數:Iterable 能夠是一個數組或者其餘iterable對象,其元素爲鍵值對(兩個元素的數組,例如: [[ 1, 'one' ],[ 2, 'two' ]])。 每一個鍵值對都會添加到新的 Map。null 會被當作 undefined。
const people = new Map();
people.set('lucy',18);
people.set('lily',20);
console.log(people);

2.Map數據結構經常使用方法

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方法清除全部成員,沒有返回值。

3.Map是能夠遍歷的

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);
})

4.Map和Object的區別

Map的鍵(即key)能夠是任意類型值,能夠是一個對象,能夠是一個函數等

  • 一個Object的鍵只能是字符串或者Symbols,但一個Map的鍵能夠是任意值,包括函數、對象、基本類型。
  • Map 中的鍵值是有序的,而添加到對象中的鍵則不是。所以,當對它進行遍歷時,Map 對象是按插入的順序返回鍵值。
const people = new Map();
people.set('lucy',18);
people.set('lily',20);
people.set({},3);
console.log(people);

5.與其餘數據結構的互相轉換

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);


5.2 數組轉爲Map
將數組轉入Map構造函數,就能夠轉爲Map。

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

WeakMap與Map的區別在於:

  • WeakMap它只接受對象做爲鍵名(null除外),不接受其餘類型的值做爲鍵名
  • WeakMap不能遍歷
  • WeakMap的元素在其餘地方沒有被引用時,垃圾回收機制會自動清理掉該元素

WeakMap與Map在API上的區別主要是兩個:

  • 沒有遍歷操做(即沒有key()values()entries()方法),也沒有size屬性;
  • 沒法清空,即不支持clear方法。這與WeakMap的鍵不被計入引用、被垃圾回收機制忽略有關。所以,WeakMap只有四個方法可用:get()set()has()delete()

Proxy

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
相關文章
相關標籤/搜索