我是靠谱客的博主 爱笑音响,最近开发中收集的这篇文章主要介绍设计模式之迭代器模式,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

简介

相关概念

迭代器模式提供一种方法顺序访问一个聚合对象(将多个对象聚合在一起形成的总体)中的各个元素,而又不暴露该对象的内部表示,其本质是抽离集合对象迭代行为到迭代器中,对外提供一致的访问接口;

迭代器和循环不是等价的,循环是迭代器的基础,迭代器是按照指定的顺序进行多次重复的循环某一程序,但是会有明确的终止条件;

遍历是指按照规定访问非线性结构中的每一项 - 可以访问某一个区间段内的元素,而迭代只能按顺序依次访问;

迭代器模式可以把迭代的过程从业务逻辑中分离出来,在使用迭代器模式之后,即使不了解对象的内部构造,也可以按照顺序访问其中每个元素;

迭代器模式在生活中常见的有:快递传送带和地铁扫码进站等;
快递传送带:不用关注快递内部是什么物品,只需要将快递进行打包贴码即可,实现解耦的效果;
地铁扫码:不用关注扫码人的个人信息(性别、体征等),只需要实现扫码进站即可;

类图

在这里插入图片描述

迭代器模式应该具备的角色信息
  • 抽象迭代器(Iterator):抽象迭代器负责定义访问和遍历元素的接口。
  • 具体迭代器(ConcreteIterator):提供具体的元素遍历行为。
  • 抽象容器(IAggregate):负责定义提供具体迭代器的接口。
  • 具体容器(ConcreteAggregate):创建具体迭代器。

特点

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

分类

  1. 内部迭代器 (jQuery 的 $.each / for…of)
  2. 外部迭代器 (ES6 的 yield)
内部迭代器

内部定义迭代规则,控制整个迭代规则,外部只需一次初始调用即可

内部迭代器在调用的时候非常方便,外部只需要进行一次初始化即可,不用关心内部的实现,但正因为内部提前定义了迭代规则,所以无法较为方便的进行相关拓展、无法满足复杂遍历的需求,确实灵活性,可以在回调函数里进行一些操作;

function each(ary, callback) {
  for (let i = 0; i < ary.length; i++) {
    callback.call(ary[i], i, ary[i]);
  }
}
each([1, 2, 3], (i, n) => console.log([i, n]));
复制代码
外部迭代器

外部显示的进行控制迭代你下一个数据项

外部迭代器必须显示的请求迭代下一个元素,增加了一些遍历调用的复杂度,但因为需要显示的控制迭代逻辑,因此相对灵活一些,可以手动控制迭代过程或者迭代顺序;

外部迭代器需要提供的API
  • 访问下一个元素的方法 next()
  • 当前遍历是否结束 isDone()
  • 获取当前元素 getCurrentItem()
const iterator = function (obj) {
  let current = 0;

  const next = () => (current += 1);

  const isDone = () => current >= obj.length;

  const getCurrItem = () => {
    return obj[current];
  };

  const getCurrIndex = () => {
    return current;
  };

  return {
    next,
    isDone,
    getCurrItem,
    getCurrIndex,
    length: obj.length,
  };
};

let test = iterator(["a", "b", "c"]);

console.log(test.length," length");
while (!test.isDone()) {
  console.log(test.getCurrItem()," -> ",test.getCurrIndex());
  test.next();
}

// 3  length
// a  ->  0
// b  ->  1
// c  ->  2
复制代码
迭代器对比
  • 内部迭代器调用方式上简单,和外部的使用也只进行一次初始化即可,但是在灵活性上欠缺,适合较为简单的迭代场景
  • 外部迭代器相对于内部迭代器来说调用方式上复杂了许多,但在灵活度上来说有了明显提升,可以满足更多多边的需求;
  • 两者没有绝对的优劣之分,需要根据特定的需求场景来说; 迭代器模式不仅可以迭代数组,还可以迭代类数组对象,只要被迭代的聚合对象有length属性且可以用下标访问,就可以被迭代
  if (isArray) {
    // 迭代类数组
    for (; i < length; i++) {
      value = callback.call(obj[i], i, obj[i]);
      if (value === false) {
        break;
      }
    }
  } else {
    // 迭代object 对象
    for (i in obj) {
      value = callback.call(obj[i], i, obj[i]);
      if (value === false) {
        break;
      }
    }
  }
  return obj;
};
复制代码

应用场景

  1. 访问一个集合对象的内容,无需暴露其内部表示;
  2. 为遍历不同对的集合结构提供一个统一的访问接口;
  3. 需要为聚合对象提供多种遍历方式

是否符合设计原则

  • 使用者和目标分离,解耦
  • 目标能自行控制内部逻辑
  • 使用者不关心目标和内部结构

相关应用

封装上传检测函数,用于适配不同浏览器对上传功能的支持度

常规情况下需要封装不同的兼容函数到不同环境中,并且需要做错误处理,当采用迭代器模式将不同的上传方式分别定义后再进行统一管理,这时外部只需要调用指定暴露的接口即可,无需关注内部实现细节,同时也方便后续的拓展与维护;

const getActiveUploadObj = function () {
    try {
        return new ActionXObject('TXFTNActiveX.FTNUpload')
    } catch (e) {
        return false
    }
}

const getFlashUploadObj = function () {
    if (supportFlash()) { // 未提供这个函数, 是否支持flash
        const str = `<object type="application/x-shockwave-flash"></object>`
        return $(str).appendTo($('body'))
    }
    return false
}

const getFormUploadObj = function () {
    const str = '<input name="file" type="file" />' // 表单上传
    return $(str).appendTo($('body'))
}


