js迭代器模式

更新日期: 2019-07-03阅读: 2.4k标签: 模式

文章内容分两部分:

  1. 前半部分为 “迭代器模式” 概念;
  2. 后半部分为 ES6 中 Iterator (迭代器)

上半部分开始...

迭代器模式:提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。

简单理解(白话理解):统一 “集合” 型数据结构的遍历接口,实现可循环遍历获取集合中各数据项(不关心数据项中的数据结构)。

生活小栗子:清单 TodoList。每日清单有学习类、生活类、工作类、运动类等项目,清单列表只管罗列,不管类别。


模式特点

  1. 为遍历不同数据结构的 “集合” 提供统一的接口;
  2. 能遍历访问 “集合” 数据中的项,不关心项的数据结构


模式实现

// 统一遍历接口实现
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]

“迭代器模式的核心,就是实现统一遍历接口。”


模式细分

  1. 内部迭代器 (jquery 的 $.each / for...of)
  2. 外部迭代器 (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 作用:

  1. 为各种数据结构,提供一个统一的、简便的访问接口;
  2. 使得数据结构的成员能够按某种次序排列;
  3. 为新的遍历语法 for...of 实现循环遍历

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

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

遍历器对象的特征:

  1. 拥有 next 属性方法;
  2. 执行 next(),会返回一个包含 value 和 done 属性的对象

    • value: 当前数据结构成员的值
    • done: 布尔值,表示遍历是否结束

原生具备 Iterator 接口的数据结构:

  1. Array
  2. Map
  3. Set
  4. String
  5. TypedArray
  6. 函数的 arguments 对象
  7. 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 运行原理:

  1. 首先调用遍历对象 [Symobo.iterator]() 方法,拿到遍历器对象;
  2. 每次循环,调用遍历器对象 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 接口,因为对象属性遍历顺序不确定,需开发者手动指定。

注意:

  1. 普通对象部署数组的 Symbol.iterator 方法,并无效果;
  2. 普通对象若 Symbol.iterator 方法对应的部署遍历器生成函数(即返回一个遍历器对象),解释引擎会报错。
var obj = {};
obj[Symbol.iterator] = () => 1;
[...obj]; // TypeError: [] is not a function

for...of 遍历普通对象的解决方法:

  1. 使用 Objet.keys 将对象键名生成一个数组,然后遍历该数组;
  2. 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 应用场景

  1. 解构赋值
  2. 扩展运算符
  3. yield*
  4. 任何以数组为参数的遍历的场景:

    • 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:

  1. 只能获取键名,不能获取键值
  2. 以字符串为键名(但数组的键名为数值类型索引)
  3. 任意顺序遍历键名(???)
  4. 会遍历手动添加的其它键(原型链上的键)
  5. 为遍历对象设计,不适用数组
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 较其它三者优点:

  1. 和 for...in 一样简洁,但没有 for...in 的缺点;
  2. 不同于 forEach, 可使用 break/return/continue 退出循环;
  3. 提供了遍历所有数据的统一接口

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


参考文章

本文首发Github,期待Star!https://github.com/ZengLingYong/blog,作者:以乐之名 

 

链接: https://fly63.com/article/detial/3984

js设计模式之单例模式,javascript如何将一个对象设计成单例

单例模式是我们开发中一个非常典型的设计模式,js单例模式要保证全局只生成唯一实例,提供一个单一的访问入口,单例的对象不同于静态类,我们可以延迟单例对象的初始化,通常这种情况发生在我们需要等待加载创建单例的依赖。

前端设计模式:从js原始模式开始,去理解Js工厂模式和构造函数模式

工厂模式下的对象我们不能识别它的类型,由于typeof返回的都是object类型,不知道它是那个对象的实例。另外每次造人时都要创建一个独立的person的对象,会造成代码臃肿的情况。

JavaScript设计模式_js实现建造者模式

建造者模式:是将一个复杂的对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。工厂类模式提供的是创建单个类的模式,而建造者模式则是将各种产品集中起来进行管理,用来创建复合对象

html和xhtml,DOCTYPE和DTD,标准模式和兼容模式

主要涉及知识点: HTML与XHTML,HTML与XHTML的区别,DOCTYPE与DTD的概念,DTD的分类以及DOCTYPE的声明方式,标准模式(Standard Mode)和兼容模式(Quircks Mode),标准模式(Standard Mode)和兼容模式(Quircks Mode)的区别

前端四种设计模式_JS常见的4种模式

JavaScript中常见的四种设计模式:工厂模式、单例模式、沙箱模式、发布者订阅模式

javascript 策略模式_理解js中的策略模式

javascript 策略模式的定义是:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。 策略模式利用组合,委托等技术和思想,有效的避免很多if条件语句,策略模式提供了开放-封闭原则,使代码更容易理解和扩展, 策略模式中的代码可以复用。

javascript观察者模式_深入理解js中的观察者模式

javascript观察者模式又叫发布订阅模式,观察者模式的好处:js观察者模式支持简单的广播通信,自动通知所有已经订阅过的对象。存在一种动态关联,增加了灵活性。目标对象与观察者之间的抽象耦合关系能够单独扩展以及重用。

Vue中如何使用方法、计算属性或观察者

熟悉 Vue 的都知道 方法methods、计算属性computed、观察者watcher 在 Vue 中有着非常重要的作用,有些时候我们实现一个功能的时候可以使用它们中任何一个都是可以的

我最喜欢的 JavaScript 设计模式

我觉得聊一下我爱用的 JavaScript 设计模式应该很有意思。我是一步一步才定下来的,经过一段时间从各种来源吸收和适应直到达到一个能提供我所需的灵活性的模式。让我给你看看概览,然后再来看它是怎么形成的

Flutter 设计模式 - 简单工厂

在围绕设计模式的话题中,工厂这个词频繁出现,从 简单工厂 模式到 工厂方法 模式,再到 抽象工厂 模式。工厂名称含义是制造产品的工业场所,应用在面向对象中,顺理成章地成为了比较典型的创建型模式

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!