JavaScript 设计模式(五):迭代器模式
栏目: JavaScript · 发布时间: 5年前
内容简介:文章内容分两部分:上半部分开始...
文章内容分两部分:
- 前半部分为 “迭代器模式” 概念;
- 后半部分为 ES6 中 Iterator (迭代器)
上半部分开始...
迭代器模式:提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。
简单理解(白话理解):统一 “集合” 型数据结构的遍历接口,实现可循环遍历获取集合中各数据项(不关心数据项中的数据结构)。
生活小栗子:清单 TodoList。每日清单有学习类、生活类、工作类、运动类等项目,清单列表只管罗列,不管类别。
模式特点
- 为遍历不同数据结构的 “集合” 提供统一的接口;
- 能遍历访问 “集合” 数据中的项,不关心项的数据结构
模式实现
// 统一遍历接口实现 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 / for...of)
- 外部迭代器 (ES6 的 yield)
内部迭代器
内部迭代器: 内部定义迭代规则,控制整个迭代过程,外部只需一次初始调用
// 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']
优点:灵活性更佳,适用面广,能应对更加复杂的迭代需求
缺点:需显示调用迭代进行(手动控制迭代过程),外部调用方式较复杂
适用场景
不同数据结构类型的 “数据集合”,需要对外提供统一的遍历接口,而又不暴露或修改内部结构时,可应用迭代器模式实现。
下半部分开始...
ES6 的 Iterator 迭代器
“迭代器等同于遍历器。在某些文章中,可能会出现遍历器的字眼,其实两者的意思一致。”
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 接口的数据结构:
- Array
- Map
- Set
- String
- TypedArray
- 函数的 arguments 对象
- NodeList 对象
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
ES6 的 Iterator 应用场景
- 解构赋值
- 扩展运算符
-
yield*
-
任何以数组为参数的遍历的场景:
for...of Array.from() Map()/Set()/WeakMap()/WeakSet() Promise.all()/Promise.race()
for...of 对比 for / for...in / forEach
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!
https://github.com/ZengLingYong/blog作者:以乐之名
以上所述就是小编给大家介绍的《JavaScript 设计模式(五):迭代器模式》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 设计模式(十七)迭代器模式
- 设计模式 | 迭代器模式及典型应用
- golang设计模式之迭代器模式
- 折腾Java设计模式之迭代器模式
- 设计模式——迭代器模式(遍历王者荣耀和英雄联盟英雄信息)
- 迭代器萃取与反向迭代器
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。