ES6 迭代器与可迭代对象的实现


Posted in Javascript onFebruary 11, 2019

ES6 新的数组方法、集合、for-of 循环、展开运算符(...)甚至异步编程都依赖于迭代器(Iterator )实现。本文会详解 ES6 的迭代器与生成器,并进一步挖掘可迭代对象的内部原理与使用方法

、迭代器的原理

在编程语言中处理数组或集合时,使用循环语句必须要初始化一个变量记录迭代位置,而程序化地使用迭代器可以简化这种数据操作

如何设计一个迭代器呢?

迭代器的本身是一个对象,这个对象有 next( ) 方法返回结果对象,这个结果对象有下一个返回值 value、迭代完成布尔值 done,模拟创建一个简单迭代器如下:

function createIterator(iterms) {
 let i = 0
 return {
  next() {
   let done = (i >= iterms.length)
   let value = !done ? iterms[i++] : undefined
   return {
    done,
    value
   }
  }
 }
}

let arrayIterator = 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() {
 yield 123;
 yield 'someValue'
}

let someIterator = 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关键字可以返回任意值或表达式,可以给迭代器批量添加元素:

// let createIterator = function *(items) { // 生成器函数表达式
function *createIterator(items) {
 for (let i = 0; i < items.length; i++) {
  yield items[i]
 }
}

let someIterator = createIterator([123, 'someValue'])

console.log(someIterator.next()) // { value: 123, done: false }
console.log(someIterator.next()) // { value: 'someValue', done: false }
console.log(someIterator.next()) // { value: undefined, done: true }

由于生成器本身是函数,所以可添加到对象中,使用方式如下:

let obj = {
 // createIterator: function *(items) { // ES5
 *createIterator(items) { // ES6
  for (let i = 0; i < items.length; i++) {
   yield items[i]
  }
 }
}
let someIterator = obj.createIterator([123, 'someValue'])

生成器函数的一个特点是,当执行完一句 yield 语句后函数会自动停止执行,再次调用迭代器的 next( ) 方法才会继续执行下一个 yield 语句。

这种自动中止函数执行的能力衍生出很多高级用法。

三、可迭代对象

在 ES6 中常用的集合对象(数组、Set/Map集合)和字符串都是可迭代对象,这些对象都有默认的迭代器和Symbol.iterator属性。

通过生成器创建的迭代器也是可迭代对象,因为生成器默认会为Symbol.iterator属性赋值。

3.1 Symbol.iterator

可迭代对象具有Symbol.iterator属性,即具有Symbol.iterator属性的对象都有默认迭代器。

我们可以用Symbol.iterator来访问对象的默认迭代器,例如对于一个数组:

let list = [11, 22, 33]
let iterator = list[Symbol.iterator]()
console.log(iterator.next()) // { value: 11, done: false }

Symbol.iterator获得了数组这个可迭代对象的默认迭代器,并操作它遍历了数组中的元素。

反之,我们可以用Symbol.iterator来检测对象是否为可迭代对象:

function isIterator(obj) {
 return typeof obj[Symbol.iterator] === 'function'
}

console.log(isIterator([11, 22, 33])) // true
console.log(isIterator('sometring')) // true
console.log(isIterator(new Map())) // true
console.log(isIterator(new Set())) // true
console.log(isIterator(new WeakMap())) // false
console.log(isIterator(new WeakSet())) // false

显然数组、Set/Map 集合、字符串都是可迭代对象,而 WeakSet/WeakMap 集合(弱引用集合)是不可迭代的。

3.2 创建可迭代对象

默认情况下,自定义的对象都是不可迭代的。

刚才讲过,通过生成器创建的迭代器也是一种可迭代对象,生成器默认会为Symbol.iterator属性赋值。

那如何将自定义对象变为可迭代对象呢?通过给Symbol.iterator属性添加一个生成器:

let collection = {
 items: [11,22,33],
 *[Symbol.iterator]() {
  for (let item of this.items){
   yield item
  }
 }
}

