ES6 迭代器(Iterator)和 for...of循環(huán)使用方法

一医增、什么是迭代器蜀漆?

生成器概念在Java谅河,Python等語(yǔ)言中都是具備的,ES6也添加到了JavaScript中。Iterator可以使我們不需要初始化集合绷耍,以及索引的變量吐限,而是使用迭代器對(duì)象的 next 方法,返回集合的下一項(xiàng)的值褂始,偏向程序化诸典。

迭代器是帶有特殊接口的對(duì)象。含有一個(gè)next()方法崎苗,調(diào)用返回一個(gè)包含兩個(gè)屬性的對(duì)象狐粱,分別是value和done,value表示當(dāng)前位置的值益缠,done表示是否迭代完脑奠,當(dāng)為true的時(shí)候,調(diào)用next就無效了幅慌。

ES5中遍歷集合通常都是 for循環(huán)宋欺,數(shù)組還有 forEach 方法,對(duì)象就是 for-in胰伍,ES6 中又添加了 MapSet齿诞,而迭代器可以統(tǒng)一處理所有集合數(shù)據(jù)的方法。迭代器是一個(gè)接口骂租,只要你這個(gè)數(shù)據(jù)結(jié)構(gòu)暴露了一個(gè)iterator的接口祷杈,那就可以完成迭代。ES6創(chuàng)造了一種新的遍歷命令for...of循環(huán)渗饮,Iterator接口主要供for...of消費(fèi)但汞。

二、如何使用迭代器互站?

1私蕾、默認(rèn) Iterator 接口

數(shù)據(jù)結(jié)構(gòu)只要部署了 Iterator 接口,我們就成這種數(shù)據(jù)結(jié)構(gòu)為“可遍歷”(Iterable)胡桃。ES6 規(guī)定踩叭,默認(rèn)的 Iterator 接口部署在數(shù)據(jù)結(jié)構(gòu)的 Symbol.iterator 屬性,或者說翠胰,一個(gè)數(shù)據(jù)結(jié)構(gòu)只要具有 Symbol.iterator 數(shù)據(jù)容贝,就可以認(rèn)為是“可遍歷的”(iterable)。

可以供 for...of 消費(fèi)的原生數(shù)據(jù)結(jié)構(gòu)

  • Array
  • Map
  • Set
  • String
  • TypedArray(一種通用的固定長(zhǎng)度緩沖區(qū)類型之景,允許讀取緩沖區(qū)中的二進(jìn)制數(shù)據(jù))
  • 函數(shù)中的 arguments 對(duì)象
  • NodeList 對(duì)象

可以看上面的原生數(shù)據(jù)結(jié)構(gòu)中并沒有對(duì)象(Object)斤富,為什么呢?
那是因?yàn)閷?duì)象屬性的遍歷先后順序是不確定的锻狗,需要開發(fā)者手動(dòng)指定茂缚。本質(zhì)上戏罢,遍歷器是一種線性處理,對(duì)于任何非線性的數(shù)據(jù)結(jié)構(gòu)脚囊,部署遍歷器接口就等于部署一種線性變換龟糕。
做如下處理,可以使對(duì)象供 for...of 消費(fèi):

// code1
function Obj(value) {
    this.value = value;
    this.next = null;
}
Obj.prototype[Symbol.iterator] = function() {
    var iterator = {
        next: next
    };
    var current = this;
    function next() {
        if (current) {
            var value = current.value;
            current = current.next;
            return {
                done: false,
                value: value
            };
        } else {
            return {
                done: true
            };
        }
    }
    return iterator;
}
var one = new Obj(1);
var two = new Obj(2);
var three = new Obj(3);
one.next = two;
two.next = three;
for (var i of one) {
    console.log(i);
}
// 1
// 2
// 3
2悔耘、調(diào)用 Iterator 接口的場(chǎng)合

(1) 解構(gòu)賦值

// code2
let set = new Set().add('a').add('b').add('c');
let [x,y] = set;
// x='a'; y='b'
let [first, ...rest] = set;
// first='a'; rest=['b','c'];

(2) 擴(kuò)展運(yùn)算符

// code3
// 例一
var str = 'hello';
[...str] // ['h','e','l','l','o']
// 例二
let arr = ['b', 'c'];
['a', ...arr, 'd']
// ['a', 'b', 'c', 'd']

(3)Generator 函數(shù)中的 yield* 表達(dá)式(下一章介紹)

