ECMAScript6(14):iterator 迭代器

因为 ES6 中引入了许多数据结构, 算上原有的包括Object, Array, TypedArray, DataView, buffer, Map, WeakMap, Set, WeakSet等等, 数组须要一个东西来管理他们, 这就是遍历器(iterator)。jquery

for...of

遍历器调用一般使用 for...of 循环, for...of 能够遍历具备 iterator 的对象, ES6中默认只有数组, Set, Map, String, Generator和一些类数组对象(arguments, DOM NodeList)带有遍历器, 其余的数据结构须要本身定义遍历器。数组

  • 数组

默认 for...of 遍历器遍历值安全

var arr = ["red", "green", "blue"];
for(let v of arr){    //至关于 for(let i in arr.values())
  console.log(v);     //依次输出 "red", "green", "blue"
}
for(let i in arr){
  console.log(i);     //依次输出 0, 1, 2
}
for(let [key, value] of arr.entries()){
  console.log(key + ": " + value);      //依次输出 0: "red", 1: "green", 2: blue"
}
for(let key of arr.keys()){
  console.log(key);     //依次输出 0, 1, 2
}

不难看出 for...of 默认获得值, 而 for...in 只能获得索引。固然数组的 for...of 只返回数字索引的属性, 而 for...in 没有限制:数据结构

var arr = ["red", "green", "blue"];
arr.name = "color";
for(let v of arr){
  console.log(v);     //依次输出 "red", "green", "blue"
}
for(let i in arr){
  console.log(arr[i]);     //依次输出 "red", "green", "blue", "color"
}
  • Set

默认 for...of 遍历器遍历值函数

var set = new Set(["red", "green", "blue"]);
for(let v of set){    //至关于 for(let i in arr.values())
  console.log(v);     //依次输出 "red", "green", "blue"
}
for(let [key, value] of set.entries()){
  console.log(key + ": " + value);      //依次输出 "red: red", "green: green", "blue: blue"
}
for(let key of set.keys()){
  console.log(key);     //依次输出 "red", "green", "blue"
}
  • map

默认 for...of 遍历器遍历键值对this

var map = new Map();
map.set("red", "#ff0000");
map.set("green", "#00ff00");
map.set("blue", "#0000ff");
for(let [key, value] of map){    //至关于 for(let i in arr.entries())
  console.log(key + ": " + value);      //依次输出 "red: #ff0000", "green: #00ff00", "blue: #0000ff"
}
for(let value of map.values()){
  console.log(value);     //次输出 "#ff0000", "#00ff00", "#0000ff"
}
for(let key of map.keys()){
  console.log(key);     //次输出 "red", "green", "blue"
}
  • 字符串

for...of能够很好的处理区分32位 Unicode 字符串prototype

var str = "Hello";
for(let v of str){
  console.log(v);     //依次输出 "H", "e", "l", "l", "o"
}
  • 类数组对象
// DOM NodeList
var lis = document.getElementById("li");
for(let li of lis){
  console.log(li.innerHTML);   //遍历每一个节点
}

//arguments
function fun(){
  for(let arg of arguments){
    console.log(arg);          //遍历每一个参数
  }
}

不是全部类数组对象都有 iterator, 若是没有, 能够先用Array.from()进行转换:指针

var o = {0: "red", 1: "green", 2: "blue", length: 3};
var o_arr = Array.from(o);
for(let v of o_arr){
  console.log(v);        //依次输出 "red", "green", "blue"
}
技巧1: 添加如下代码, 使 for...of 能够遍历 jquery 对象:
$.fn[Symbol.iterator] = [][Symbol.iterator];
技巧2: 利用 Generator 从新包装对象:
function* entries(obj){
  for(let key of Object.keys(obj)){
    yield [key, obj[key]];
  }
}
var obj = {
  red: "#ff0000",
  green: "#00ff00",
  blue: "#0000ff"
};
for(let [key, value] of entries(obj)){
  console.log(`${key}: ${value}`);        //依次输出 "red: #ff0000", "green: #00ff00", "blue: #0000ff"
}

几种遍历方法的比较

  • for 循环: 书写比较麻烦
  • forEach方法: 没法终止遍历
  • for...in: 仅遍历索引, 使用不便捷; 会遍历原型链上的属性, 不安全; 会遍历非数字索引的数组属性;
  • for...of:

iterator 与 [Symbol.iterator]

iterator 遍历过程是这样的:code

  1. 建立一个指针对象, 指向当前数据结构的起始位置。即遍历器的本质就是一个指针。
  2. 调用一次指针的 next 方法, 指针指向第一数据成员。以后每次调用 next 方法都会将以后向后移动一个数据。
  3. 知道遍历结束。

咱们实现一个数组的遍历器试试:对象