console.log(isIterator(collection)) // true

for (let item of collection){
 console.log(item) // 11 22 33
}

数组 items 是可迭代对象,collection 对象通过给Symbol.iterator属性赋值也成为可迭代对象。

3.3 for-of

注意到上个栗子使用了for-of代替索引循环,for-of是 ES6 为可迭代对象新加入的特性。

思考一下for-of循环的实现原理。

对于使用for-of的可迭代对象,for-of每执行一次就会调用这个可迭代对象的 next( ),并将返回结果存储在一个变量中,持续执行直到可迭代对象 done 属性值为 false。

// 迭代一个字符串
let str = 'somestring'

for (let item of str){
 console.log(item) // s o m e s t r i n g
}

本质上来说,for-of调用 str 字符串的Symbol.iterator属性方法获取迭代器(这个过程由 JS 引擎完成),然后多次调用 next( ) 方法将对象 value 值存储在 item 变量。

for-of用于不可迭代对象、null 或 undefined 会报错!

3.4 展开运算符(...)

ES6 语法糖展开运算符(...)也是服务于可迭代对象,即只可以“展开”数组、集合、字符串、自定义可迭代对象。

以下栗子输出不同可迭代对象展开运算符计算的结果:

let str = 'somestring'
console.log(...str) // s o m e s t r i n g


let set = new Set([1, 2, 2, 5, 8, 8, 8, 9])
console.log(set) // Set { 1, 2, 5, 8, 9 }
console.log(...set) // 1 2 5 8 9


let map = new Map([['name', 'jenny'], ['id', 123]])
console.log(map) // Map { 'name' => 'jenny', 'id' => 123 }
console.log(...map) // [ 'name', 'jenny' ] [ 'id', 123 ]


let num1 = [1, 2, 3], num2 = [7, 8, 9]
console.log([...num1, ...num2]) // [ 1, 2, 3, 7, 8, 9 ]


let udf
console.log(...udf) // TypeError: undefined is not iterable

由以上代码可以看出,展开运算符(...)可以便捷地将可迭代对象转换为数组。同for-of一样,展开运算符(...)用于不可迭代对象、null 或 undefined 会报错!

四. 默认迭代器

ES6 为很多内置对象提供了默认的迭代器,只有当内建的迭代器不能满足需求时才自己创建迭代器。

ES6 的 三个集合对象:Set、Map、Array 都有默认的迭代器,常用的如values()方法、entries()方法都返回一个迭代器,其值区别如下:

  • entries():多个键值对
  • values():集合的值
  • keys():集合的键

调用以上方法都可以得到集合的迭代器,并使用for-of循环,示例如下:

/******** Map ***********/
let map = new Map([['name', 'jenny'], ['id', 123]])

for(let item of map.entries()){
 console.log(item) // [ 'name', 'jenny' ] [ 'id', 123 ]
}
for(let item of map.keys()){
 console.log(item) // name id
}
for (let item of map.values()) {
 console.log(item) // jenny 123
}

/******** Set ***********/
let set = new Set([1, 4, 4, 5, 5, 5, 6, 6,])

for(let item of set.entries()){
 console.log(item) // [ 1, 1 ] [ 4, 4 ] [ 5, 5 ] [ 6, 6 ]
}

/********* Array **********/
let array = [11, 22, 33]

for(let item of array.entries()){
 console.log(item) // [ 0, 11 ] [ 1, 22 ] [ 2, 33 ]
}