// code4
let generator = function* () {
yield 1;
yield* [2,3,4];
yield 5;
};
var iterator = generator();
iterator.next() // { value: 1, done: false }
iterator.next() // { value: 2, done: false }
iterator.next() // { value: 3, done: false }
iterator.next() // { value: 4, done: false }
iterator.next() // { value: 5, done: false }
iterator.next() // { value: undefined, done: true }

(4)其它場(chǎng)合

  • for..of
  • Array.from
  • Map()讲岁、Set()、WeakMap()衬以、WeakSet()
  • Promise.all()
  • Promise.race()
3缓艳、for...of 循環(huán)的優(yōu)勢(shì)

先看看,數(shù)組 forEach 方法的缺點(diǎn):

// code5
myArray.forEach(function (value) {
  console.log(value);
});

這個(gè)寫法的問題在于看峻,無法中途跳出 forEach 循環(huán)阶淘,break 命令或 return 命令都不能生效

再看看互妓,對(duì)象 for...in 的循環(huán)的缺點(diǎn):

for (var index in myArray) {
  console.log(myArray[index]);
};
  • 數(shù)組的鍵名是數(shù)字溪窒,但是 for...in 循環(huán)是以字符串作為鍵名,“0”冯勉、“1”澈蚌、“2”等。
  • for...in 循環(huán)不僅可以遍歷數(shù)字鍵名灼狰,還會(huì)遍歷手動(dòng)添加的期推薦宛瞄,甚至包括原型鏈上的鍵。
  • 某些情況下交胚,for...in 循環(huán)會(huì)議任意順序遍歷鍵名
  • for...in 遍歷主要是為遍歷對(duì)象而設(shè)計(jì)的份汗,不適用于遍歷數(shù)組

那么,for...of 有哪些顯著的優(yōu)點(diǎn)呢蝴簇?

  • 有著同 for...in 一樣的簡(jiǎn)潔語(yǔ)法裸影,但是沒有 for...in 那些缺點(diǎn)
  • 不同于 forEach 方法,它可以與 break军熏、continue 和 return 配合使用
  • 提供了遍歷所有數(shù)據(jù)結(jié)構(gòu)的統(tǒng)一操作接口
for (var n of fibonacci) {
  if (n > 1000) {
    break;
    console.log(n);
  }
}
4、各數(shù)據(jù)類型如何使用 for...of 循環(huán)卷扮?

(1)數(shù)組

  • for...of 循環(huán)允許遍歷數(shù)組獲得鍵值
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ù)字索引的值
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"
}

(2)Map 和 Set 結(jié)構(gòu)

var engines = new Set(["Gecko", "Trident", "Webkit", "Webkit"]);
for (var e of engines) {
    console.log(e);
}
// Gecko
// Trident
// Webkit
var es6 = new Map();
es6.set("edition", 6);
es6.set("committee", "TC39");
es6.set("standard", "ECMA-262");
for (var [name, value] of es6) {
    console.log(name + ": " + value);
}
// edition: 6
// committee: TC39
// standard: ECMA-262

由上述的代碼可以看出,for...of 循環(huán)遍歷Map 和 Set 結(jié)構(gòu)時(shí)晤锹,遍歷的順序是按照各個(gè)成員被添加進(jìn)數(shù)據(jù)結(jié)構(gòu)的順序摩幔,Set 結(jié)構(gòu)遍歷時(shí)返回的是一個(gè)值,而 Map 結(jié)構(gòu)遍歷時(shí)返回的是一個(gè)數(shù)組鞭铆,該數(shù)組的兩個(gè)成員分別為當(dāng)前 Map 成員的鍵名和鍵值或衡。

(3)類數(shù)組對(duì)象

  • 字符串
// 普通的字符串遍歷
let str = "yuan";
for (let s of str) {
  console.log(s); // y u a n
}

// 遍歷含有 32位 utf-16字符的字符串
for (let x of 'a\uD83D\uDC0A') {
  console.log(x);
}
// 'a'
// '\uD83D\uDC0A'
  • DOM NodeList 對(duì)象
let paras = document.querySelectorAll("p");
for (let p of paras) {
  p.classList.add("test");
}
  • arguments 對(duì)象
function printArgs() {
  for (let x of arguments) {
    console.log(x);
  }
}
printArgs("a", "n");
// "a"
// "n"
  • 沒有 Iterator 接口類數(shù)組對(duì)象的遍歷處理
    借用 Array.from 方法處理
