文章内容分两部分:javascript
上半部分开始...java
迭代器模式:提供一种方法顺序访问一个聚合对象中的各个元素,而又不须要暴露该对象的内部表示。git
简单理解(白话理解):统一 “集合” 型数据结构的遍历接口,实现可循环遍历获取集合中各数据项(不关心数据项中的数据结构)。es6
生活小栗子:清单 TodoList。每日清单有学习类、生活类、工做类、运动类等项目,清单列表只管罗列,无论类别。github
// 统一遍历接口实现
var each = function(arr, callBack) {
for (let i = 0, len = arr.length; i < len; i++) {
// 将值,索引返回给回调函数callBack处理
if (callBack(i, arr[i]) === false) {
break; // 停止迭代器,跳出循环
}
}
}
// 外部调用
each([1, 2, 3, 4, 5], function(index, value) {
if (value > 3) {
return false; // 返回false停止each
}
console.log([index, value]);
})
// 输出:[0, 1] [1, 2] [2, 3]
复制代码
“迭代器模式的核心,就是实现统一遍历接口。”设计模式
内部迭代器: 内部定义迭代规则,控制整个迭代过程,外部只需一次初始调用数组
// jQuery 的 $.each(跟上文each函数实现原理相似)
$.each(['Angular', 'React', 'Vue'], function(index, value) {
console.log([index, value]);
});
// 输出:[0, Angular] [1, React] [2, Vue]
复制代码
优势:调用方式简单,外部仅需一次调用 缺点:迭代规则预先设置,欠缺灵活性。没法实现复杂遍历需求(如: 同时迭代比对两个数组)浏览器
外部迭代器: 外部显示(手动)地控制迭代下一个数据项数据结构
借助 ES6 新增的 Generator
函数中的 yield*
表达式来实现外部迭代器。函数
// ES6 的 yield 实现外部迭代器
function* generatorEach(arr) {
for (let [index, value] of arr.entries()) {
yield console.log([index, value]);
}
}
let each = generatorEach(['Angular', 'React', 'Vue']);
each.next();
each.next();
each.next();
// 输出:[0, 'Angular'] [1, 'React'] [2, 'Vue']
复制代码
优势:灵活性更佳,适用面广,能应对更加复杂的迭代需求 缺点:需显示调用迭代进行(手动控制迭代过程),外部调用方式较复杂
不一样数据结构类型的 “数据集合”,须要对外提供统一的遍历接口,而又不暴露或修改内部结构时,可应用迭代器模式实现。
下半部分开始...
“迭代器等同于遍历器。在某些文章中,可能会出现遍历器的字眼,其实二者的意思一致。”
JavaScript 中 原有表示 “集合” 的数据结构主要是 “数组(Array)” 和 “对象(Object)”,ES6又新增了 Map
和 Set
,共四种数据集合,浏览器端还有 NodeList
类数组结构。为 “集合” 型数据寻求统一的遍历接口,正是 ES6 的 Iterator 诞生的背景。
ES6 中迭代器 Iterator 做为一个接口,做用就是为各类不一样数据结构提供统一的访问机制。任何数据结构只要部署了 Iterator 接口,就能够完成遍历操做。
Iterator 做用:
for...of
实现循环遍历Iterator只是一种接口,与遍历的数据结构是分开的。 重温迭代器模式特色:我只要统一遍历数据项的接口,不关心其数据结构。
ES6 默认的 Iterator 接口部署在数据结构的 Symbol.iterator
属性上,该属性自己是一个函数,表明当前数据结构默认的遍历器生成函数。执行该函数 [Symbol.iterator]()
,会返回一个遍历器对象。只要数据结构拥有 Symbol.iterator
属性,那么它就是 “可遍历的” 。
遍历器对象的特征:
next
属性方法;next()
,会返回一个包含 value
和 done
属性的对象
value
: 当前数据结构成员的值done
: 布尔值,表示遍历是否结束原生具有 Iterator 接口的数据结构:
let arr = ['a', 'b', 'c'];
let iterator = arr[Symbol.iterator]();
iterator.next(); // { value: 'a', done: false }
iterator.next(); // { value: 'b', done: false }
iterator.next(); // { value: 'c', done: false }
iterator.next(); // { value: undefined, done: false }
复制代码
原生部署 Iterator 接口的数据结构,无需手动执行遍历器生成函数,可以使用 for...of
自动循环遍历。
for...of
运行原理:
[Symobo.iterator]()
方法,拿到遍历器对象;next()
方法,获得 {value: ..., done: ... }
对象// for...of 自动遍历拥有 Iterator 接口的数据结构
let arr = ['a', 'b', 'c'];
for (let item of arr) {
console.log(item);
}
// 输出:a b c
复制代码
类数组对象:存在数值键名和
length
属性的对象
类数组对象部署 Iterator 方法:
// 方法一:
NodeList.prototype[Symbol.iterator] = Array.prototype[Sybmol.iterator];
// 方法二:
NodeList.prototype[Symbol.iterator] = [][Symbol.iterator];
// for...of 遍历类数组对象
let arrLike = {
0: 'a',
1: 'b',
2: 'c',
length: 3,
[Symbol.iterator]: Array.prototype[Symbol.iterator]
};
for (let item of arrLike) {
console.log(item);
}
// 输出:a b c
复制代码
对象(Object)没有默认 Iterator 接口,由于对象属性遍历顺序不肯定,需开发者手动指定。
注意:
Symbol.iterator
方法,并没有效果;Symbol.iterator
方法对应的部署遍历器生成函数(即返回一个遍历器对象),解释引擎会报错。var obj = {};
obj[Symbol.iterator] = () => 1;
[...obj]; // TypeError: [] is not a function
复制代码
for...of
遍历普通对象的解决方法:
Objet.keys
将对象键名生成一个数组,而后遍历该数组;let person = {
name: 'Ken',
sex: 'Male'
}
// Object.keys
for (let key of Object.keys(person)) {
console.log(`${key}: ${person[key]}`);
}
// Generator 包装对象
function* entries(obj) {
for (let key of Object.keys(obj)) {
yield [key, obj[key]];
}
}
for (let [key, value] of entries(person)) {
console.log(`${key}: ${value}`);
}
// 输出:
// name: Ken
// sex: Male
复制代码
yield*
for...of
Array.from()
Map()/Set()/WeakMap()/WeakSet()
Promise.all()/Promise.race()
for 循环 :需定义索引变量,指定循环终结条件。
for (let i = 0, len = arr.length; i < len; i++) {
console.log(arr[i]);
}
复制代码
forEach: 没法中途跳出循环,break/return
。
forEach(arr, function(item, index) {
console.log(item, index);
})
复制代码
for...in:
let triangle = {a: 1, b: 2, c: 3};
function ColoredTriangle() {
this.color = 'red';
}
ColoredTriangle.prototype = triangle;
let obj = new ColoredTriangle();
for (let prop in obj) {
// 需手动判断是否属于自身属性,而不是原型链属性
if (obj.hasOwnProperty(prop)) {
console.log(`obj.${prop} = ${obj[prop]}`);
}
}
// 输出:obj.color = red
复制代码
for...of 较其它三者优势:
for...in
同样简洁,但没有 for...in
的缺点;forEach
, 可以使用 break/return/continue
退出循环;缺点:遍历普通对象时,不能直接使用。
参考文章
本文首发Github,期待Star! github.com/ZengLingYon…
做者:以乐之名 本文原创,有不当的地方欢迎指出。转载请指明出处。