摸鱼日记之—— js 中的遍历器:Iterator 与 for of
1.什么是Iterator ?
遍历器(Iterator)它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作。
Iterator 的作用有三个:
1.是为各种数据结构,提供一个统一的、简便的访问接口;
2.是使得数据结构的成员能够按某种次序排列;
3.是 ES6 创造了一种新的遍历命令for...of
循环,Iterator 接口主要供for...of
消费。
2.Iterator怎么用 ?
for...of
语句在可迭代对象(包括 Array
,Map
,Set
,String
,TypedArray
,arguments 对象等等)上创建一个迭代循环,调用自定义迭代钩子,并为每个不同属性的值执行语句
/** * 语法 * @variable 每次迭代中属性的值 * @iterable 被迭代枚举其属性的对象 */ for (variable of iterable) { //statements }
const array1 = ['a', 'b', 'c']; for (const element of array1) { console.log(element); } // a // b // c
接下来一起来实践一下上面那些类型是不是真的可以用
// Array let arr = [10, 20, 30]; for (let value of arr) { value += 1; console.log(value); } // 11 // 21 // 31 // Map let map = new Map([["a", 1], ["b", 2], ["c", 3]]); for (let entry of map) { console.log(entry); } // ["a", 1] // ["b", 2] // ["c", 3] // Set let set = new Set([1, 1, 2, 2, 3, 3]); for (let value of set) { console.log(value); } // 1 // 2 // 3 // String let str = "boo"; for (let value of str) { console.log(value); } // "b" // "o" // "o" // TypedArray let typedArr = new Uint8Array([0x00, 0xff]); for (let value of typedArr) { console.log(value); } // 0 // 255 // arguments (function() { for (let argument of arguments) { console.log(argument); } })(1, 2, 3); // 1 // 2 // 3
View Code
那其他的类型用for of会怎么样?
比如一个普通的object类型
产品:“那我就要把for of用在对象上”
我:“对象不能用for of,你看这不都报错了嘛”
产品:“不听不听,你是不是不想做”
我:“好吧好吧,既然你想要用,那就满足你”
首先要了解遍历器 Iterator 的协议,传送门:MDN 迭代协议,
简而言之就是:
可迭代协议:
可迭代协议允许 JavaScript 对象定义或定制它们的迭代行为,例如,在一个 for..of 结构中,哪些值可以被遍历到。一些内置类型同时是内置可迭代对象,并且有默认的迭代行为,比如 Array
或者 Map
,而其他内置类型则不是(比如 Object
))。
要成为可迭代对象, 一个对象必须实现 @@iterator
方法。这意味着对象(或者它原型链上的某个对象)必须有一个键为 @@iterator
的属性,可通过常量 Symbol.iterator 访问该属性:
迭代器协议:
迭代器协议定义了产生一系列值(无论是有限个还是无限个)的标准方式。当值为有限个时,所有的值都被迭代完毕后,则会返回一个默认返回值。
只有实现了一个拥有以下语义(semantic)的 next()
方法,一个对象才能成为迭代器:
var obj = {
data: [1,2,3],
[Symbol.iterator]() {
var nextIndex = 0, self = this;
return {
next() {
var done = nextIndex >= self.data.length;
var value = done ? undefined : self.data[nextIndex++]
return { value: value, done: done }
}
}
}
}
for (const item of obj) { console.log(item) }// 1// 2// 3
由此可见,普通对象不可直接使用for of,在[Symbol.iterator]属性上部署遍历器生成的方法后即可被for of使用(原型链上的对象具有该方法也可)
实际上,对象之所以没有默认部署 Iterator 接口,是因为对象的哪个属性先遍历,哪个属性后遍历是不确定的,需要开发者手动指定。
那知道了上面遍历器的协议,我们可以通过更简单的方式判定上述六种类型是否实现了遍历器接口了
Array.prototype.hasOwnProperty(Symbol.iterator); // true Set.prototype.hasOwnProperty(Symbol.iterator); // true Map.prototype.hasOwnProperty(Symbol.iterator); // true String.prototype.hasOwnProperty(Symbol.iterator); // true (function(){ console.log(arguments.hasOwnProperty(Symbol.iterator)) })(1,2,3) // true Uint8Array.prototype.__proto__.hasOwnProperty(Symbol.iterator) // true
3. 与for in的区别
可能看到for of 有人想到for in,那这两个长这么像,他两有啥区别呢。咱们一起来看看
var arr1 = ['a', 'b', 'c', 'd']; // 输出键名 for (let a in arr1) { console.log(a); // 0 1 2 3 } // 输出键值 for (let a of arr1) { console.log(a); // a b c d } // 注意:还有个细节
// for...of
循环调用遍历器接口,数组的遍历器接口只返回具有数字索引的属性。这一点跟for...in
循环也不一样。
let arr2 = [3, 5, 7];
arr2.foo = 'hello';
for (let i in arr2) {
console.log(i); // "0", "1", "2", "foo"
}
for (let i of arr2) {
console.log(i); // "3", "5", "7"
}
4. 与其他循环的区别
var arr = Array(10).fill(1);
// for i 循环法,不够简洁
for (var i = 0; i < arr.length; i++) {
console.log(arr[i])
}
// forEach 循环,不能手动跳出循环
arr.forEach(function (value) {
console.log(value);
})
// for i 循环获取的是键名,而不是键值,因此不大适用于遍历数组
for (var i in arr) {
console.log(i)
}
// for of 可以跳出循环,语法简洁
for (var i of arr) {
console.log(i)
}
经比较,遍历对象用for in, 遍历数组用for of
实际上,要实现对象的Iterator接口还有更简洁的方法,就是使用generator
let obj = { * [Symbol.iterator]() { yield 'hello'; yield 'world'; } }; for (let x of obj) { console.log(x); } // hello // world
看到这里有人又奇怪了,这个 * 号是个什么鬼?
答案是 Generator 函数,Generator 函数是一个普通函数,但是有两个特征。一是,function
关键字与函数名之间有一个星号;二是,函数体内部使用yield
表达式,定义不同的内部状态(yield
在英语里的意思就是“产出”)。
2020-06-09 11:23:51