在ES6的教程里先說了Map和Set再說Iteratator,盡管我們理解遍歷的意思是什么,但是學習Iterator的原理和結構有助于我們進一步理解有應用到遍歷器的語法。
Iterator原理
Iterator 的遍歷過程是這樣的。
(1)創(chuàng)建一個指針對象,指向當前數(shù)據(jù)結構的起始位置。也就是說,遍歷器對象本質(zhì)上,就是一個指針對象。
(2)第一次調(diào)用指針對象的next方法,可以將指針指向數(shù)據(jù)結構的第一個成員。
(3)第二次調(diào)用指針對象的next方法,指針就指向數(shù)據(jù)結構的第二個成員。
(4)不斷調(diào)用指針對象的next方法,直到它指向數(shù)據(jù)結構的結束位置。
默認的Iterator
存在這樣的一個默認接口:如果一個數(shù)據(jù)結構擁有屬性Symbol.iterator,則這個數(shù)據(jù)結構是可遍歷的。屬性Symbol.iterator對應一個函數(shù),該函數(shù)規(guī)定了遍歷的規(guī)則。執(zhí)行這個函數(shù),就會返回一個遍歷器。
const obj = {
[Symbol.iterator] : function () {
return {
next: function () {
return {
value: 1,
done: true
};
}
};
}
};
該例子中常量obj是可遍歷的,因為它擁有Symbol.iterator屬性,該屬性對應一個函數(shù),函數(shù)返回一個擁有next屬性(對應一個返回value和done的函數(shù))的對象。所以當我們調(diào)用obj.next()的時候,就會返回對應的value和done的值。
原生Iterator
以下是原生擁有Iterator接口的數(shù)據(jù)結構:
Array
Map
Set
String
TypedArray
函數(shù)的 arguments 對象
NodeList 對象
對于原生部署 Iterator 接口的數(shù)據(jù)結構,不用自己寫遍歷器生成函數(shù),for...of循環(huán)會自動遍歷它們。除此之外,其他數(shù)據(jù)結構(主要是對象)的 Iterator 接口,都需要自己在Symbol.iterator屬性上面部署(部署Symbol.iterator的函數(shù),還有next函數(shù)),這樣才會被for...of循環(huán)遍歷。部署還有一種取巧的方法,就是類似數(shù)組的對象可以直接借用Array.prototype[Symbol.iterator]。
return方法和throw方法
return方法和throw方法在部署中是可選的。
return方法針對以下情況執(zhí)行:
在for...of循環(huán)中,
1.出錯 2.break語句 3.continue語句
for...of和for...in
for...in循環(huán),只能獲得對象的鍵名,不能直接獲取鍵值。ES6 提供for...of循環(huán),允許遍歷獲得鍵值。
var arr = ['a', 'b', 'c', 'd'];
for (let a in arr) {
console.log(a); // 0 1 2 3
}
for (let a of arr) {
console.log(a); // a b c d
}
for...of循環(huán)調(diào)用遍歷器接口,數(shù)組的遍歷器接口只返回具有數(shù)字索引的屬性。這一點跟for...in循環(huán)也不一樣。
let arr = [3, 5, 7];
arr.foo = 'hello';
for (let i in arr) {
console.log(i); // "0", "1", "2", "foo"
}
for (let i of arr) {
console.log(i); // "3", "5", "7"
}
iterator的使用
iterator主要應用于Generator,但在此之前還要溫習一下具備原生接口的Set和Map。
Set和weakset
Set
創(chuàng)建Set
const set = new Set()
也可以傳入數(shù)組初始化set
const set = new Set([1,2,3,4])
set里面沒有重復的值,傳入重復的值會被排除掉。
Set 實例的屬性和方法
Set 結構的實例有以下屬性。
Set.prototype.constructor:構造函數(shù),默認就是Set函數(shù)。
Set.prototype.size:返回Set實例的成員總數(shù)。
操作方法:
add(value):添加某個值,返回 Set 結構本身。
delete(value):刪除某個值,返回一個布爾值,表示刪除是否成功。
has(value):返回一個布爾值,表示該值是否為Set的成員。
clear():清除所有成員,沒有返回值。
遍歷方法:
keys():返回鍵名的遍歷器
values():返回鍵值的遍歷器
entries():返回鍵值對的遍歷器
forEach():使用回調(diào)函數(shù)遍歷每個成員
Weakset
const ws = new WeakSet();
WeakSet 可以接受一個數(shù)組或類似數(shù)組的對象作為參數(shù)。(實際上,任何具有 Iterable 接口的對象,都可以作為 WeakSet 的參數(shù)。)該數(shù)組的所有成員,都會自動成為 WeakSet 實例對象的成員。
WeakSet.prototype.add(value):向 WeakSet 實例添加一個新成員。
WeakSet.prototype.delete(value):清除 WeakSet 實例的指定成員。
WeakSet.prototype.has(value):返回一個布爾值,表示某個值是否在
WeakSet 沒有size屬性,沒有辦法遍歷它的成員。
Map和weakmap
Map
Map是一種具有鍵值對的數(shù)據(jù)結構,相對于Object來說,它的優(yōu)點是鍵的范圍不限于字符串,各種類型的值(包括對象)都可以當作鍵。也就是說,Object 結構提供了“字符串-值”的對應,Map 結構提供了“值-值”的對應。
const m = new Map();
const o = {p: 'Hello World'};
//Map { { p: 'hello world' } => 'content' }
m.set(o, 'content')
m.get(o) // "content"
m.has(o) // true
m.delete(o) // true
m.has(o) // false
賦值應用:任何具有 Iterator 接口、且每個成員都是一個雙元素的數(shù)組的數(shù)據(jù)結構(詳見《Iterator》一章)都可以當作Map構造函數(shù)的參數(shù)。這就是說,Set和Map都可以用來生成新的 Map。
const set = new Set([
['foo', 1],
['bar', 2]
]);
const m1 = new Map(set);
m1.get('foo') // 1
const m2 = new Map([['baz', 3]]);
const m3 = new Map(m2);
m3.get('baz') // 3
如果對同一個鍵多次賦值,后面的值將覆蓋前面的值。
const map = new Map();
map
.set(1, 'aaa')
.set(1, 'bbb');
map.get(1) // "bbb"
上面代碼對鍵1連續(xù)賦值兩次,后一次的值覆蓋前一次的值。
如果讀取一個未知的鍵,則返回undefined。
new Map().get('asfddfsasadf')
// undefined
Map 的鍵實際上是跟內(nèi)存地址綁定的,只要內(nèi)存地址不一樣,就視為兩個鍵。如果 Map 的鍵是一個簡單類型的值(數(shù)字、字符串、布爾值),則只要兩個值嚴格相等,Map 將其視為一個鍵,比如0和-0就是一個鍵,布爾值true和字符串true則是兩個不同的鍵。另外,undefined和null也是兩個不同的鍵。雖然NaN不嚴格相等于自身,但 Map 將其視為同一個鍵。
實例屬性和方法
1.size屬性返回 Map 結構的成員總數(shù)。
2.set(key, value) set方法返回的是當前的Map對象,因此可以采用鏈式寫法。
3.get(key) 如果找不到key,返回undefined。
4.has(key)
5.delete(key)刪除某個鍵,返回true。如果刪除失敗,返回false
6.clear()清除所有成員,沒有返回值。
遍歷方法:
keys():返回鍵名的遍歷器。
values():返回鍵值的遍歷器。
entries():返回所有成員的遍歷器。
forEach():遍歷 Map 的所有成員。
Weakmap
首先,WeakMap只接受對象作為鍵名(null除外),不接受其他類型的值作為鍵名。
其次,WeakMap的鍵名所指向的對象,不計入垃圾回收機制。
只要所引用的對象的其他引用都被清除,垃圾回收機制就會釋放該對象所占用的內(nèi)存。也就是說,一旦不再需要,WeakMap 里面的鍵名對象和所對應的鍵值對會自動消失,不用手動刪除引用。
垃圾回收機制以后再寫一篇筆記說明。
WeakMap只有四個方法可用:get()、set()、has()、delete()。
沒有遍歷機制和size屬性。
Generator
終于講到了Generator。
Generator可以看作是一個封裝了多個內(nèi)部狀態(tài)的函數(shù),這些狀態(tài)是可遍歷的。執(zhí)行Generator返回一個遍歷器。
形式上,Generator 函數(shù)是一個普通函數(shù),但是有兩個特征。一是,function關鍵字與函數(shù)名之間有一個星號;二是,函數(shù)體內(nèi)部使用yield表達式,定義不同的內(nèi)部狀態(tài)
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
var hw = helloWorldGenerator();
hw.next()
// { value: 'hello', done: false }
hw.next()
// { value: 'world', done: false }
hw.next()
// { value: 'ending', done: true }
hw.next()
// { value: undefined, done: true }
Generator 函數(shù)是分段執(zhí)行的,yield表達式是暫停執(zhí)行的標記,而next方法可以恢復執(zhí)行。
Generator 函數(shù)可以不用yield表達式,這時就變成了一個單純的暫緩執(zhí)行函數(shù)。
function* f() {
console.log('執(zhí)行了!')
}
var generator = f();
setTimeout(function () {
generator.next()
}, 2000);
注意的地方:
1.yield表達式只能用在 Generator 函數(shù)里面,用在其他地方都會報錯。
2.yield表達式如果用在另一個表達式之中,必須放在圓括號里面。
3.yield表達式用作函數(shù)參數(shù)或放在賦值表達式的右邊,可以不加括號。
next()帶參數(shù)
next方法的參數(shù)表示上一個yield表達式的返回值(所以在第一次使用next方法時,傳遞參數(shù)是無效的)
function* foo(x) {
var y = 2 * (yield (x + 1));
var z = yield (y / 3);
return (x + y + z);
}
var a = foo(5);
a.next() // Object{value:6, done:false}
a.next() // Object{value:NaN, done:false}
a.next() // Object{value:NaN, done:true}
var b = foo(5);
b.next() // { value:6, done:false }
b.next(12) // { value:8, done:false }
b.next(13) // { value:42, done:true }
for...of
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
從例子可以看出,當done為true時,for..of停止遍歷并且不返回當前值。
除了for...of循環(huán)以外,擴展運算符(...)、解構賦值和Array.from方法內(nèi)部調(diào)用的,都是遍歷器接口。這意味著,它們都可以將 Generator 函數(shù)返回的 Iterator 對象,作為參數(shù)。
function* numbers () {
yield 1
yield 2
return 3
yield 4
}
// 擴展運算符
[...numbers()] // [1, 2]
// Array.from 方法
Array.from(numbers()) // [1, 2]
// 解構賦值
let [x, y] = numbers();
x // 1
y // 2
// for...of 循環(huán)
for (let n of numbers()) {
console.log(n)
}
// 1
// 2
return()和throw()
throw方法可以接受一個參數(shù),該參數(shù)會被catch語句接收。
在generator內(nèi)如果有try...catch,那么generator的錯誤會被捕捉到一次。當內(nèi)部已經(jīng)捕獲到錯誤,后繼的錯誤就由外部處理或者直接中斷程序。
throw方法被捕獲以后,會附帶執(zhí)行下一條yield表達式。也就是說,會附帶執(zhí)行一次next方法。
return方法,可以返回給定的值,并且終結遍歷 Generator 函數(shù)。如果return方法調(diào)用時,不提供參數(shù),則返回值的value屬性為undefined。
generator的this指向哪里
如果不作任何處理,在generator實例內(nèi)定義一個this.a=1,this指向的是全局。
如果希望generator可以兼?zhèn)鋘ext方法和正常的this指向,有兩個方法
1,使用call或者apply綁定一個空對象
2,綁定generator函數(shù)的prototype