资讯专栏INFORMATION COLUMN

ECMAScript6(14):iterator 迭代器

toddmark / 2649人阅读

摘要:由于中引入了许多数据结构算上原有的包括等等数组需要一个东西来管理他们这就是遍历器。数组默认遍历器遍历值相当于依次输出依次输出依次输出依次输出不难看出默认得到值而只能得到索引。即遍历器的本质就是一个指针。

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

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 遍历器遍历键值对

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 字符串

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 遍历过程是这样的:

创建一个指针对象, 指向当前数据结构的起始位置。即遍历器的本质就是一个指针。

调用一次指针的 next 方法, 指针指向第一数据成员。之后每次调用 next 方法都会将之后向后移动一个数据。

知道遍历结束。

我们实现一个数组的遍历器试试:

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++ 中的析构函数十分类似, 后者是在对象删除后做一些释放内存的工作, 防止内存泄露。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/97443.html

相关文章

  • ECMAScript6(13):Generator 函数

    摘要:函数可以没有返回值,此时它依然返回一个并且在调用方法时一次行执行完函数内全部代码,返回。将一个可遍历结构解构,并逐一返回其中的数据。 Generator Generator 函数是 es6 中的新的异步编程解决方案,本节仅讨论 Generator 函数本身,异步编程放在后面的部分。Generator 函数之前也提到过,描述内部封装的多个状态,类似一个状态机,当然也是很好的 iterat...

    wangxinarhat 评论0 收藏0
  • ECMAScript6学习笔记

    摘要:笔记和和是块作用域的,是声明常量用的。一个对象如果要有可被循环调用的接口,就必须在的属性上部署遍历器生成方法原型链上的对象具有该方法也可。这种方式会访问注册表,其中存储了已经存在的一系列。这种方式与通过定义的独立不同,注册表中的是共享的。 ECMAScript6 笔记 let 和 const let和const是块作用域的 ,const是声明常量用的。 {let a = 10;} a ...

    CODING 评论0 收藏0
  • python迭代与生成小结

    摘要:迭代器要说生成器,必须首先说迭代器区分与讲到迭代器,就需要区别几个概念看着都差不多,其实不然。比如常见就是与分离实现的本身是可迭代对象,但不是迭代器,类似与但是又不同。 2016.3.10关于例子解释的补充更新 源自我的博客 例子 老规矩,先上一个代码: def add(s, x): return s + x def gen(): for i in range(...

    hellowoody 评论0 收藏0
  • python奇遇记:迭代和生成

    摘要:来说说迭代器和生成器,还有可迭代对象和生成器表达式。有点绕是不是,其实,一般只要知道可迭代对象以及它是如何实现的就行了,中常常用生成器来代替迭代器,可以说,生成器就是迭代器。 来说说迭代器和生成器,还有可迭代对象和生成器表达式。 之前简单的提到过,一个对象是可迭代的可以理解为能够使用for循环。这样说其实不太准确,某个对象可迭代是因为它内部实现了$__iter__$这个特殊方法。比如在...

    atinosun 评论0 收藏0
  • ECMAScript6(16):异步编程

    摘要:异步编程程序执行分为同步和异步,如果程序每执行一步都需要等待上一步完成才能开始,此所谓同步。因此异步编程十分重要。 异步编程 程序执行分为同步和异步,如果程序每执行一步都需要等待上一步完成才能开始,此所谓同步。如果程序在执行一段代码的同时可以去执行另一段代码,等到这段代码执行完毕再吧结果交给另一段代码,此所谓异步。比如我们需要请求一个网络资源,由于网速比较慢,同步编程就意味着用户必须等...

    曹金海 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<