var arr = [1, 3, 6, 5, 2];
var it = makeIterator(arr);
console.log(it.next());       //Object {value: 1, done: false}
console.log(it.next());       //Object {value: 3, done: false}
console.log(it.next());       //Object {value: 6, done: false}
console.log(it.next());       //Object {value: 5, done: false}
console.log(it.next());       //Object {value: 2, done: false}
console.log(it.next());       //Object {value: undefined, done: true}

function makeIterator(arr){
  var nextIndex = 0;
  return {
    next: function(){
      return nextIndex < arr.length ?
        {value: arr[nextIndex++], done: false} :
        {value: undefined, done: true}
    }
  };
}

由这个例子咱们能够看出如下几点:

  • 迭代器具备 next() 方法, 用来获取下一元素
  • next() 方法具备返回值, 返回一个对象, 对象 value 属性表明下一个值, done 属性表示是否遍历是否结束
  • 若是一个数据结构自己不具有遍历器, 或者自带的遍历器不符合使用要求, 请按此例格式自定义一个遍历器。

其实一个 id 生成器就很相似一个遍历器:

function idGen(){
  var id = 0;
  return {
    next: function(){ return id++; }
  };
}
var id = idGen();
console.log(id.next());   //0
console.log(id.next());   //1
console.log(id.next());   //2
//...

对于大多数数据结构, 咱们不须要再像这样写遍历器函数了。由于他们已经有遍历器函数[Symbol.iterator], 好比Array.prototype[Symbol.iterator] 是数组结构的默认遍历器。

下面定义一个不完整(仅包含add()方法)的链表结构的实例:

function Node(value){
  this.value = value;
  this.next = null;
}
function LinkedList(LLName){
  this.head = new Node(LLName);
  this.tail = this.head;
}
var proto = {
  add: function(value){
    var newNode = new Node(value);
    this.tail = this.tail.next = newNode;
    return this;
  }
}
LinkedList.prototype = proto;
LinkedList.prototype.constructor = LinkedList;
LinkedList.prototype[Symbol.iterator] = function(){
  var cur = this.head;
  var curValue;
  return {
    next: function(){
      if(cur !== null){
        curValue = cur.value;
        cur = cur.next;
        return {value: curValue, done: false}
      } else {
        return {value: undefined, done: true}
      }
    }
  };
}

var ll = new LinkedList("prime");
ll.add(1).add(2).add(3).add(5).add(7).add(11);
for(let val of ll){
  console.log(val);    //依次输出 1, 2, 3, 5, 7, 11
}

注意, 若是遍历器函数[Symbol.iterator]返回的不是如上例所示结构的对象, 会报错。
固然, 若是不不喜欢用for...of(应该鲜有这样的人吧), 能够用 while 遍历:

var arr = [1, 2, 3, 5, 7];
var it = arr[Symbol.iterator];
var cur = it.next();
while(!cur.done){
  console.log(cur.value);
  cur = it.next();
}

如下操做会在内部调用相应的 iterator:

  • 数组的解构赋值
  • 展开运算符
  • yield* 后面带有一个可遍历结构
  • for...of
  • Array.from() 将类数组对象转换为数组
  • Map(), Set(), WeakMap(), WeakSet() 等构造函数传输初始参数时
  • Promise.all()
  • Promise.race()

Generator 与遍历器

iterator 使用 Generator 实现会更简单:

var it = {};
it[Symbol.iterator] = function* (){
  var a = 1, b = 1;
  var n = 10;
  while(n){
    yield a;
    [a, b] = [b, a + b];
    n--;
  }
}
console.log([...it]); //1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

固然, 以上代码还能够这样写:

var it = {
  *[Symbol.iterator](){
    var a = 1, b = 1;
    var n = 10;
    while(n){
      yield a;
      [a, b] = [b, a + b];
      n--;
    }
  }
}
console.log([...it]); //[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

遍历器对象的其余方法

以上的遍历器对象只提到了 next() 方法, 其实遍历器还有 throw() 方法和 return() 方法:

  • 若是遍历终止(break, continue, return或者出错), 会调用 return() 方法
  • Generator 返回的遍历器对象具throw() 方法, 通常的遍历器用不到这个方法。具体在 Generator 中解释。
function readlineSync(file){
  return {
    next(){
      if(file.isAtEndOfFile()){
        file.close();
        return {done: true};
      }
    },
    return(){
      file.close();
      return {done: true};
    }
  }
}

上面实现了一个读取文件内数据的函数, 当读取到文件结尾跳出循环, 可是当循环跳出后, 须要作一些事情(关闭文件), 以防内存泄露。这个和 C++ 中的析构函数十分相似, 后者是在对象删除后作一些释放内存的工做, 防止内存泄露。

相关文章
相关标签/搜索