此外 String 和 NodeList 类型都有默认的迭代器,虽然没有提供其它的方法,但可以用for-of循环

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
12款经典的白富美型—jquery图片轮播插件—前端开发必备
Jan 08 Javascript
浅谈类似于(function(){}).call()的js语句
Mar 30 Javascript
详谈LABJS按需动态加载js文件
May 07 Javascript
详解js中class的多种函数封装方法
Jan 03 Javascript
jQuery插件ajaxfileupload.js实现上传文件
Oct 23 Javascript
js判断是否为空和typeof的用法(详解)
Oct 07 Javascript
Bootstrap图片轮播组件Carousel使用方法详解
Oct 20 Javascript
JavaScript实现随机数生成器(去重)
Oct 13 Javascript
Node.js原生api搭建web服务器的方法步骤
Feb 15 Javascript
Vue-CLI 3.X 部署项目至生产服务器的方法
Mar 22 Javascript
微信小程序使用字体图标的方法
May 23 Javascript
解决VUE mounted 钩子函数执行时 img 未加载导致页面布局的问题
Jul 27 Javascript
详解mpvue中小程序自定义导航组件开发指南
Feb 11 #Javascript
如何用JavaScript实现功能齐全的单链表详解
Feb 11 #Javascript
vue2.0实现的tab标签切换效果(内容可自定义)示例
Feb 11 #Javascript
vue实现动态显示与隐藏底部导航的方法分析
Feb 11 #Javascript
Vue指令v-for遍历输出JavaScript数组及json对象的常见方式小结
Feb 11 #Javascript
react配置antd按需加载的使用
Feb 11 #Javascript
react中使用css的7中方式(最全总结)
Feb 11 #Javascript
You might like
C#使用PHP服务端的Web Service通信实例
2014/04/08 PHP
百万级别知乎用户数据抓取与分析之PHP开发
2015/09/28 PHP
PHP使用微信开发模式实现搜索已发送图文及匹配关键字回复的方法
2017/09/13 PHP
jquery的键盘事件修改代码
2011/02/24 Javascript
JQuery性能优化的几点建议
2014/05/14 Javascript
js+csss实现的一个带复选框的下拉框
2014/09/29 Javascript
jQuery中find()方法用法实例
2015/01/07 Javascript
超级简单实现JavaScript MVC 样式框架
2015/03/24 Javascript
jQuery的animate函数实现图文切换动画效果
2015/05/03 Javascript
javascript实现base64 md5 sha1 密码加密
2015/09/09 Javascript
jQuery仿写百度百科的目录树
2017/01/03 Javascript
vue2.0 与 bootstrap datetimepicker的结合使用实例
2017/05/22 Javascript
node使用UEditor富文本编辑器的方法实例
2017/07/11 Javascript
Vue路由 重定向和别名的区别说明
2020/09/09 Javascript
Python中使用装饰器时需要注意的一些问题
2015/05/11 Python
python统计多维数组的行数和列数实例
2018/06/23 Python
Random 在 Python 中的使用方法
2018/08/09 Python
在IPython中进行Python程序执行时间的测量方法
2018/11/01 Python
对python requests发送json格式数据的实例详解
2018/12/19 Python
Python如何在DataFrame增加数值
2020/02/14 Python
如何利用python web框架做文件流下载的实现示例
2020/06/02 Python
Keras自定义实现带masking的meanpooling层方式
2020/06/16 Python
Numpy实现卷积神经网络(CNN)的示例
2020/10/09 Python
举例讲解Python装饰器
2020/12/24 Python
装上这 14 个插件后,PyCharm 真的是无敌的存在
2021/01/11 Python
Python对excel的基本操作方法
2021/02/18 Python
CSS3 flex布局之快速实现BorderLayout布局
2015/12/03 HTML / CSS
美国定制钻石订婚戒指:Ritani
2017/12/08 全球购物
儿科主治医生个人求职信
2013/09/23 职场文书
铁路个人事迹材料
2014/01/30 职场文书
天猫某品牌专卖店运营计划书
2014/03/21 职场文书
《美丽的南沙群岛》教学反思
2014/04/27 职场文书
给学校建议书范文
2014/05/13 职场文书
法人授权委托书样本
2014/09/19 职场文书
MySQL下使用Inplace和Online方式创建索引的教程
2021/05/26 MySQL
Docker部署Mysql8的实现步骤
2022/07/07 Servers