// 这三个函数都有同一个约定, 如果该函数里面的upload对象是可用的,则让函数返回该对象, 否则返回false, 提示迭代器继续迭代

// 1.提供一个可以被迭代的方法, 使用三个方法 依照优先级被循环迭代
// 2. 如果正在被迭代的函数返回一个函数,则表示找到了upload的对象, 反之函数返回false,则继续迭代

const iteratorUploadObj = function () {
    for (let i = 0, fn; fn = arguments[i++]) {
        const uploadObj = fn()
        if (uploadObj !== false) {
            return uploadObj
        }
    }
}

const uploadObj = iteratorUploadObj(getActiveUploadObj, getFlashUploadObj, iteratorUploadObj)

// 后续拓展
// const getWebkitUploadObj = function () {
//     // ...代码略
// }
// const getHtml5UploadObj = function () {
//     // ...代码略
// }

// // 依靠优先级添加进迭代器
// const uploadObj = iteratorUploadObj(getActiveUploadObj, getFlashUploadObj, iteratorUploadObj, getWebkitUploadObj, getHtml5UploadObj)
复制代码

code by

for…of…的内部interator实现可以迭代类数组对象的功能,使用for…of…的时候会调用Symbol.iterator接口

「 一种数据结构只要定义了 Iterator 接口,我们就称这种数据结构是可遍历的 」

js中的Iterator接口定义在数据结构的Symbol.iterator属性上,可以通过调用原生的Symbol.iterator方法返回一个遍历器对象,该对象就包含next等方法属性,放回当前对象的信息;

let arr = ['a', 'b', 'c'];
let iter = arr[Symbol.iterator](); // 返回一个遍历器对象
iter.next() // { value: 'a', done: false }
iter.next() // { value: 'b', done: false }
iter.next() // { value: 'c', done: false }
iter.next() // { value: undefined, done: true }
复制代码

interator遍历的时候会先生成一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。这个对象里面有一个next方法,然后调用该next方法,移动指针使得指针指向数据结构中的第一个元素。每调用一次next方法,指针就指向数据结构里的下一个元素,这样不断的调用next方法就可以实现遍历元素的效果了!另外每次调用next方法,都会返回数据结构的当前成员的信息。具体来说,就是返回一个包含valuedone两个属性的对象。其中,value属性是当前成员的值,done属性是一个布尔值,表示遍历是否结束,没有结束返回false,结束为true

function myIteration(arr) {
  let index = 0;
  return {
    next: function () {
      return index < arr.length ?
        { value: arr[index++], done: false } :
        { value: undefined, done: true }
    }
  }
}
var test = myIteration([1, 2])
console.log(test.next()) //{ "value": 1, "done": false }
console.log(test.next()) //{ "value": 2, "done": false }
console.log(test.next()) //{ "value": undefined, "done": true }
复制代码
调用
  • generator函数、解构赋值、拓展运算符、Array.from()、Promise.all()等
let iterator = {
  [Symbol.iterator]: function* () {
    yield 'a';
    yield 'b';
    yield 'c';
  }
};
console.log([...iterator]) // [a, b, c]

for (let i of iterator){
  console.log(i)  // [a, b, c]
}
复制代码
更改原生及对象,使其支持迭代
// let obj = {
//   'name': 'Lbxin',
//   'age': '20'
// }
// for (let i of obj) {
//   console.log(i) //TypeError: obj is not iterable , obj不是可迭代对象
// }

// 添加 Symbol.iterator 接口 使其支持迭代
let obj = {
  data: ['name: Lbxin', 'age: 20'],
  [Symbol.iterator]: function () {
    const _this = this
    let index = 0;
    return {
      next: function () {
        if (index < _this.data.length) {
          return {
            value: _this.data[index++],
            done: false
          };
        }
        return { value: undefined, done: true };
      }
    }
  }
}
for (let i of obj) {
  console.log(i)
}
// name: Lbxin
// age: 20


// const obj = {
//     arr : [1,2,3,4],
//     *[Symbol.iterator](){
//         yield* this.arr;
//     }
// }

// // 1 2 3 4
// for(let i of obj){
//     console.log(i);
// }
复制代码

原生js具备interator接口得到数据结构有

  • Array
  • Map
  • Set
  • String
  • TypedArray
  • 函数的 arguments 对象
  • NodeList 对象

通用迭代器封装

class Iterator {
  constructor(data) {
    this.data = data
    this.index = 0
  }

  next() {
    const list = this.data.list
    const flag = this.hasNext()
    if (flag) {
      return { value: list[this.index++], flag }
    } else {
      return { value: undefined, flag }
    }
  }

  hasNext() {
    const lg = this.data.list.length
    if (this.index >= lg) return false
    else return true
  }
}

class data {
  constructor(list) {
    this.list = list
  }

  getIterator() {
    return new Iterator(this)
  }
}
复制代码

优缺点分析

优点

  • 为不同的聚合对象提供一致的遍历接口
  • 将集合对象的具体迭代行为接口抽离到迭代器中,简化集合对象的逻辑
  • 同一个集合对象可以有不同的迭代行为
  • 将集合对象与迭代接口抽离解耦,各自的变化不会相互影响

缺点

  • 对于比较简单的对象遍历,使用迭代器反而繁琐了
  • 因为将对象数据和迭代接口抽离,增加新的内存去存储对应的逻辑,在一定程度上增加了系统的复杂性和存储成本

最后

以上就是爱笑音响为你收集整理的设计模式之迭代器模式的全部内容,希望文章能够帮你解决设计模式之迭代器模式所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(59)

评论列表共有 0 条评论

立即
投稿
返回
顶部