ES6 之前的迭代器模式

JavaScript 在 Iterator 出现前如何实现迭代

问题

在 ES6 引入 Iterator 协议之前,JavaScript 是如何实现迭代器模式的?

解答

原始的 for 循环

最基础的迭代方式:

var arr = [1, 2, 3];

for (var i = 0; i < arr.length; i++) {
  console.log(arr[i]);
}

问题:暴露了索引细节,不够抽象。

回调式迭代器

jQuery 的 $.each 是经典实现:

// 简化版 $.each 实现
function each(collection, callback) {
  if (Array.isArray(collection)) {
    for (var i = 0; i < collection.length; i++) {
      // 返回 false 可中断迭代
      if (callback(i, collection[i]) === false) {
        break;
      }
    }
  } else {
    for (var key in collection) {
      if (collection.hasOwnProperty(key)) {
        if (callback(key, collection[key]) === false) {
          break;
        }
      }
    }
  }
}

// 使用
each([1, 2, 3], function(index, value) {
  console.log(index, value);
});

each({ a: 1, b: 2 }, function(key, value) {
  console.log(key, value);
});

内部迭代器

迭代逻辑完全封装在内部:

function forEach(arr, fn) {
  for (var i = 0; i < arr.length; i++) {
    fn(arr[i], i, arr);
  }
}

// ES5 原生支持
[1, 2, 3].forEach(function(item) {
  console.log(item);
});

外部迭代器

调用者控制迭代过程:

// 手动实现迭代器对象
function createIterator(arr) {
  var index = 0;
  
  return {
    hasNext: function() {
      return index < arr.length;
    },
    next: function() {
      return arr[index++];
    },
    reset: function() {
      index = 0;
    }
  };
}

// 使用
var iterator = createIterator([1, 2, 3]);

while (iterator.hasNext()) {
  console.log(iterator.next()); // 1, 2, 3
}

类 Java 风格迭代器

function Iterator(items) {
  this.items = items;
  this.cursor = 0;
}

Iterator.prototype.hasNext = function() {
  return this.cursor < this.items.length;
};

Iterator.prototype.next = function() {
  if (!this.hasNext()) {
    throw new Error('No more elements');
  }
  return this.items[this.cursor++];
};

// 使用
var it = new Iterator(['a', 'b', 'c']);
while (it.hasNext()) {
  console.log(it.next());
}

对比 ES6 Iterator

// ES6 迭代器协议
const modernIterator = {
  items: [1, 2, 3],
  [Symbol.iterator]() {
    let index = 0;
    const items = this.items;
    
    return {
      next() {
        if (index < items.length) {
          return { value: items[index++], done: false };
        }
        return { value: undefined, done: true };
      }
    };
  }
};

// 可以用 for...of
for (const item of modernIterator) {
  console.log(item);
}

关键点

  • 内部迭代器forEach$.each,简单但不够灵活
  • 外部迭代器:调用者控制,可暂停、可同时迭代多个集合
  • ES6 之前:没有统一协议,各库自行实现
  • ES6 Iterator:统一了 next() 返回 { value, done } 的协议
  • 核心价值:分离集合的遍历逻辑与数据结构