(十一)迭代器模式

96 阅读3分钟

@TOC

迭代器模式

  • 介绍
  • 演示
  • 场景
  • 总结

迭代器模式 介绍

  • 顺序访问一个集合

  • 使用者无需知道集合的内部结构(封装)

概念

这种模式用于顺序访问集合对象的元素,不需要知道集合对象的底层表示。

示例

没有找到合适的例子,就以一个常用的 jQuery 为示例吧。

<p>jquery each</p>
<p>jquery each</p>
<p>jquery each</p>

<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script>
<script>
    var arr = [1, 2, 3]
    var nodeList = document.getElementsByTagName('p')
    var $p = $('p')

    // 要对这三个变量进行遍历,需要写三个遍历方法
    // 第一
    arr.forEach(function (item) {
        console.log(item)
    })
    // 第二
    var i, length = nodeList.length
    for (i = 0; i < length; i++) {
        console.log(nodeList[i])
    }
    // 第三
    $p.each(function (key, p) {
        console.log(key, p)
    })
</script>

如何能写出一个方法来遍历这三种对象呢?

// 如何能写出一个方法来遍历这三种对象呢?
function each(data) {
    var $data = $(data) // 生成迭代器
    $data.each(function (key, p) {
        console.log(key, p)
    })
}

// 测试代码
each(arr)
each(nodeList)
each($p)

//顺序遍历有序集合
//使用者不必知道集合的内部结构

其中var $data = $(data)就是一个迭代器 ,它可以以统一的方式访问内部结构,但是不需要知道内部结构是什么。这就是迭代器模式的主要思想。

迭代器模式 演示

先看下传统的 UML 类图

在这里插入图片描述

简化之后的 UML 类图

在这里插入图片描述

代码演示如下。大家不必惊讶于:为何一个简单的数组不直接遍历而是费劲如此周折?其实:

  • 设计模式应用小 demo 本身就会变得更加复杂,让人费解,这很正常
  • 该示例是演示如何生成迭代器,故而将数组生成迭代器再遍历,而不是直接遍历
class Iterator {
    constructor(conatiner) {
        this.list = conatiner.list
        this.index = 0
    }
    next() {
        if (this.hasNext()) {
            return this.list[this.index++]
        }
        return null
    }
    hasNext() {
        if (this.index >= this.list.length) {
            return false
        }
        return true
    }
}

class Container {
    constructor(list) {
        this.list = list
    }
    getIterator() {
        return new Iterator(this)
    }
}

// 测试代码
let container = new Container([1, 2, 3, 4, 5])
let iterator = container.getIterator()
while(iterator.hasNext()) {
    console.log(iterator.next())
}

迭代器模式 场景

jQuery each

// 如何能写出一个方法来遍历这三种对象呢?
function each(data) {
    var $data = $(data) // 生成迭代器
    $data.each(function (key, p) {
        console.log(key, p)
    })
}

// 测试代码
each(arr)
each(nodeList)
each($p)

//顺序遍历有序集合
//使用者不必知道集合的内部结构

ES6 Iterator

ES6 Iterator为何存在?

  • ES6语法中,有序集合的数据类型已经有很多
  • Array Map Set String TypedArray 函数中的arguments NodeList
  • 需要有一个统一的遍历接口来遍历所有数据类型
  • (注意,object不是有序集合,可以用Map代替)

ES6 是什么?

  • 以上数据类型,都有[Symbol iterator]属性
  • 属性值是函数,执行函数返回一个迭代器,即Iterator类型
  • 这个迭代器就有next方法可顺序迭代子元素
  • 可运行Array.prototype[Symbol.iterator]来测试,在 chrome 控制台中运行Array.prototype[Symbol.iterator]看下,返回的是不是一个函数。

在这里插入图片描述

function each(data) {
    // 生成遍历器
    let iterator = data[Symbol.iterator]()

    // console.log(iterator.next())  // 有数据时返回 {value: 1, done: false}
    // console.log(iterator.next())
    // console.log(iterator.next())
    // console.log(iterator.next())
    // console.log(iterator.next())  // 没有数据时返回 {value: undefined, done: true}

    let item = {done: false}
    while (!item.done) {
        item = iterator.next()
        if (!item.done) {
            console.log(item.value)
        }
    }
}

// 测试代码
let arr = [1, 2, 3, 4]
let nodeList = document.getElementsByTagName('p')
let m = new Map()
m.set('a', 100)
m.set('b', 200)

each(arr)
each(nodeList)
each(m)

有一个问题,Symbol.iterator并不是人人都知道,而且这么让大家用,每个人都需要封装一个each函数,相当不人人性化 —— ES6 当然知道这个问题,于是就有了for...of语法。但是你要明白,这个语法最终依赖的还是 ES6 Iterator ,是基于迭代器模式的。

// `Symbol.iterator` 并不是人人都知道
// 也不是每个人都需要封装一个 each 方法
// 因此有了 `for...of` 语法
function each(data) {
	//for...in访问数组,for...of访问带有遍历特性的对象
	//带有遍历特性的对象,data[Symbol iterator]有值
    for (let item of data) {
        console.log(item)
    }
}

each(arr)
each(nodeList)
each(m)

说明: Object 不是有序即可,如果想要 Object 的遍历器功能,可以使用 Map

Iterator 的价值不限于上述几个类型的遍历 还有Generator的使用 即只要返回的数据符合 Iterator 接口的要求 即可使用Iterator 语法,这就是迭代器模式 在这里插入图片描述

function* helloWorldGenerator() {
  yield 'hello';
  yield 'world';
  return 'ending';
}

var hw = helloWorldGenerator();
hw.next()
hw.next()
hw.next()
hw.next()

当然也可以用for...of

function* foo() {
  yield 1;
  yield 2;
  yield 3;
  yield 4;
  yield 5;
  return 6;
}

// 当然也可以用`for...of`
for (let v of foo()) {
  console.log(v);
}

以上代码放在 nodejs 环境下运行) Generator 的语法和应用是另外一部分知识,此处就不展开讲解了,拿来只是为了理解迭代器模式的设计思想。

迭代器模式 总结

  • 什么是迭代器模式
  • 核心:是创建一个迭代器对象或者接口,用于迭代数据,使用者和具体数据分离
  • JS 中的场景

设计原则验证:

  • 迭代器对象和目标对象分离
  • 迭代器将使用者与目标对象隔离开
  • 符合开放封闭原则