ES6 迭代器(Iterator)和 for.of循環(huán)使用方法學(xué)習(xí)(總結(jié))
一、什么是迭代器?
生成器概念在Java,Python等語言中都是具備的,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 中又添加了 Map 和 Set,而迭代器可以統(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)潔語法,但是沒有 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é)語
本章內(nèi)容,重點(diǎn)是明白 Iterator 接口的機(jī)制,以及 for...of 循環(huán)的使用方法。下一章介紹生成器函數(shù) Generator 函數(shù)。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Javascript實(shí)現(xiàn)可旋轉(zhuǎn)的圓圈實(shí)例代碼
這篇文章主要介紹了Javascript實(shí)現(xiàn)可旋轉(zhuǎn)的圓圈實(shí)例代碼,可實(shí)現(xiàn)圓圈的旋轉(zhuǎn)與運(yùn)動(dòng)效果,涉及javascript動(dòng)態(tài)操作頁面元素屬性及進(jìn)行相應(yīng)數(shù)學(xué)運(yùn)算的技巧,需要的朋友可以參考下2015-08-08JS獲取當(dāng)前時(shí)間實(shí)例代碼(年月日時(shí)分秒)
在javascript中,可以使用Date對(duì)象中的Date()方法來獲取當(dāng)前時(shí)間,下面這篇文章主要給大家介紹了關(guān)于JS獲取當(dāng)前時(shí)間(年月日時(shí)分秒)的相關(guān)資料,需要的朋友可以參考下2022-09-09js實(shí)現(xiàn)鼠標(biāo)懸浮給圖片加邊框的方法
這篇文章主要介紹了js實(shí)現(xiàn)鼠標(biāo)懸浮給圖片加邊框的方法,涉及jquery.insetborder.js中borderEffect方法的使用技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-01-01Layui 數(shù)據(jù)表格批量刪除和多條件搜索的實(shí)例
今天小編就為大家分享一篇Layui 數(shù)據(jù)表格批量刪除和多條件搜索的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2019-09-09解讀IE和firefox下JScript和HREF的執(zhí)行順序
2008-01-01JS判斷文本框內(nèi)容改變事件的簡(jiǎn)單實(shí)例
本篇文章主要是對(duì)JS判斷文本框內(nèi)容改變事件的簡(jiǎn)單實(shí)例進(jìn)行了詳細(xì)的介紹,需要的朋友可以過來參考下,希望對(duì)大家有所幫助2014-03-03使用hasOwnProperty時(shí)報(bào)錯(cuò)的解決方法
hasOwnProperty這個(gè)方法是用來查找一個(gè)對(duì)象是否有某個(gè)屬性,且查找的屬性必須是對(duì)象本身的一個(gè)成員,但是不會(huì)去查找對(duì)象的原型鏈,文中介紹了使用示例代碼及使用時(shí)可能會(huì)遇到的問題,對(duì)hasOwnProperty報(bào)錯(cuò)原因分析及解決方法感興趣的朋友一起看看吧2024-01-01