let arrayLike = {
    length: 2,
    0 : 'a',
    1 : 'b'
};
// 報(bào)錯(cuò)
for (let x of arrayLike) {
    console.log(x);
}
// 正確
for (let x of Array.from(arrayLike)) {
    console.log(x);
}

(4)對(duì)象
對(duì)于普通對(duì)象焦影,不能直接使用 for...of 遍歷,否則會(huì)報(bào)錯(cuò)封断,必須部署了 Iterator 接口才能使用斯辰。如下兩種方法部署:

// 方法一:使用 Object.keys 方法講對(duì)象的鍵名生成一個(gè)數(shù)組
for (var key of Object.keys(someObject)) {
  console.log(key + ": " + someObject[key]);
}

// 方法二:使用Generator 函數(shù)將對(duì)象重新包裝一下
function * entries(obj) {
    for (let key of Object.keys(obj)) {
        yield[key, obj[key]];
    }
}
for (let[key, value] of entries(obj)) {
    console.log(key, "->", value);
}
// a -> 1
// b -> 2
// c -> 3

三、迭代器應(yīng)用實(shí)例

1坡疼、斐波那契數(shù)列
下面我們就使用迭代器來自定義自己的一個(gè)斐波那契數(shù)列組彬呻,我們直到斐波那契數(shù)列有兩個(gè)運(yùn)行前提,第一個(gè)前提是初始化的前兩個(gè)數(shù)字為0柄瑰,1,第二個(gè)前提是將來的每一個(gè)值都是前兩個(gè)值的和闸氮。這樣我們的目標(biāo)就是每次都迭代輸出一個(gè)新的值。

var it = { [Symbol.iterator]() {
        return this
    },
    n1: 0,
    n2: 1,
    next() {
        let temp1 = this.n1,
        temp2 = this.n2;
        [this.n1, this.n2] = [temp2, temp1 + temp2]
        return {
            value: temp1,
            done: false
        }
    }
}

for (var i = 0; i < 20; i++) {
    console.log(it.next())
}

// 
    "value": 0,
    "done": false
} {
    "value": 1,
    "done": false
} {
    "value": 1,
    "done": false
} {
    "value": 2,
    "done": false
} {
    "value": 3,
    "done": false
} {
    "value": 5,
    "done": false
}... {
    "value": 2584,
    "done": false
} {
    "value": 4181,
    "done": false
}

2教沾、任務(wù)隊(duì)列迭代器
我們可以定義一個(gè)任務(wù)隊(duì)列蒲跨,該隊(duì)列初始化時(shí)為空,我們將待處理的任務(wù)傳遞后授翻,傳入數(shù)據(jù)進(jìn)行處理或悲。這樣第一次傳遞的數(shù)據(jù)只會(huì)被任務(wù)1處理,第二次傳遞的只會(huì)被任務(wù)2處理… 代碼如下:

var Task = {
    actions: [],
    [Symbol.iterator]() {
        var steps = this.actions.slice();
        return { [Symbol.iterator]() {
                return this;
            },
            next(...args) {
                if (steps.length > 0) {
                    let res = steps.shift()(...args);
                    return {
                        value: res,
                        done: false
                    }
                } else {
                    return {
                        done: true
                    }
                }
            }
        }
    }
}

Task.actions.push(function task1(...args) {
    console.log("任務(wù)一:相乘") return args.reduce(function(x, y) {
        return x * y
    })
},
function task2(...args) {
    console.log("任務(wù)二:相加") return args.reduce(function(x, y) {
        return x + y
    }) * 2
},
function task3(...args) {
    console.log("任務(wù)三:相減") return args.reduce(function(x, y) {
        return x - y
    })
});

var it = Task[Symbol.iterator]();
console.log(it.next(10, 100, 2));
console.log(it.next(20, 50, 100)) console.log(it.next(10, 2, 1))
 // 
任務(wù)一:相乘 {
    "value": 2000,
    "done": false
}任務(wù)二:相加 {
    "value": 340,
    "done": false
}任務(wù)三:相減 {
    "value": 7,
    "done": false
}

**3藏姐、延遲執(zhí)行
假設(shè)我們有一個(gè)數(shù)據(jù)表隆箩,我們想按大小順序依次的獲取數(shù)據(jù),但是我們又不想提前給他排序羔杨,有可能我們根本就不去使用它捌臊,所以我們可以在第一次使用的時(shí)候再排序,做到延遲執(zhí)行代碼:

