js--迭代器总结
前言
正文
1 、迭代器的产生、定义和模拟
(1) for 循环的弊端
普通的for循环的弊端因为数组有已知的长度,且数组每一项都可以通过索引获取,所以整个数组可以通过递增索引来遍历。由于如下原因,通过这种循环来执行例程并不理想。
a、 迭代之前需要事先知道如何使用数据结构。数组中的每一项都只能先通过引用取得数组对象,然后再通过 [] 操作符取得特定索引位置上的项。这种情况并不适用于所有数据结构。
b、 遍历顺序并不是数据结构固有的。通过递增索引来访问数据是特定于数组类型的方式,并不适用于其他具有隐式顺序的数据结构。
(2) 迭代器的产生
迭代器是被设计专用于迭代的对象,带有特定的接口,迭代器持有一个指向集合位置的内部指针,每当调用了next()方法,迭代器就会返回一个结果 IteratorResult 对象,该结果对象有两个属性,对应下一个值的 value 以及一个布尔类型的 done,在最后一个值再调用next()则返回 done 属性值为 true(标识没有更多值供使用),并且value属性会是迭代器自身的返回值. 该返回值不是原始数据集的一部分,却成为相关数据的最后一部分,或在迭代器未提供返回值的时候使用undefined ,迭代器的自身返回值类似于函数的返回值,是向调用者返回信息的最后手段。
(3) 迭代器的模拟
根据上面的定义,手动实现迭代器函数
// createIterator 函数返回一个带有next()方法的对象,作为迭代器,每次调用next()方法,返回具体的IteratorResult 对象值 function createIterator(items) { var i = 0; return { next() { var done = (i >= items.length); var value = !done ? items[i++] : undefined; return { done, value } } } } var iterator = createIterator([11, 22, 33]) console.log(iterator.next());//{done: false, value: 11} console.log(iterator.next());//{done: false, value: 22} console.log(iterator.next());//{done: false, value: 33} console.log(iterator.next());//{done: true, value: undefined}
2、 迭代器模式和可迭代对象
迭代器模式(特别是在 ECMAScript 这个语境下)描述了一个方案,即可以把有些结构称为“可迭代对象”(iterable),因为它们实现了正式的 Iterable 接口,而且可以通过迭代器 Iterator 消费。
(1)访问默认迭代器 symbol.iterator
let values = [1, 2, 3, 4] let iterator = values[Symbol.iterator]() console.log(iterator.next());//{value:1,deno:false} console.log(iterator.next());//{value:2,deno:false} console.log(iterator.next());//{value:3,deno:false} console.log(iterator.next());//{value:4,deno:false} console.log(iterator.next());//{value:undefiend,deno:true}
(2) 检测一个对象是否可以用来迭代
let num = 1; let obj = {}; // 这两种类型没有实现迭代器工厂函数 console.log(num[Symbol.iterator]); // undefined console.log(obj[Symbol.iterator]); // undefined let str = 'abc'; let arr = ['a', 'b', 'c']; let map = new Map().set('a', 1).set('b', 2).set('c', 3); let set = new Set().add('a').add('b').add('c'); let els = document.querySelectorAll('div'); // 这些类型都实现了迭代器工厂函数 console.log(str[Symbol.iterator]); // f values() { [native code] } console.log(arr[Symbol.iterator]); // f values() { [native code] } console.log(map[Symbol.iterator]); // f values() { [native code] } console.log(set[Symbol.iterator]); // f values() { [native code] } console.log(els[Symbol.iterator]); // f values() { [native code] } // 调用这个工厂函数会生成一个迭代器 console.log(str[Symbol.iterator]()); // StringIterator {} console.log(arr[Symbol.iterator]()); // ArrayIterator {} console.log(map[Symbol.iterator]()); // MapIterator {} console.log(set[Symbol.iterator]()); // SetIterator {} console.log(els[Symbol.iterator]()); // ArrayIterator {}
总结:检测一个对象是否可以用来迭代的方式:typeof object[Symbol.iterator] === "function",同时可以得出很多内置类型都实现了 Iterable 接口:字符串、 数组、映射、集合、arguments 对象、NodeList 等 DOM 集合类型等。
注意:
a、 每个迭代器都表示对可迭代对象的一次性有序遍历。不同迭代器的实例相互之间没有联系,只会独立地遍历可迭代对象。
b、迭代器并不与可迭代对象某个时刻的快照绑定,而仅仅是使用游标来记录遍历可迭代对象的历程。如果可迭代对象在迭代期间被修改了,那么迭代器也会反映相应的变化。如下:
let arr = ['foo', 'baz']; let iter = arr[Symbol.iterator](); let iter2 = arr[Symbol.iterator](); console.log(iter.next()); // { done: false, value: 'foo' } console.log(iter2.next()); // { done: false, value: 'foo' } // 在数组中间插入值 arr.splice(1, 0, 'bar'); console.log(iter.next()); // { done: false, value: 'bar' } console.log(iter.next()); // { done: false, value: 'baz' } console.log(iter.next()); // { done: true, value: undefined }
3 、开发中使用的迭代器
(1) 集合的内置迭代器
/* entries()迭代器会在每次next()被调用时返回一个双项数组,此数组代表了集合中每个元素的键与值, 对于数组来说,第一项是数值索引, 对于set,第一项也是值,因为它的值也会被视为键, 对于map来说,第一项就是键 */ let arr = [1, 2, 3] console.log(arr.entries());//Array Iterator [] for (const item of arr.entries()) { console.log(item); }// [0:1],[1:2],[2:3] let set = new Set([1, 2, 3]) console.log(set.entries());//SetIterator {[1=>1],[2=>2],[3=>3]} for (const item of set.entries()) { console.log(item); }// [1:1],[2:2],[3:3] let map = new Map() map.set("first", "firseetValue") map.set("second", "seondValue") map.set("third", "thirdValue") console.log(map.entries());//MapIterator {["first"=>"firseetValue"],["second"=>"seondValue"],["third"=>"thirdValue"]} for (const item of map.entries()) { console.log(item); }// ["first":"firseetValue"],["second":"seondValue"],["third":"thirdValue"]
(2)字符串的迭代器
// 访问字符串中的字符可以通过下标的形式 let message = "a b" console.log(message[0])// 'a' let info = "a b" for (const c of info) { console.log(c); }//a, ,b
4、自定义函数实现迭代器
// 与 Iterable 接口类似,任何实现 Iterator 接口的对象都可以作为迭代器使用。 class Counter { constructor(limit) { this.limit = limit; } [Symbol.iterator]() { let count = 1,// 把计数器变量放到闭包里,然后通过闭包返回迭代器,让一个可迭代对象能够创建多个迭代器,且每创建一个迭代器就对应一个新计数器 limit = this.limit; return { next() { if (count <= limit) { return { done: false, value: count++ }; } else { return { done: true, value: undefined }; } }, // 可选的 return() 方法用于指定在迭代器提前关闭时执行的逻辑 return() { console.log('Exiting early'); return { done: true }; } }; } } let counter = new Counter(3); for (let i of counter) { console.log(i); } // 1 // 2 // 3 for (let i of counter) { console.log(i); } // 1 // 2 // 3 let counter2 = new Counter(5); try { for (let i of counter2) { if (i > 2) { throw 'err'; } console.log(i); } } catch (e) { } // 1 // 2 // Exiting early
5、如何使用for...of遍历对象
for…of是作为ES6新增的遍历方式,允许遍历一个含有iterator接口的数据结构(数组、对象等)并且返回各项的值,普通的对象用for..of遍历是会报错的。
如果需要遍历的对象是类数组对象,用Array.from转成数组即可。
var obj = { 0:'one', 1:'two', length: 2 }; obj = Array.from(obj); for(var k of obj){ console.log(k)// one two }
如果不是类数组对象,就给对象添加一个[Symbol.iterator]属性,并指向一个迭代器即可。
//方法一: var obj = { a:1, b:2, c:3 }; obj[Symbol.iterator] = function(){ var keys = Object.keys(this); var count = 0; return { next(){ if(count<keys.length){ return {value: obj[keys[count++]],done:false}; }else{ return {value:undefined,done:true}; } } } }; for(var k of obj){ console.log(k); } // 方法二 var obj = { a:1, b:2, c:3 }; obj[Symbol.iterator] = function*(){ var keys = Object.keys(obj); for(var k of keys){ yield [k,obj[k]] } }; for(var [k,v] of obj){ console.log(k,v); }
写在最后
以上就是本文的全部内容,希望给读者带来些许的帮助和进步,方便的话点个关注,小白的成长之路会持续更新一些工作中常见的问题和技术点。