设计模式-迭代器模式

75 阅读2分钟

提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部结构,迭代即遍历,作为一种相对简单的模式,绝大多数语言都内置了迭代器。

  • 内部迭代器

最常见的如数组的forEach方法,你可以通过该方法遍历数组内的每一个元素:

[1, 2, 3].forEach((num) => {
    console.log(num);
});

我们自己动手实现一下,该迭代器方法满足下面两个要求:

  1. 接收两个参数,一个数组,一个回调函数;
  2. 调用该迭代器,需提供有效的回调函数,参数为数组内的每一项;
function each(array, callback) {
    for (let i = 0; i < array.length; i++) {
        callback(array[i]);
    }
}

each([1, 2, 3], (num) => {
    console.log(num);
});

至此,一个简单的迭代器模型已经完成,那么在此基础上,我们试着改造一下内部迭代器的逻辑:

  • 中止迭代器
function each(array, callback) {
    for (let i = 0; i < array.length; i++) {
        if (callback(array[i]) === false) {
            break;
        }
    }
}

each([1, 2, 3], (num) => {
    if (num > 1) {
        return false;
    }
    console.log(num);
});

我们在迭代器内部,对于callback的返回值加了一层判断,如果返回false,那么跳过本地循环,这种模式,又称为中止迭代器。

  • 外部迭代器

上面这两种迭代器,迭代规则都是被提前规定,外部调用不能关心迭代器内部实现,还有一种外部迭代器,它具有以下特点:

  1. 必须显示地请求迭代下一个元素;
  2. 增加了一些调用的复杂度,可以手工控制迭代的过程或者顺序;
function outIterator(obj) {
    ...
    return {
        next,
        isDone,
        getCurentItem,
        length,
    };
}

首先抛开内部实现,它返回以下内容:

  1. 迭代器的手动步进next方法;
  2. 迭代器是否结束的查询isDone方法;
  3. 获取当前迭代元素;
  4. 当前迭代对象的长度

基于上述四点需求,我们简单实现一个外部迭代器:

function outIterator(obj) {
    const length = obj.length;
    let curIdx = 0;
    const next = () => {
        curIdx += 1;
    };
    const isDone = () => {
        return curIdx >= obj.length - 1;
    };
    const getCurentItem = () => {
        return obj[curIdx];
    };
    return {
        next,
        isDone,
        getCurentItem,
        length,
    };
}

let list = [1, 2, 3];

const outCtx = outIterator(list);

console.log(outCtx.getCurentItem()); // 1
outCtx.next();
outCtx.next();
console.log(outCtx.getCurentItem()); // 3
console.log(outCtx.isDone());        // true

虽然大部分能用到迭代器的地方,语言层面上内置的迭代器方法已经满足开发需要,但是通过了解迭代器模式,会为了让我们对于迭代的原理和实现有更清晰的认识和理解。