var table = {
    "d": 1,
    "b": 4,
    "c": 12,
    "a": 12
}
table[Symbol.iterator] = function() {
    var _this = this;
    var keys = null;
    var index = 0;

    return {
        next: function() {
            if (keys === null) {
                keys = Object.keys(_this).sort();
            }

            return {
                value: keys[index],
                done: index++>keys.length
            };
        }
    }
}

for (var a of table) {
    console.log(a)
} 
// a b c d

戳我博客

參考:

章節(jié)目錄

1兜材、ES6中啥是塊級(jí)作用域理澎?運(yùn)用在哪些地方?
2曙寡、ES6中使用解構(gòu)賦值能帶給我們什么糠爬?
3、ES6字符串?dāng)U展增加了哪些举庶?
4执隧、ES6對(duì)正則做了哪些擴(kuò)展?
5户侥、ES6數(shù)值多了哪些擴(kuò)展镀琉?
6、ES6函數(shù)擴(kuò)展(箭頭函數(shù))
7蕊唐、ES6 數(shù)組給我們帶來哪些操作便利屋摔?
8、ES6 對(duì)象擴(kuò)展
9替梨、Symbol 數(shù)據(jù)類型在 ES6 中起什么作用钓试?
10装黑、Map 和 Set 兩數(shù)據(jù)結(jié)構(gòu)在ES6的作用
11、ES6 中的Proxy 和 Reflect 到底是什么鬼弓熏?
12恋谭、從 Promise 開始踏入異步操作之旅
13、ES6 迭代器(Iterator)和 for...of循環(huán)使用方法
14硝烂、ES6 異步進(jìn)階第二步:Generator 函數(shù)
15箕别、JavaScript 異步操作進(jìn)階第三步:async 函數(shù)
16、ES6 構(gòu)造函數(shù)語(yǔ)法糖:class 類

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末滞谢,一起剝皮案震驚了整個(gè)濱河市串稀,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌狮杨,老刑警劉巖母截,帶你破解...
    沈念sama閱讀 211,265評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異橄教,居然都是意外死亡清寇,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門护蝶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來华烟,“玉大人,你說我怎么就攤上這事持灰】梗” “怎么了?”我有些...
    開封第一講書人閱讀 156,852評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵堤魁,是天一觀的道長(zhǎng)喂链。 經(jīng)常有香客問我,道長(zhǎng)妥泉,這世上最難降的妖魔是什么椭微? 我笑而不...
    開封第一講書人閱讀 56,408評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮盲链,結(jié)果婚禮上蝇率,老公的妹妹穿的比我還像新娘。我一直安慰自己刽沾,他們只是感情好本慕,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評(píng)論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著悠轩,像睡著了一般。 火紅的嫁衣襯著肌膚如雪攻泼。 梳的紋絲不亂的頭發(fā)上火架,一...
    開封第一講書人閱讀 49,772評(píng)論 1 290
  • 那天鉴象,我揣著相機(jī)與錄音,去河邊找鬼何鸡。 笑死纺弊,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的骡男。 我是一名探鬼主播淆游,決...
    沈念sama閱讀 38,921評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼隔盛!你這毒婦竟也來了犹菱?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,688評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤吮炕,失蹤者是張志新(化名)和其女友劉穎腊脱,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體龙亲,經(jīng)...
    沈念sama閱讀 44,130評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡陕凹,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了鳄炉。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片杜耙。...
    茶點(diǎn)故事閱讀 38,617評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖拂盯,靈堂內(nèi)的尸體忽然破棺而出佑女,到底是詐尸還是另有隱情,我是刑警寧澤磕仅,帶...
    沈念sama閱讀 34,276評(píng)論 4 329
  • 正文 年R本政府宣布珊豹,位于F島的核電站,受9級(jí)特大地震影響榕订,放射性物質(zhì)發(fā)生泄漏店茶。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評(píng)論 3 312
  • 文/蒙蒙 一劫恒、第九天 我趴在偏房一處隱蔽的房頂上張望贩幻。 院中可真熱鬧,春花似錦两嘴、人聲如沸丛楚。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)趣些。三九已至,卻和暖如春贰您,著一層夾襖步出監(jiān)牢的瞬間坏平,已是汗流浹背拢操。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評(píng)論 1 265
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留舶替,地道東北人令境。 一個(gè)月前我還...
    沈念sama閱讀 46,315評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像顾瞪,于是被迫代替她去往敵國(guó)和親舔庶。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容