资讯专栏INFORMATION COLUMN

JavaScript 设计模式(五):迭代器模式

zhou_you / 2878人阅读

摘要:文章内容分两部分前半部分为迭代器模式概念后半部分为中迭代器上半部分开始迭代器模式提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。下半部分开始的迭代器迭代器等同于遍历器。执行该函数,会返回一个遍历器对象。

文章内容分两部分:

前半部分为 “迭代器模式” 概念;

后半部分为 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又新增了 MapSet,共四种数据集合,浏览器端还有 NodeList 类数组结构。为 “集合” 型数据寻求统一的遍历接口,正是 ES6 的 Iterator 诞生的背景。

ES6 中迭代器 Iterator 作为一个接口,作用就是为各种不同数据结构提供统一的访问机制。任何数据结构只要部署了 Iterator 接口,就可以完成遍历操作。

Iterator 作用:

为各种数据结构,提供一个统一的、简便的访问接口;

使得数据结构的成员能够按某种次序排列;

为新的遍历语法 for...of 实现循环遍历

Iterator只是一种接口,与遍历的数据结构是分开的。 重温迭代器模式特点:我只要统一遍历数据项的接口,不关心其数据结构。

ES6 默认的 Iterator 接口部署在数据结构的 Symbol.iterator 属性上,该属性本身是一个函数,代表当前数据结构默认的遍历器生成函数。执行该函数 [Symbol.iterator](),会返回一个遍历器对象。只要数据结构拥有 Symbol.iterator 属性,那么它就是 “可遍历的” 。

遍历器对象的特征:

拥有 next 属性方法;

执行 next(),会返回一个包含 valuedone 属性的对象

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 将对象键名生成一个数组,然后遍历该数组;

Generator 函数重新包装对象

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 退出循环;

提供了遍历所有数据的统一接口

缺点:遍历普通对象时,不能直接使用。

参考文章

《JavaScript 设计模式与开发实践》

《阮一峰ES6入门:Iterator 和 for...of 循环》

本文首发Github,期待Star!
https://github.com/ZengLingYong/blog

作者:以乐之名
本文原创,有不当的地方欢迎指出。转载请指明出处。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/105230.html

相关文章

  • JavaScript 模式》知识点小抄本(上)

    摘要:单体模式有以下优点用来划分命名空间,减少全局变量数量。通常我们使用操作符创建单体模式的三种选择,让构造函数总返回最初的对象使用全局对象来存储该实例不推荐,容易全局污染。实现该工厂模式并不困难,主要是要找到能够穿件所需类型对象的构造函数。 介绍 最近开始给自己每周订个学习任务,学习结果反馈为一篇文章的输出,做好学习记录。 这一周(02.25-03.03)我定的目标是《JavaScri...

    didikee 评论0 收藏0
  • ES6 Features系列:GeneratorFunction介绍

    摘要:没有显示显示显示关键字迭代器生成器用于马上退出代码块并保留现场,当执行迭代器的函数时,则能从退出点恢复现场并继续执行下去。迭代器迭代器是一个拥有方法和方法的对象,通过函数不断执行以关键字分割的代码段,通过函数令分割的代码段抛出异常。 一、前言                            第一次看koajs的示例时,发现该语句 function *(next){..........

    golden_hamster 评论0 收藏0
  • JS迭代模式JavaScript设计模式与开发实践》阅读笔记

    摘要:但实际中,内部迭代器和外部迭代器两者并无优劣。迭代器并不只迭代数组迭代器模式不仅能迭代数组,还可以迭代一些类数组对象。晚安了,参考设计模式与开发实践曾探本文作者本文链接迭代器模式设计模式与开发实践阅读笔记 迭代器模式:一个相对简单的模式,目前绝大多数语言都内置了迭代器,以至于大家都不觉得这是一种设计模式 迭代器模式 迭代器模式指提供一种方法访问一个聚合对象中的各个元素,而又不需要暴露该...

    djfml 评论0 收藏0
  • JavaScript 设计模式(六) 迭代模式

    摘要:迭代器模式可以把迭代的过程从业务逻辑中分离出来,在使用迭代器模式后,即使不关心内部构造,也可以按顺序访问其他的每个元素。中的迭代器迭代器模式无非就是循环访问聚合对象中的各个元素。目前绝大部分都内置了迭代器 迭代器模式指提供一种方法顺序访问一个聚合对象中的各个元素,而不需要暴露对象的内部。迭代器模式可以把迭代的过程从业务逻辑中分离出来,在使用迭代器模式后,即使不关心内部构造,也可以按顺序...

    xiongzenghui 评论0 收藏0
  • JavaScript iterator 设计模式

    摘要:迭代器模式就是按照顺序访问一个对象中元素,而不用暴露该对象的内部组成。迭代器模式就是将这个迭代实现从业务中分离出来。外部迭代器外部迭代器必须显式地请求才会迭代下一个元素。 迭代器模式就是按照顺序访问一个对象中元素,而不用暴露该对象的内部组成。迭代器模式就是将这个迭代实现从业务中分离出来。 但实际开发中我们并不将他当成一个设计模式。 前瞻后顾 说起迭代器,想必对ES6有了解的同学应该不会...

    CocoaChina 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<