[ 国庆节不停更 ] 设计模式-迭代器模式

338 阅读6分钟

目录

  1. 迭代器模式介绍
  2. JS中的遍历
  3. 迭代器存在的价值
  4. ES6对迭代器的实现 - Iterator Symbol.iterator 属性
  5. 实现一个迭代器生成函数

一 迭代器模式介绍

迭代器模式提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露该对象的内部表示。 ——《设计模式:可复用面向对象软件的基础》

迭代器模式,它就解决这一个问题——遍历

二 JS中的遍历

往往针对,数组,对象,类数组,字符串,等等不同的数据类型,遍历时,我们需要使用不同的遍历方法. 比如 对象遍历的 7 种方法, 再比如,[JS基础回顾] 数组遍历的 10 种方法

比如:

1) for...of 不可遍历对象 却可以遍历数组

var obj = {
    a:1,
    b:2
};
for(item of obj){
    console.log(item)
}

image.png

报错,obj不是一个迭代器.

var arr = [11,2,3];

for(item of arr){
    console.log(item)
}
// 11  2  3

所以, 我们遍历不同的数据类型时,需要考虑,我们遍历的是什么数据类型. 那么, 迭代器不需要考虑数据类型,是更通用的遍历方案

看迭代器的定义是什么——遍历集合的同时,我们**不需要关心集合的内部结构。而forEach,for,map...只能做到允许我们不关心数组这一种集合的内部结构,看来想要一套统一的遍历方案,我们非得请出一个更强的通用迭代器**

三 迭代器存在的价值

1) 最早的迭代器?-JQ迭代器

jQuery的迭代器为我们统一了不同类型集合,比如: JQ对象集合, DOM对象(类数组)集合, 数组集合,的遍历方式,使我们在访问集合内每一个成员时不用去关心集合本身的内部结构以及集合与集合间的差异,这就是迭代器存在的价值~

ES6对迭代器的实现 - Iterator Symbol.iterator 属性

在ES6中新增了很多的特性,包括Map,Set等新的数据结构,算上数组和对象已经有四种数据集合了,就像数组可以使用forEach对象可以使用for...in 进行遍历一样,是不是随着Map和Set的出现也应该给这两种集合一个遍历方法呢?如果这样的话js的方法对象就太多了,既然这数组,对象,Map,Set 四种集合都是需要遍历的,那么完全可以用一个统一的访问机制。于是乎 Iterator 应运而生。

1) Symbol.iterator 属性for...of迭代器的next方法

ES6约定,任何数据结构只要具备 Symbol.iterator 属性(这个属性就是Iterator的具体实现它本质上是当前数据结构默认的迭代器生成函数),就可以被遍历——准确地说,是被for...of迭代器的next方法 遍历。 for...of的背后正是对next方法的反复调用

2) for...of 除了对象都可以遍历

是什么原因呢?我们看一下数组和对象实例的原型吧

image.png

image.png

在ES6中,针对Array、Map、Set、String、LikeArray、函数的 arguments 对象、NodeList 对象这些原生的数据结构都可以通过for...of进行遍历。原理都是一样的,此处我们拿最简单的数组进行举例,当我们用for...of遍历数组时:

3) for...of 遍历数组

var arr1 = [1, 2, 3]
var len = arr1.length
for(item of arr1) {
    console.log(`${item}`)
}

4) 为什么for...of可以遍历数组呢?

之所以能够按顺序一次一次地拿到数组里的每一个成员,是因为我们借助数组的Symbol.iterator生成了它对应的迭代器对象,通过反复调用迭代器对象的next方法访问了数组成员

5) 迭代器对象上的next, arrSymbol.iterator.next()

var arr1 = [1, 2, 3]
// 通过调用iterator,拿到迭代器对象
var iterator1 = arr[Symbol.iterator]()

// 对迭代器对象执行next,就能逐个访问集合的成员
iterator1.next()
iterator1.next()
iterator1.next() // {value: 3, done: false}
iterator1.next() // {value: undefined, done: true}

6) for...of 就是 iterator循环调用

var arr1=[2,3,5,6]
// 通过调用iterator,拿到迭代器对象
var iterator1 = arr1[Symbol.iterator]()

// 初始化一个迭代结果
let now = { done: false }

// 循环往外迭代成员
while(!now.done) {
    now = iterator1.next()
    if(!now.done) {
        console.log(now.value)
    }
}
// 2 3 5 6

五 实现一个迭代器生成函数

迭代器生成函数帮我们生成 迭代器对象. 我们通过生成器Generator)来生成迭代器对象

1) 用ES6生成器 编写一个迭代器生成函数

function *iteratorGenerator() {
    yield '100'
    yield '200'
    yield '300'
}

var iterator = iteratorGenerator()

iterator.next(); // {value: '100', done: false}
iterator.next(); // {value: '200', done: false}
iterator.next(); // {value: '300', done: false}
iterator.next(); // {value: undefined, done: true}

2) 用ES5去写一个能够生成迭代器对象迭代器生成函数

// 定义生成器函数,入参是任意集合
function iteratorGenerator(list) {
    // idx记录当前访问的索引
    var idx = 0
    // len记录传入集合的长度
    var len = list.length
    return {
        // 自定义next方法
        next: function() {
            // 如果索引还没有超出集合长度,done为false
            var done = idx >= len
            // 如果done为false,则可以继续取值
            var value = !done ? list[idx++] : undefined
            
            // 将当前值与遍历是否完毕(done)返回
            return {
                done: done,
                value: value
            }
        }
    }
}

var iterator = iteratorGenerator(['100', '200', '300'])

iterator.next(); // {value: '100', done: false}
iterator.next(); // {value: '200', done: false}
iterator.next(); // {value: '300', done: false}
iterator.next(); // {value: undefined, done: true}

此处为了记录每次遍历的位置,我们实现了一个闭包借助自由变量来做我们的迭代过程中的“游标”

迭代器模式比较特别,它非常重要,重要到语言和框架都争着抢着帮我们实现。但也正因为如此,大家业务开发中需要手动写迭代器的场景几乎没有,所以很少有同学会去刻意留意迭代器模式、思考它背后的实现机制。

参考

总结

  • 迭代器模式提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露该对象的内部表示
  • 迭代器模式,它就解决这一个问题——遍历
  • 我们遍历不同的数据类型时,需要考虑,我们遍历的是什么数据类型. 那么, 迭代器不需要考虑数据类型,是更通用的遍历方案
  • 使我们在访问集合内每一个成员时不用去关心集合本身的内部结构以及集合与集合间的差异,这就是迭代器存在的价值
  • 既然这数组,对象,Map,Set 四种集合都是需要遍历的,那么完全可以用一个统一的访问机制。于是乎 Iterator 应运而生。
  • ES6约定,任何数据结构只要具备 Symbol.iterator 属性(这个属性就是Iterator的具体实现它本质上是当前数据结构默认的迭代器生成函数),就可以被遍历
  • for...of的背后正是对next方法反复调用
  • for...of 可以遍历数组是因为我们借助数组的Symbol.iterator生成了它对应的迭代器对象,通过反复调用迭代器对象的next方法访问了数组成员
  • 迭代器生成函数 入参与返回 : 列表 -> {}
  • 迭代器生成函数 返回的对象有一个 next方法, next方法返回一个对象{done:x, value:x}