ES6 迭代器 iterator 介绍和使用

发布于 2022-10-21 22:18:50 字数 6307 浏览 123 评论 2

生成器概念在 Java,Python 等语言中都是具备的,ES6 也添加到了 JavaScript 中。Iterator 可以使我们不需要初始化集合,以及索引的变量,而是使用迭代器对象的 next 方法,返回集合的下一项的值,偏向程序化。

ES5 中遍历集合通常都是 for 循环,数组还有 forEach 方法,对象就是 for-in,而迭代器可以统一处理所有集合数据的方法。迭代器是一个接口,只要你这个数据结构暴露了一个 iterator 的接口,那就可以完成迭代。ES6 创造了一种新的遍历命令 for...of 循环,Iterator 接口主要供 for...of 消费。

ES5 中的 loop

for 循环

var arr = [1, 2, 3];
for(var i = 0, len = arr.length; i < len; i++) {
  console.log(arr[i]);
}

这是标准 for 循环的写法,字符串也支持,定义一个变量 i 作为索引,以跟踪访问的位置,len 是数组的长度,条件就是 i 不能超过 len。

这种写法看起来算是比较简单明了的,只不过 for 里面要写一大坨,而且当出现嵌套的时候,要定义多个变量去维护不同集合的索引,增加复杂度,容易出错。

forEach

forEach 是数组内置方法,写起来比较简洁,问题就是不能中断,跳出循环。而这个是我们经常会遇见的,达到某个条件就不需要往后遍历了。

var arr = [1, 2, 3];
arr.forEach(item => console.log(item))

for-in

常用来遍历对象,可以获得对象的 key值,但是只能提取 key,value 需要我们自己 obj[key] 的形式去访问。

 var obj = {
   name: 'syj',
   age: 24,
   sex: 'male',
   hobby: 'girl',
 }
   
for(var key in obj) {
  console.log(key);
}

什么是迭代器

迭代器是带有特殊接口的对象。含有一个 next() 方法,调用返回一个包含两个属性的对象,分别是 value 和 done,value 表示当前位置的值,done 表示是否迭代完,当为 true 的时候,调用 next 就无效了。

ES5 模拟一个迭代器

function createIterator(ary) {
  var i = 0;
  return {
    next: function() {
      return {
        value: ary[i++],
        done: i > ary.length
      }
    }
  }
}
var iterator = createIterator(['a', 'b', 'c'])
var done = false;

while (!done) {
  var result = iterator.next();
  console.log(result);
  done = result.done;
}
//{ value: 'a', done: false }
//{ value: 'b', done: false }
//{ value: 'c', done: false }
//{ value: undefined, done: true }

createIterator 可以看做一个返回迭代器对象的工厂函数,通过 while 循环调用返回的 iterator.next() 会得到 result,直到 done 变为 true。用 ES5 写还是比较麻烦的,而且我们这样写并不支持 for...of。很快就会看到 ES6 真正的写法啦。

timg1

迭代器协议 Iteration protocols

迭代器对象不是新的语法或新的内置对象,而一种协议(迭代器协议),所有遵守这个协议的对象,都可以称之为迭代器。也就是说我们上面ES5的写法得到的对象遵循迭代器协议,即包含 next,调用 next 返回一个 result{value, done}。

可迭代类型

ES6 还引入了一个新的 Symbol 对象,symbol 值是唯一的。定义了一个 Symbol.iterator 属性,只要对象中含有这个属性,就是可迭代的,可用于 for...of。在 ES6 中,所有的集合对象,包括数组,Map 和 Set,还有字符串都是可迭代的,因为他们都有默认的迭代器。

尝试使用 for...of

let ary = ['a', 'b', 'c']; //数组

let str = 'str'; //字符串

let set = new Set(); //Set
set.add('s');
set.add('s');
set.add('e');
set.add('t');

let map = new Map(); //Map
let o = {};
map.set('m', 'm');
map.set(o, 'object');

let obj = {   //对象
  name: 'syj',
  age: 24
}

function forOf(list) {
  for(var value of list) {
    console.log(value);
  }
}

forOf(ary); //a b c
forOf(str); // s t r
forOf(set); // s e t
forOf(map); //[ 'm', 'm' ], [ {}, 'object' ]
forOf(obj); //TypeError: list[Symbol.iterator] is not a function

通过结果可以看出,确实集合类型和字符串都可以用默认的 for...of 来迭代。但是对象却不可以,内部抛出一个错误,找不到迭代器的接口,证实了上面的言论。

也许你不了解 Set、Map、Set 通常是类似于数组的,无重复项的集合,Map 是类似于对象的,但是他的key可以是任何类型,增强版的“键值对”的数据结构。

可以访问默认的 iterator

数组中默认的 iterator 我们是可以访问的,用法是一样的。

