本小节包括:
JavaScript 有 4 种数据集合,分别是数组(Array)、对象(Object)、Map 和 Set。而遍历器(Iterator)是一种接口,为各种不同的数据结构提供统一的访问机制。
Iterator 的作用:
数据结构只要部署了 Iterator 接口,我们就称这种数据结构为“可遍历的”。ES6 规定,默认的 Iterator 接口部署在数据结构的 Symbol.iterator (内置的 Symbol 值之一)属性上,或者说,一个数据接口只要具有 Symbol.iterator 属性,就可以认为是“可遍历的”。
原生具备 Iterator 接口的 数据结构如下:
例如,数组的 Symbol.iterator 属性
let arr ['a', 'b', 'c']
let iter = arr[Symbol.iterator]()
iter.next() // { value: 'a', done: false }
iter.next() // { value: 'b', done: false }
iter.next() // { value: 'c', done: false }
iter.next() // { value: undefined, done: true}
对于原生部署 Iterator 接口的数据结构,我们不用自己编写遍历器生成函数。而其他数据结构(主要是对象)的 Iterator 接口都需要自己在 Symbol.iterator 属性上部署,这样才会被 for...of 循环遍历。
为对象添加 Iterator 接口的例子
let obj = {
data: ['hello', 'world'],
[Symbol.iterator]() {
const self = this;
let index = 0;
return {
next() {
if(index < self.data.length) {
return {
value: self.data[index++],
done: false
}
} else {
return { value: undefined, done: true }
}
}
};
}
}
对于类似数组的对象(存在数值键名和 length 属性),部署 Iterator 接口可以直接引用数组的 Iterator 接口。
let iterable = {
0: 'a',
1: 'b',
2: 'c',
length: 3,
[Symbol.iterator]: Array.prototype[Symbol.iterator]
}
for (const item of iterable) {
console.log(item) // 'a', 'b', 'c'
}
有一些场合会默认调用 Iterator 接口(即 Symbol.iterator 方法)
字符串是一个类似数组的对象,也具有原生 Iterator 接口。
const str = 'hi'
typeof str[Symbol.iterator] // "function"
const iterator = str[Symbol.iterator]()
iterator.next() // { value: "h", done: false }
iterator.next() // { value: "i", done: false }
iterator.next() // { value: undefined, done: true }
Symbol.iterator 方法的最简单实现是使用下一章的 Generator 函数.
const myIterable = {}
myIterable[Symbol.iterator] = function* () {
yield 1;
yield 2;
yield 3;
}
[...myIterable] // [1, 2, 3]
for...of 循环内部调用的是数据结构的 Symbol.iterator 方法。for...of 循环可以使用的范围包括数组、Set 和 Map 结构、某些类似数组的对象(比如 arguments 对象、DOM NodeList 对象)、后面章节的 Generator 对象,以及字符串。
1.数组
for...of 循环调用遍历器接口,数组的遍历器接口只返回具有数字索引的属性。这一点跟 for..in 循环不同。
let arr = [3, 4, 5]
arr.foo = 'hi'
for (const i in arr) {
console.log(i) // "0", "1", "2", "foo"
}
for (const j of arr) {
console.log(j) // 3, 4, 5
}
2.Set 和 Map 结构
需要注意两点:首先,遍历的顺序是按照各个成员被添加进数据结构的顺序;其次,Set 结构遍历时返回的是一个值,而 Map 结构遍历时返回的是一个数组,该数组的两个成员分别为当前 Map 成员的键名和键值。
let mySet = new Set(['hi', 'hello'])
for (const item of mySet) {
console.log(item)
}
// "hi"
// "hello"
let myMap = new Map().set('a', 1).set('b', 2)
for (const item of myMap) {
console.log(item)
}
// ['a', 1]
// ['b', 2]
3.类似数组的对象
字符串、DOM NodeList 对象、arguments 对象的例子
// 字符串
let str = 'hi'
for (const s of str) {
console.log(s) // h i
}
// DOM NodeList 对象
let paras = document.querySelectorAll('p')
for (const p of paras) {
p.classList.add('test')
}
// arguments 对象
function printArgs() {
for (const x of arguments) {
console.log(x)
}
}
printArgs('a', 'b') // "a" "b"
4.与其他遍历语法的比较
JavaScript 中最原始的遍历方法就是 for 循环,但这种写法比较麻烦,后来有了 forEach 方法。但 forEach 方法又有缺陷,无法 break 或 return。for...in 循环就不说了,这个适合用来遍历对象。