JavaScript中的迭代器模式

86 阅读3分钟

1.定义

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

2.迭代器

迭代器就是一个数据可以调用这个迭代器,他会把这个数据里面的每个元素顺序的返回出来,例如Array.prototype.forEach

3.实现一个迭代器

实现一个each函数,接受两个参数,第一个为循环的数组,第二个为循环后调用的回调函数

var each = function (arr, callback) {
  for (let i = 0, l = arr.length; i < l; i++) {
    callback.call(arr[i], i, arr[i]);
  }
};

each([1, 2, 3], function (i, item) {
  console.log(i, item);
});

4.内部迭代器和外部迭代器

使用迭代器比较两个数组元素是否完全相等

内部迭代器

each 函数属于内部迭代器,each 函数的内部已经定义好了迭代规则,它完全接手整个迭代过程,外部只需要一次初始调用,缺点是不能控制迭代规则,如果要同时迭代两个数组就不行了。

var compare = function (arr1, arr2) {
  each(arr1, function (i, n) {
    if (n !== arr2[i]) {
      throw new Error("arr1和arr2不相等");
    }
  });
};
compare([1, 2, 3], [1, 2, 4]); // arr1和arr2不相等

外部迭代器

外部迭代器必须显式地请求迭代下一个元素。 外部迭代器增加了一些调用的复杂度,但相对也增强了迭代器的灵活性,我们可以手工控制迭代的过程或者顺序

// 外部迭代器
var Iterator = function (obj) {
  var current = 0;
  var next = function () {
    current += 1;
  };
  var isDone = function () {
    return current >= obj.length;
  };
  var getCurrentItem = function () {
    return obj[current];
  };
  return {
    next,
    isDone,
    getCurrentItem,
  };
};

var compare2 = function (iterator1, iterator2) {
  while (!iterator1.isDone() &amp;&amp; !iterator2.isDone()) {
    if (iterator1.getCurrentItem() !== iterator2.getCurrentItem()) {
      throw new Error("arr1和arr2不相等");
    }
    iterator1.next();
    iterator2.next();
  }
};

compare2(Iterator([1, 2, 3]), Iterator([1, 2, 4])); // arr1和arr2不相等

5.其他迭代器

迭代类数组和子面量对象

迭代器不止可以迭代数组,还可以迭代类数组如(arguments),只要被迭代的聚合对象拥有length属性而且可以用下标访问,那么它就可以被迭代

还可以通过for in去迭代对象

倒叙迭代器

由于 GoF 中对迭代器模式的定义非常松散,所以我们可以有多种多样的迭代器实现。总的来说, 迭代器模式提供了循环访问一个聚合对象中每个元素的方法,但它没有规定我们以顺序、倒序还是中序来循环遍历聚合对象

var reverseEach = function (ary, callback) {
  for (var l = ary.length - 1; l >= 0; l--) {
    callback.call(ary[l], l, ary[l]);
  }
};
reverseEach([1, 2, 3], function (i, item) {
  console.log(i, item);
});

中止迭代器

迭代器可以像普通for 循环中的break 一样,提供一种跳出循环的方法

var each = function (arr, callback) {
  for (let i = 0, l = arr.length; i < l; i++) {
    if (callback.call(arr[i], i, arr[i]) === false) {
      break // 跳出循环
    }
  }
};
each([1, 2, 3], function (i, item) {
  if (item > 2) {
    return false;
  }
  console.log(i, item);
});

6.迭代器模式的应用

业务场景:在不同浏览器环境下,选择上传的方式不一样,判断当前环境并获取对应上传组件

未使用迭代器模式写法

var getUploadObj = function () {
  try {
    return new ActiveXObject("TXFTNActiveX.FTNUpload"); // IE 上传控件
  } catch (e) {
    if (supportFlash()) {
      // supportFlash 函数未提供
      var str = '<object  type="application/x-shockwave-flash"></object>';
      return $(str).appendTo($("body"));
    } else {
      var str = '<input name="file" type="file"/>'; // 表单上传
      return $(str).appendTo($("body"));
    }
  }
};

解析 这个getUploadObj 函数里面充斥了try,catch以及if 条件分支,有这些缺点

  • 很难阅读
  • 严重违反开放-封闭原则,函数耦合性太高,导致形成了脆弱和低内聚设计,如果要再加一种情况,又要改变getUploadObj函数

所以我们可以把获取upload对象的方法都封装在各自的函数中,然后通过一个迭代器,迭代获取这些upload对象,直到获取一个可用的为止

使用迭代器模式写法

var getActiveXObject = function () {
  try {
    return new ActiveXObject("TXFTNActiveX.FTNUpload");
  } catch {
    return false;
  }
};

var getSupportFlash = function () {
  if (supportFlash()) {
    // supportFlash 函数未提供
    var str = '<object  type="application/x-shockwave-flash"></object>';
    return $(str).appendTo($("body"));
  }
  return false;
};

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

// 迭代器模式获取upload对象
var iteratorUploadObj = function () {
  for (var i = 0, fn; (fn = arguments[i++]); ) {
    var uploadObj = fn();
    if (uploadObj !== false) {
      return uploadObj;
    }
  }
};
var uploadObj = iteratorUploadObj(
  getActiveXObject,
  getSupportFlash,
  getFormUploadObj
);

解析 重构代码之后,我们可以看到,获取不同上传对象的方法被隔离在各自的函数里互不干扰,try、catch 和 if 分支不再纠缠在一起,使得我们可以很方便地的维护和扩展代码。 如果之后需要添加其他的获取upload对象方法只需要

var getHtmlUploadObj = function  () {
}
var getWebkitUploadObj = function () {}

var uploadObj = iteratorUploadObj( getActiveUploadObj, getFlashUploadObj, getFormUpladObj );

这样子降低了函数的耦合性,也符合了开放-封闭原则

总结

迭代器模式是一种相对简单的模式,简单到很多时候我们都不认为它是一种设计模式。目前的绝大部分语言都内置了迭代器。