设计模式之迭代器模式在前端的应用

·  阅读 329

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第5天,点击查看活动详情

迭代器模式在实际的应用场景不多,因为常用的JS“数据集合”已经实现了迭代器。但是在框架层面,如果实现了某种特定场景下的功能而设计了一种新的数据结果,我们又想使用for...of,那就要自己实现迭代器了。尽管实际应用场景不多,但是我觉得还是很有必要学习一下迭代器模式。

迭代器模式

在学习迭代器模式之前,我们需要弄明白两个基础概念,虽然基础,但是未必大部分人都能理解的很到位。

  • 迭代(iterate) - 按顺序访问线性结构中的每一项(只能顺序依次访问);
  • 遍历(traversal) - 按规则访问非线性结构中的每一项(可访问其中一段区间的元素);

基础

迭代器模式是指提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。迭代器模式可以把迭代的过程从业务逻辑中分离出来,在使用迭代器模式之后,即使不关心对象的内部构造,也可以按顺序访问其中的每个元素。

ES6实现迭代器的例子:

class Container {
    constructor(list){
        this.list = list;
    }

    getIterator() {
        return new Iterator(this);
    }
}

class Iterator {
    constructor(container){
        this.list = container.list;
        this.index = 0;
    }

    next(){
        let done = this.index >= this.list.length;

        let res = {
            value: this.list[this.index],
            done: done
        };

        if(!done) {
            this.index++;
        }
        
        return res;
    }
}

// 测试代码
let arr = [5,6,9,88];
let container = new Container(arr);
let it = container.getIterator();

let tmp = it.next();
while(!tmp.done){
    console.log(tmp.value);
    tmp = it.next();
}
复制代码

其实上面的代码会有点奇怪,既然使用了array数组,那我们直接for,或者while循环不就可以实现我们想要的功能了吗? 是的,没错,的确可以实现。但是只是人家给你封装好了迭代器,你直接使用就行了,不用关心内部怎么实现,但是如果不允许你使用js中的这些集合,比如array,map,set,array-like,你可以手动实现一个迭代器把集合中的元素都迭代出来吗

实现目标

先来实现一个小目标,实现我们自己的迭代器。

目标:

  • 实现自己的迭代器,把集合中的元素都迭代出来;
  • 自己实现的迭代器要支持for(let item of iterator);
// 迭代器只要把集合中的元素都迭代出来就行;
// 迭代器不只可以迭代出来元素,还要支持下面的语法
for(let item of iterator){
   ...
}
复制代码

迭代器中的集合必须是有序的集合,所以我们可以用链表来实现,第一步,我们先来实现一下把集合中的元素迭代出来,下一步再考虑怎么支持for...of。

小目标1

// 有序集合
class ListNode {
    constructor(val){
        this.val = val;
        this.next = null;
    }
}

// 2-6-8-5
let head = new ListNode(2);
let n1 = new ListNode(6);
let n2 = new ListNode(8);
let n3 = new ListNode(5);
head.next = n1;
n1.next = n2;
n2.next = n3;

class Container {
    constructor(list){
        this.list = list;
    }

    getIterator(){
        return new Iterator(this);
    }
}

class Iterator{
    constructor(container){
        this.list = container.list;
        this.prev = this.list;
    }

    next(){
        let done = (this.prev == null);

        let res = {
            value: done ? null : this.prev.val,
            done
        };

        if(!done) {
            this.prev = this.prev.next;
        }

        return res;
    }
}

let container = new Container(head);
let it = container.getIterator();

let temp = it.next();
while(!temp.done){
    console.log(temp.value);
    temp = it.next();
}
复制代码

小目标2

思考:
现在有一个数组,需要把数组中的元素都遍历出来,但是有个条件,就是不能通过数组的下标来访问元素,也不能通过for语句来实现循环,请问怎么把数组中的元素都遍历出来?

了解Symbol:

let obj = {
  name: 'David',
  age: 20,
  [Symbol('my symbol')]: 666
};
console.log(obj, Object.keys(obj));
复制代码

用内置迭代器来实现:

let pro = Object.getOwnPropertySymbols(arr.__proto__)[0];
let it = arr[pro]();

let tmp = it.next();
while (!tmp.done) {
  console.log(tmp.value);
  tmp = it.next();
}

// 也可以用for...of
for (let item of it) {
  console.log(item);
}
复制代码

有没有发现上面的代码(next,value,done)有点眼熟啊?是不是和之前说的generator函数就联系起来了,之前说过generator函数返回的正是一个迭代器。

class Iterator {
  constructor(container) {
    this.list = container.list;
    this.prev = this.list;
  }

  [Symbol.iterator]() {
    let _this = this;
    return {
      next() {
        let done = (_this.prev == null);

        let res = {
          value: done ? null : _this.prev.val,
          done
        };

        if (!done) {
          _this.prev = _this.prev.next;
        }

        return res;
      }
    };
  }
}

let container = new Container(head);
let it = container.getIterator();

for (let item of it) {
  console.log(item);
}
复制代码

大功告成!

总结

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

应用场景:

不同数据结构类型的 “数据集合”,需要对外提供统一的遍历接口,而又不暴露或修改内部结构时,可应用迭代器模式实现。比如在用for...of遍历集合的时候,我并不关心集合是array,map,set,也不关心他们的内部是怎么实现迭代器的。 \实际应用的场景不多,是因为常用的js的“数据集合”已经实现好了迭代器。但是如果在框架层面,如果实现了一种新的数据结构,又想使用for...of,那就必须自己实现迭代器。

分类:
前端
标签:
收藏成功!
已添加到「」, 点击更改