這篇文章跟大家介紹的內容是關於遍歷器Iterator存取資料集合的統一介面的方法,有著一定的參考價值,有需要的朋友可以參考一下。
遍歷器Iterator
是ES6為存取資料集合提供的統一介面。任何內部部署了遍歷器介面的資料集合,對於使用者來說,都可以使用相同方式取得到對應的資料結構。如果使用的是最新版Chrome
瀏覽器,那麼你要知道——我們所熟悉的數組小姐,已悄悄的打開了另一扇可抵達她心扉的小徑。
某個資料集合部署了Iterator
接口,是指其Symbol.iterator
屬性指向一個能傳回Iterator
介面的函數。任何預設使用遍歷器存取資料集合的方法,都會呼叫此屬性以獲得遍歷器對象,再按照設定的順序依次存取該資料結構的成員(關於Symbol.iterator
請看最後一節的延伸閱讀)。例如原生數組的遍歷器為[][Symbol.iterator]
,也可以直接透過其建構子的原型取得Array.prototype[Symbol.iterator]
。
呼叫Iterator
介面會傳回一個新的遍歷器物件(指針對象)。
物件中必然有next
方法,用來存取下一個資料成員。指標初始時指向目前資料結構的起始位置。
第一次呼叫物件的next
方法,指標指向資料結構的第一個成員。
第二次呼叫物件的next
方法,指標指向資料結構的第二個成員。
不斷的呼叫物件的next
方法,直到它指向資料結構的結束位置。
每次呼叫next
方法,都會傳回相同的資料結構:{ value, done }
。
其中value
表示目前指向成員的值,沒有則為undefined
。
其中done
是一個布林值,表示遍歷是否結束,結束為true
,否則false
。
遍歷器介面的標準十分簡潔,不提供諸如:操作內部指標、判斷是否有值等等方法。只需要一直不斷的呼叫next
方法,當done
為false
時取得當時的value
,done
為true
時停止即可。第一次接觸遍歷器的行為模式是在2016的冬天,那時底蘊不夠雞毛也沒長全,理解不了簡潔性的適用與強大。直到現在——在即將打包被迫離開公司的前夕才驀然的覺醒。多麼痛的領悟。
let iterator = [1, 2, 3][Symbol.iterator](); console.log( iterator.next() ); // {value: 1, done: false} console.log( iterator.next() ); // {value: 2, done: false} console.log( iterator.next() ); // {value: 3, done: false} console.log( iterator.next() ); // {value: undefined, done: true}
面向不同的資料結構,有不同的遍歷器實作方法,我們簡單的實作下數組的遍歷器方法。
let res = null; let iterator = myIterator([3, 7]); console.log( iterator.next() ); // {value: 3, done: false} console.log( iterator.next() ); // {value: 7, done: false} console.log( iterator.next() ); // {value: undefined, done: true} function myIterator(array = []) { let index = 0; return { next() { return index < array.length ? { value: array[index++], done: false } : { value: undefined, done: true }; } }; }
除了為遍歷器物件部署next
方法,還可以有return
和throw
方法。其中return
方法會在提前退出for of
迴圈時(通常是因為出錯,或觸發了break
語句)被呼叫。而throw
方法主要是配合Generator
函數使用,一般的遍歷器物件用不到這個方法,所以不予介紹。
let obj = { [Symbol.iterator]() { let index = 0; let array = [1, 2, 3]; return { next() { return index < array.length ? { value: array[index++], done: false } : { value: undefined, done: true }; }, return() { console.log('Trigger return.'); return {}; } }; } }; for (let v of obj) { console.log(v); // 打印出:1, 2, 3,没触发 return 函数。 } for (let v of obj) { if (v === 2) break; console.log(v); // 打印出:1,之后触发 return 函数。 } for (let v of obj) { if (v === 3) break; console.log(v); // 打印出:1, 2,之后触发 return 函数。 } for (let v of obj) { if (v === 4) break; console.log(v); // 打印出:1, 2, 3,没触发 return 函数。 } for (let v of obj) { if (v === 2) throw Error('error'); console.log(v); // 打印出:1,之后触发 return 函数,并报错停止执行。 }
原生預設持有遍歷器介面的資料結構有:
基本型別:Array
, Set
, Map
(四個基本資料集合:Array
, Object
, Set
# 和Map
)。
類別陣列物件:arguments
, NodeList
, String
。
let iterator = '123'[Symbol.iterator](); console.log( iterator.next() ); // {value: "1", done: false} console.log( iterator.next() ); // {value: "2", done: false} console.log( iterator.next() ); // {value: "3", done: false} console.log( iterator.next() ); // {value: undefined, done: true}
遍歷器與先前的遍歷方法
一個資料集合擁有遍歷器接口,並不意味著所有遍歷它的方法都是使用此接口。實際上,只有ES6新增的幾種方式和某些方法會使用,以下會有介紹。以陣列來說,對其使用for
和for of
雖然可存取到相同的成員,但是實際的操作方式卻不同。
// 改变数组默认的遍历器接口。 Array.prototype[Symbol.iterator] = function () { let index = 0; let array = this; console.log('Use iterator'); return { next() { return index < array.length ? { value: array[index++], done: false } : { value: undefined, done: true }; } } }; let arr = [1, 2]; for (let v of arr) { console.log(v); // 打印出 Use iterator, 1, 2。 } for (let i = 0; i < arr.length; i++) { console.log(arr[i]); // 打印出 1, 2。 } arr.forEach(d => { console.log(d); // 打印出 1, 2。 });
物件沒有預設的遍歷器介面
為什麼物件沒有預設的遍歷器介面?這要從兩方面說明。一為遍歷器是種線性處理結構,對於任何非線性的資料結構,部署了遍歷器接口,就等於部署一種線性轉換。二是物件本來就是一個無序的集合,如果希望其有序,可以用Map
來代替。這即是各有其長,各安其職。屎殼郎如果不滾糞球而去採蜜,那,呃,花妹妹可能就遭殃咯。
自行生成的类数组对象(拥有length
属性),不具备遍历器接口。这与String
等原生类数组对象不同,毕竟人家是亲生的,一出生就含着金钥匙(也不怕误吞)。不过我们可以将数组的遍历器接口直接应用于自行生成的类数组对象,简单有效无副作用。
let obj = { 0: 'a', 1: 'b', length: 2, [Symbol.iterator]: Array.prototype[Symbol.iterator] }; let iterator = obj[Symbol.iterator](); console.log( iterator.next() ); // {value: "a", done: false} console.log( iterator.next() ); // {value: "b", done: false} console.log( iterator.next() ); // {value: undefined, done: true}
为对象添加遍历器接口,也不影响之前不使用遍历器的方法,比如for in
, Object.keys
等等(两者不等同)。
let obj = { 0: 'a', 1: 'b', length: 2, [Symbol.iterator]: Array.prototype[Symbol.iterator] }; console.log( Object.keys(obj) ); // ["0", "1", "length"] for (let v of obj) { console.log(v); // 依次打印出:"a", "b"。 } for (let k in obj) { console.log(k); // 依次打印出:"0", "1", "length"。 }
for of for of
是专门用来消费遍历器的,其遍历的是键值(for in
遍历的是键名)。
for (let v of [1, 2, 3]) { console.log(v); // 依次打印出:1, 2, 3。 }
扩展运算符
无论是解构赋值或扩展运算都是默认调用遍历器的。
let [...a] = [3, 2, 1]; // [3, 2, 1] let b = [...[3, 2, 1]]; // [3, 2, 1]
yield*
在Generator
函数中有yield*
命令,如果其后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。
for (let v of G()) { console.log(v); // 依次打印出:1, 2, 3, 4, 5 } function* G() { yield 1; yield* [2,3,4]; yield 5; }
其它场合
有些接受数组作为参数的函数,会默认使用数组的遍历器接口,所以也等同于默认调用。比如Array.from()
, Promise.all()
。
相关推荐:
以上是遍歷器Iterator存取資料集合的統一介面的方法的詳細內容。更多資訊請關注PHP中文網其他相關文章!