背景知识
在ES5 时代,如果想遍历数组中的数据,必须要初始化一个变量来记录每一次遍历在数组中的位置。
例子:
const arr = ["A", "B", "C"];
for (let i = 0; i < arr.length; i++) {
console.log(arr[i]);
}
//输出:A B C
截止到ES6,JavaScript 已经拥有了数组、对象、Map集合和Set集合这样四种数据结构。为了统一和简化遍历这四种数据结构的操作,ES6引入了遍历器机制。
基本概念
ES6 规定,可遍历的对象都具有Symbol.iterator 属性,这个属性指向一个函数,就是当前对象默认的遍历器生成函数。这个遍历器生成函数大致的模样可以用ES5 语法模拟出来:这个函数返回一个next() 方法,每调用next() 方法,都会返回数据结构的当前成员的信息。具体来说,就是返回一个包含value和done两个属性的对象。其中,value属性是当前成员的值,done属性是一个布尔值,表示遍历是否结束。
例子:
function createIterator(items) {
let i = 0;
return {
next() {
let done = i >= items.length;
let value = !done ? items[i++] : undefined;
return {
done: done,
value: value
}
}
}
}
let iterator = createIterator([1, 2, 3]);
console.log(iterator.next()); //输出:{done: false, value: 1}
console.log(iterator.next()); //输出:{done: false, value: 2}
console.log(iterator.next()); //输出:{done: false, value: 3}
console.log(iterator.next()); //输出:{done: true, value:undefined }
默认遍历器
在ES6 中,已经默认为绝大多数內建的数据结构提供了遍历器,不需要像上面例子中那样自己去创建。默认的遍历器主要有以下三种:
1、keys()方法:默认遍历器,其值为集合中的所有键名。
2、values()方法:默认遍历器,其值为集合中的所有值。
3、entries()方法:默认遍历器,其值为所有成员的键值对。
例子:
const arr = ["A", "B", "C"];
console.log([...arr.keys()]); //输出:[0, 1, 2]
console.log([...arr.values()]); //输出:["A", "B", "C"]
console.log([...arr.entries()]); //输出:[[0, "A"],[1, "B"],[2, "C"]]
const set = new Set(arr);
console.log([...set.keys()]); //输出:["A", "B", "C"]
console.log([...set.values()]); //输出:["A", "B", "C"]
console.log([...set.entries()]); //输出:[["A", "A"],["B", "B"],["C", "C"]]
const map = new Map().set("name", "Tom").set("age", 19);
console.log([...map.keys()]); //输出:["name", "age"]
console.log([...map.values()]); //输出:["Tom", 19]
console.log([...map.entries()]); //输出:[["name", "Tom"],["age", 19]]
*扩展运算符(…)会持续调用遍历器的next() 方法,并将value属性值插入到数组中,直到结果对象的done 属性值为true。
*Set 集合的键名和键值是同一个值,所以keys() 方法和values() 方法的返回值完全一致。
不同数据结构的默认遍历器
每个数据结构都有一个默认的遍历器,例如数组的默认遍历器是values()
,在没有明确指定遍历器的情况下,这些数据结构都会使用默认的遍历器。我们可以通过检测对象的Symbol.iterator 属性来判断对象是否拥有遍历器。
例子:
const arr = ["A", "B", "C"];
console.log(typeof arr[Symbol.iterator] === "function"); //输出:true
console.log(arr[Symbol.iterator]); //输出:function values() { ... }
const set = new Set(arr);
console.log(typeof set[Symbol.iterator] === "function"); //输出:true
console.log(set[Symbol.iterator]); //输出:function values() { ... }
const map = new Map().set("name", "Tom").set("age", 19);
console.log(typeof map[Symbol.iterator] === "function"); //输出:true
console.log(map[Symbol.iterator]); //输出:function entries() { ... }
原生具备遍历器的对象有:数组、Map集合、Set集合、字符串、arguments和 NodeList(节点列表)。而对象(Object)默认是不可遍历的,我们可以通过Object.keys()、Object.values()和Object.entries() 方法把对象变成数组,使其拥有遍历器,或者直接为对象添加Symbol.iterator 属性来自定义遍历器。
例子1:
const obj = {
name: "Tom",
age: 19
}
console.log(typeof Object.entries(obj)[Symbol.iterator] === "function"); //输出:true
console.log([...Object.entries(obj)]); //输出:[["name", "Tom"],["age", 19]]
例子2:
const obj = {
name: "Tom",
age: 19,
[Symbol.iterator]() {
let data = [],
i = 0;
for (let k in this) {
if (this.hasOwnProperty(k)) {
data.push(Array.of(k, this[k]));
}
}
return {
next() {
let done = i >= data.length;
let value = !done ? data[i++] : undefined;
return {
done: done,
value: value
}
}
}
}
}
console.log([...obj]);
//输出:[["name", "Tom"],["age", 19]]
调用遍历器
当我们去遍历拥有遍历器的对象的时候,系统就会自动去调用对象默认遍历器的接口。没有遍历器接口的对象不能被遍历。
for…of 循环
我们知道,遍历器内部的next() 方法,每调用一次只会返回当前成员的信息,而for…of循环将持续调用next() 方法直到返回对象的done 属性的值为 true。
例子:
const arr = ["A", "B", "C"];
const map = new Map().set("name", "Tom").set("age", 19);
const str = "love";
for (let v of arr) {
console.log(v); //输出:A B C
}
for (let [v, i] of map) {
console.log(v + ":" + i); //输出:name:Tom age:19
}
for (let v of str) {
console.log(v); //输出:l o v e
}
展开运算符
如果我们想把非数组可遍历对象的所有成员填充到一个数组中,最方便的方法是使用展开运算符(…)。展开运算符会触发默认的遍历器取得所有的值,然后按照取得顺序依次插入到数组中。
例子:
console.log([...new Set([1, 2, 3])]) //输出:[1, 2, 3]
console.log([1, ...new Set([2, 3]), ...
"love"
]) //输出:[1, 2, 3, "l", "o", "v", "e"]
ES6中,由于数组的遍历会调用遍历器接口,所以任何接受数组作为参数的场合,其实都调用了遍历器。例如Array.from()方法、Set()、Map()、解构赋值等等。
例子:
console.log(Array.from(new Set([1, 2, 3]))); //输出:[1, 2, 3]
let [a, b] = new Set([1, 2]);
console.log(a + "," + b); //输出:1,2
如有错误,欢迎指正,本人不胜感激。