ES6 迭代器与可迭代对象的实现
ES6新的数组方法、集合、for-of循环、展开运算符(...)甚至异步编程都依赖于迭代器(Iterator)实现。本文会详解ES6的迭代器与生成器,并进一步挖掘可迭代对象的内部原理与使用方法
一、迭代器的原理
在编程语言中处理数组或集合时,使用循环语句必须要初始化一个变量记录迭代位置,而程序化地使用迭代器可以简化这种数据操作
如何设计一个迭代器呢?
迭代器的本身是一个对象,这个对象有next()方法返回结果对象,这个结果对象有下一个返回值value、迭代完成布尔值done,模拟创建一个简单迭代器如下:
functioncreateIterator(iterms){ leti=0 return{ next(){ letdone=(i>=iterms.length) letvalue=!done?iterms[i++]:undefined return{ done, value } } } } letarrayIterator=createIterator([1,2,3]) console.log(arrayIterator.next())//{done:false,value:1} console.log(arrayIterator.next())//{done:false,value:2} console.log(arrayIterator.next())//{done:false,value:3} console.log(arrayIterator.next())//{done:true,value:undefined}
对以上语法感到困惑的,可参考:【ES6】对象的新功能与解构赋值
每次调用迭代器的next()都会返回下一个对象,直到数据集被用尽。
ES6中迭代器的编写规则类似,但引入了生成器对象,更简单的创建迭代器对象
二、创建迭代器
ES6封装了一个生成器用来创建迭代器。显然生成器是返回迭代器的函数,这个函数通过function后的星号(*)表示,并使用新的内部专用关键字yield指定迭代器next()方法的返回值。
如何使用ES6生成器创建一个迭代器呢?一个简单的例子如下:
function*createIterator(){ yield123; yield'someValue' } letsomeIterator=createIterator() console.log(someIterator.next())//{value:123,done:false} console.log(someIterator.next())//{value:'someValue',done:false} console.log(someIterator.next())//{value:undefined,done:true}
使用yield关键字可以返回任意值或表达式,可以给迭代器批量添加元素:
//letcreateIterator=function*(items){//生成器函数表达式 function*createIterator(items){ for(leti=0;i由于生成器本身是函数,所以可添加到对象中,使用方式如下:
letobj={ //createIterator:function*(items){//ES5 *createIterator(items){//ES6 for(leti=0;i生成器函数的一个特点是,当执行完一句yield语句后函数会自动停止执行,再次调用迭代器的next()方法才会继续执行下一个yield语句。
这种自动中止函数执行的能力衍生出很多高级用法。
三、可迭代对象
在ES6中常用的集合对象(数组、Set/Map集合)和字符串都是可迭代对象,这些对象都有默认的迭代器和Symbol.iterator属性。
通过生成器创建的迭代器也是可迭代对象,因为生成器默认会为Symbol.iterator属性赋值。
3.1Symbol.iterator
可迭代对象具有Symbol.iterator属性,即具有Symbol.iterator属性的对象都有默认迭代器。
我们可以用Symbol.iterator来访问对象的默认迭代器,例如对于一个数组:
letlist=[11,22,33] letiterator=list[Symbol.iterator]() console.log(iterator.next())//{value:11,done:false}Symbol.iterator获得了数组这个可迭代对象的默认迭代器,并操作它遍历了数组中的元素。
反之,我们可以用Symbol.iterator来检测对象是否为可迭代对象:
functionisIterator(obj){ returntypeofobj[Symbol.iterator]==='function' } console.log(isIterator([11,22,33]))//true console.log(isIterator('sometring'))//true console.log(isIterator(newMap()))//true console.log(isIterator(newSet()))//true console.log(isIterator(newWeakMap()))//false console.log(isIterator(newWeakSet()))//false显然数组、Set/Map集合、字符串都是可迭代对象,而WeakSet/WeakMap集合(弱引用集合)是不可迭代的。
3.2创建可迭代对象
默认情况下,自定义的对象都是不可迭代的。
刚才讲过,通过生成器创建的迭代器也是一种可迭代对象,生成器默认会为Symbol.iterator属性赋值。
那如何将自定义对象变为可迭代对象呢?通过给Symbol.iterator属性添加一个生成器:
letcollection={ items:[11,22,33], *[Symbol.iterator](){ for(letitemofthis.items){ yielditem } } } console.log(isIterator(collection))//true for(letitemofcollection){ console.log(item)//112233 }数组items是可迭代对象,collection对象通过给Symbol.iterator属性赋值也成为可迭代对象。
3.3for-of
注意到上个栗子使用了for-of代替索引循环,for-of是ES6为可迭代对象新加入的特性。
思考一下for-of循环的实现原理。
对于使用for-of的可迭代对象,for-of每执行一次就会调用这个可迭代对象的next(),并将返回结果存储在一个变量中,持续执行直到可迭代对象done属性值为false。
//迭代一个字符串 letstr='somestring' for(letitemofstr){ console.log(item)//somestring }本质上来说,for-of调用str字符串的Symbol.iterator属性方法获取迭代器(这个过程由JS引擎完成),然后多次调用next()方法将对象value值存储在item变量。
将for-of用于不可迭代对象、null或undefined会报错!
3.4展开运算符(...)
ES6语法糖展开运算符(...)也是服务于可迭代对象,即只可以“展开”数组、集合、字符串、自定义可迭代对象。
以下栗子输出不同可迭代对象展开运算符计算的结果:
letstr='somestring' console.log(...str)//somestring letset=newSet([1,2,2,5,8,8,8,9]) console.log(set)//Set{1,2,5,8,9} console.log(...set)//12589 letmap=newMap([['name','jenny'],['id',123]]) console.log(map)//Map{'name'=>'jenny','id'=>123} console.log(...map)//['name','jenny']['id',123] letnum1=[1,2,3],num2=[7,8,9] console.log([...num1,...num2])//[1,2,3,7,8,9] letudf console.log(...udf)//TypeError:undefinedisnotiterable由以上代码可以看出,展开运算符(...)可以便捷地将可迭代对象转换为数组。同for-of一样,展开运算符(...)用于不可迭代对象、null或undefined会报错!
四.默认迭代器
ES6为很多内置对象提供了默认的迭代器,只有当内建的迭代器不能满足需求时才自己创建迭代器。
ES6的三个集合对象:Set、Map、Array都有默认的迭代器,常用的如values()方法、entries()方法都返回一个迭代器,其值区别如下:
- entries():多个键值对
- values():集合的值
- keys():集合的键
调用以上方法都可以得到集合的迭代器,并使用for-of循环,示例如下:
/********Map***********/ letmap=newMap([['name','jenny'],['id',123]]) for(letitemofmap.entries()){ console.log(item)//['name','jenny']['id',123] } for(letitemofmap.keys()){ console.log(item)//nameid } for(letitemofmap.values()){ console.log(item)//jenny123 } /********Set***********/ letset=newSet([1,4,4,5,5,5,6,6,]) for(letitemofset.entries()){ console.log(item)//[1,1][4,4][5,5][6,6] } /*********Array**********/ letarray=[11,22,33] for(letitemofarray.entries()){ console.log(item)//[0,11][1,22][2,33] }此外String和NodeList类型都有默认的迭代器,虽然没有提供其它的方法,但可以用for-of循环
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。