ES6学习第五篇--迭代器
迭代器的概念
JavaScript 原有的表示“集合”的数据结构,主要是数组(Array)和对象(Object),ES6 又添加了Map和Set。这样就有了四种数据集合,用户还可以组合使用它们,定义自己的数据结构,比如数组的成员是Map,Map的成员是对象。这样就需要一种统一的接口机制,来处理所有不同的数据结构。
遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。
扩展知识:阮一峰解说迭代器
迭代器的作用
Iterator 的作用有三个:
一是为各种数据结构,提供一个统一的、简便的访问接口;
二是使得数据结构的成员能够按某种次序排列;
三是 ES6 创造了一种新的遍历命令for…of循环,Iterator 接口主要供for…of消费。
for-of的原理,每次遍历都会调用该对象的[Symbol.iterator]属性的next方法,当返回{value: undefined, done: true}后,表示遍历结束。
const array1 = ['a', 'b', 'c'];
const iterator = array1.values();
console.log(iterator.next()); // Object { value: "a", done: false }
console.log(iterator.next()); // Object { value: "d", done: false }
console.log(iterator.next()); // Object { value: "c", done: false }
console.log(iterator.next()); // Object { value: undefined, done: true }
迭代器的原理
普通的对象没有实现迭代器接口,不能通过迭代器遍历
let myIterable = {
a: 1,
b: 2,
c: 3
}
// 使用 for..of 循环
for(let elem of myIterable) {
console.log(elem);
}
运行结果图:
那如何实现迭代器(Iterator)接口呢,只需要在这个对象中实[Symbol.iterator]属性方法,[Symbol.iterator]这个属性名看起来怪怪的,Symbol是ES6表示唯一性的标识符,是Symbol的静态属性,标准委员会是为了避免命名的冲突,所以才这样命名。下面我们来实现[Symbol.iterator]属性。如下:
let myIterable = {
a: 1,
b: 2,
c: 3
}
myIterable[Symbol.iterator] = function() {
let self = this;
let arr = Object.keys(self);
let index = 0;
return {
next() {
return index < arr.length ? {value: self[arr[index++]], done: false} : {value: undefined, done: true};
}
}
}
for(const i of myIterable) {
console.log(i);
}
将myIterable对象添加Symbol.iterator属性,同时在返回的next方法中,添加两个属性,既让它成为了一个可迭代对象。(其实如果真的有这样的需求,可以考虑使用Map)。
迭代器的实例
原生的具备 Iterator 接口的数据结构如下:
Array
Map
Set
String
TypedArray(类数组)
函数的 arguments 对象
DOM NodeList 对象
Array
Array的原型链上面实现了迭代器接口,如下:
Array.prototype.values === Array.prototype[Symbol.iterator]
数组用for…of遍历
const array1 = ['a', 'b', 'c'];
const iterator = array1.values();
//下面两个for of后面接数组对象或数组的迭代器对象都能遍历数组
for (const value of iterator) {
console.log(value);
}//输出a b c
for (const value of array1 ) {
console.log(value);
}//输出a b c
//有朋友会问,为什么上面的iterator 也能用for...of遍历
//在控制台执行两条语句
//const array1 = ['a', 'b', 'c'];
//array1.values()[Symbol.iterator];
//执行结果:ƒ [Symbol.iterator]() { [native code] } 为一个迭代器接口
用iterator 遍历数组:
const array1 = ['a', 'b', 'c'];
const iterator = array1.values();
console.log(iterator.next()); // Object { value: "a", done: false }
console.log(iterator.next()); // Object { value: "d", done: false }
console.log(iterator.next()); // Object { value: "c", done: false }
console.log(iterator.next()); // Object { value: undefined, done: true }
运行结果图:
数组可以自己实现迭代器接口(Symbol.iterator为Symbol的静态属性):
const array1 = ['a', 'b', 'c'];
Array.prototype[Symbol.iterator] = function(){
var self = this;
let index = -1;
return {
next: function() {
let len = self.length;
index++;
//注意这个特殊打印标记,区分是调用自定义的还是原生的
console.log("自定义的迭代器接口");
return index < len ? {value:self[index],done:false}:{value:self[index],done:true};
}
};
};
for (let item of array1) {
console.log(item); // 'a', 'b', 'c'
}
运行结果图:
Map
Map.prototype[Symbol.iterator]()方法返回Map对象的键值对遍历器。其内部的实现原理和返回值和Map.prototype.entries()一致。
Map.prototype.entries === Map.prototype[Symbol.iterator]
Map的扩展知识:Map构造函数及其属性方法
迭代器遍历
var map = new Map();
map.set('a', 1);
map.set('b', 2);
map.set('c', 3);
map.set('d', 4);
map.set('e', 5);
var iter = map[Symbol.iterator]();
iter.next().value; // ['a', 1]
iter.next().value; // ['b', 2]
iter.next().value; // ['c', 3]
iter.next().value; // ['d', 4]
iter.next().value; // ['e', 5]
iter.next(); // undefined
Map的for…of遍历
var map = new Map();
map.set('a', 1);
map.set('b', 2);
map.set('c', 3);
map.set('d', 4);
map.set('e', 5);
for (let elem of map) {
console.log(elem);
console.log(elem instanceof Array);
}
运行结果图:
有兴趣的朋友也可以自己定义Map的迭代器接口
Set
Set对象是值的集合,你可以按照插入的顺序迭代它的元素。 Set中的元素只会出现一次,即 Set 中的元素是唯一的。
Set.prototype.values === Set.prototype[Symbol.iterator]
迭代器遍历
const set1 = new Set();
set1.add(42);
set1.add('forty two');
const iterator1 = set1[Symbol.iterator]();
console.log(iterator1.next().value);
// expected output: 42
console.log(iterator1.next().value);
// expected output: "forty two"
for…of遍历
const set1 = new Set();
set1.add(42);
set1.add('forty two');
for (let elem of set1) {
console.log(elem);
}
String
strSymbol.iterator方法返回一个新的Iterator对象,它遍历字符串的代码点,返回每一个代码点的字符串值。
const str = 'The quick red fox jumped over the lazy dog\'s back.';
const iterator = str[Symbol.iterator]();
let theChar = iterator.next();
while (!theChar.done && theChar.value !== ' ') {
console.log(theChar.value);
theChar = iterator.next();
// expected output: "T"
// "h"
// "e"
}
TypedArray
默认的迭代器接口遍历
//Uint8Array 数组类型表示一个8位无符号整型数组,创建时内容被初始化为0。
//创建完后,可以以对象的方式或使用数组下标索引的方式引用数组中的元素。
var arr = new Uint8Array([10, 20, 30, 40, 50]);
for (let n of arr) {
console.log(n);
}
//输出10 20 30 40. 50
//注意:arr [Symbol.iterator] === Array.prototype.values === Array.prototype[Symbol.iterator]
自定义类数组的迭代器接口进行遍历
let iterable = {
0: 'a',
1: 'b',
2: 'c',
length: 3,
[Symbol.iterator]: Array.prototype[Symbol.iterator]
};
for (let item of iterable) {
console.log(item); // 'a', 'b', 'c'
}
//运行结果输出:a b c
下面是把类数组转换为了数组后,进行遍历
let tyepArray = {
0: 'a',
1: 'b',
2: 'c',
length: 3
};
for (let item of Array.from(tyepArray)) {
console.log(item); // 'a', 'b', 'c'
}
//运行结果输出a b c
arguments
argumentsSymbol.iterator返回一个迭代器遍历每一个参数的值
function f() {
// your browser must support for..of loop
// and let-scoped variables in for loops
for (let letter of arguments) {
console.log(letter);
}
}
f('w', 'y', 'k', 'o', 'p');
DOM NodeList
Node.childNodes 返回包含指定节点的子节点的集合,子节点集合为 NodeList 类型。
for…of遍历
var node = document.createElement("div");
var kid1 = document.createElement("p");
var kid2 = document.createTextNode("hey");
var kid3 = document.createElement("span");
node.appendChild(kid1);
node.appendChild(kid2);
node.appendChild(kid3);
var list = node.childNodes;
// 使用 for..of 循环
for(var elem of list) {
console.log(elem);
}
迭代器的 return(),throw()
遍历器对象除了具有next方法,还可以具有return方法和throw方法。如果你自己写遍历器对象生成函数,那么next方法是必须部署的,return方法和throw方法是否部署是可选的。
return方法的使用场合是,如果for…of循环提前退出(通常是因为出错,或者有break语句),就会调用return方法。如果一个对象在完成遍历前,需要清理或释放资源,就可以部署return方法。
let myIterable = {
a: 1,
b: 2,
c: 3
}
myIterable[Symbol.iterator] = function() {
let self = this;
let arr = Object.keys(self);
let index = 0;
return {
next: function() {
return index < arr.length ? {value: self[arr[index++]], done: false} : {value: undefined, done: true};
},
return: function() {
console.log("遍历结束");
return {value: undefined, done: true};
}
}
}
for(const i of myIterable) {
console.log(i);
if (i == "2") {
break;
}
}
for(const i of myIterable) {
console.log(i);
if (i == "2") {
throw new Error();
}
}
运行结果图:
return和throw都会触发迭代器接口中的return方法,throw是先执行return方法后,再抛出错误。
for…of和for …in的区别
1 遍历数组的区别
for…in遍历数组
var arr = ['a', 'b', 'c'];
for(var i in arr){
console.log(arr[i]);
}
for…of遍历数组
var arr = ['a', 'b', 'c'];
for(var ch of arr){
console.log(ch);
}
for…of能直接获取数组的元素,for…in能获取到数组下标,再通过下标获取数组元素。
2 遍历对象的区别
for…of不能直接遍历普通对象,需要自定义对象的迭代器接口后才能遍历对象。
for…in能直接遍历对象的key,value
or(let key in obj) {
console.log('for in key', key)
}
- for…in 循环不仅遍历数字键名,还会遍历手动添加的其它键,甚至包括原型链上的键。
let arr = [1, 2, 3]
arr.set = 'world' // 手动添加的键
Array.prototype.name = 'hello' // 原型链上的键
for(let item in arr) {
console.log('item', item)
}
/*
item 0
item 1
item 2
item set
item name
*/