let ary = ['a', 'b', 'c'];
let iterator = ary[Symbol.iterator]();
console.log(iterator.next()); //{ value: 'a', done: false }
console.log(iterator.next()); //{ value: 'b', done: false }
console.log(iterator.next()); //{ value: 'c', done: false }
console.log(iterator.next()); //{ value: undefined, done: true }

使对象可迭代

前面说了只要对象包含 [Symbol.iterator] 的属性,就可以通过 for...of 遍历。我们也可以在对象中添加该属性。

我们用两种方式

 const obj = {
   b: 2
 }
 const a = 'a'
 obj.a = 1;
 Object.defineProperty(obj, Symbol.iterator, {
   enumerable: false,
   writable: false,
   configurable: true,
   value: function () {
     const that = this;
     let index = 0;
     const ks = Object.keys(that);
     return {
       next: function() {
         return {
           value: that[ks[index++]],
           done: (index > ks.length)
         }
       }
     }
   }
 })
 for(const v of obj) {
   console.log(v); //  2 , 1
 }

通过 defineProperty 向 obj 对象中添加 [Symbol.iterator],我们在对应的 value 做的就是通过 Object.keys 取出它的 key,然后调用一次 next 就往后找一位,可以通过 next() 尝试一下。因为 obj 有了 [Symbol.iterator],for...of 可以找到,并且调用。

 const fibonacci = {
   [Symbol.iterator]: function () {
     let [pre, next] = [0, 1];
     return {
       next() {
         [pre, next] = [next, pre + next];
         return {
           value: next,
           done: next > 1000
         }
       }
     }
   }
 }
 
 for(var n of fibonacci) {
   console.log(n)
 }
 // 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610,987

通过直接声明给对象定义一个 iterator。这里是 iterator 经典的用法。当这个数据集合特别大,甚至无限的时候,我们要把它定义好,取出来都是很庞大的操作,这时候 iterator 的优势就很明显了。只有调用一次 next,才返回下一个值,不调用就没有。假如说我们给 done 没加限制条件,这个迭代器就永远没有 done=true,就会永远可以 next。为了防止 for...of ,调用 next 的时候,有可能让我的电脑卡死,限制在 1000 以内。

还有一些值得关注的点

拓展运算符与 iterator 关系密切

借用我们 fibonacci 的例子,只有含有 [Symbol.iterator] 属性的对象,才可以使用 ... 展开。

const fibonacci = {
   a: 'a',
   b: 'b',
}
console.log(...fibonacci); //TypeError:
//(var)[Symbol.iterator] is not a function

fibonacci[Symbol.iterator] = function () {
  let [pre, next] = [0, 1];
  return {
    next() {
      [pre, next] = [next, pre + next];
      return {
        value: next,
        done: next > 1000
      }
    }
  }
}    
console.log(...fibonacci);//

第一次 log 我们尝试把 fibonacci 使用... 展开,但是会报错。把这行 console 注释掉以后,给这个对象添加一个 Symbol.iterator 属性,再次使用... ,就会得到之前的斐波那契数列了。 由于 ES6 的数组等集合默认有 Symbol.iterator 属性,所以我们都是可以直接使用扩展运算符。

类数组

ES6 中对于类数组,也添加了默认迭代器,比如 NodeList,它和数组的默认迭代器的用法是一样的。这意味着你可以使用 for...of 循环或在任何对象默认迭代器的内部来迭代 NodeList

var divs = document.getElementsByTagName("div");

for (const element of divs) {
  console.log(element.id);
}

总结

ES6 提出迭代器的概念,契合了JS语言发展的趋势,统一处理数据结构。迭代器是 ES6 中很重要的部分,我们仅仅使用是很方便的,但是自定义一些 iterator,或者更复杂的方式运行迭代器,还需要我们继续学习。

而定义一个对象的迭代器,又与 Symbol 对象有关,它采用了 Symbol 里面的一个默认属性 iterator,用来访问对象的迭代器。最后可迭代的数据类型,我们都可以用 for...of 方法循环遍历,而且集合和字符串内置迭代器,我们可以轻松方便的访问。拓展运算符也是基于 iterator 的拓展,通过...我们可以把其他数据类型转化为数组,因为...通过执行迭代器,并读取返回的 value。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

傾旎 2022-05-02 12:59:49

@liuyiliuyi 是的,感谢... 已修改。我也不知道我当时为啥写了这句话- -

负佳期 2022-05-02 08:22:21

for in “其他数组等类型都不行。“那一块写错了吧, 数组也是可以for in 的, 可以取到对应的索引。

~没有更多了~

关于作者

0 文章
0 评论
23 人气
更多

推荐作者

lorenzathorton8

文章 0 评论 0

Zero

文章 0 评论 0

萧瑟寒风

文章 0 评论 0

mylayout

文章 0 评论 0

tkewei

文章 0 评论 0

17818769742

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文