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 相关文章推荐
Gird事件机制初级读本
Mar 10 Javascript
JavaScript中Object和Function的关系小结
Sep 26 Javascript
javascript实现日历控件(年月日关闭按钮)
Dec 12 Javascript
javascript实现右侧弹出“分享到”窗口效果
Feb 01 Javascript
使用jquery.form.js实现图片上传的方法
May 05 Javascript
细数JavaScript 一个等号,两个等号,三个等号的区别
Oct 09 Javascript
详解webpack自动生成html页面
Jun 29 Javascript
JS实现的按钮点击颜色切换功能示例
Oct 19 Javascript
Vue入门之animate过渡动画效果
Apr 08 Javascript
详解Vue中的Props与Data细微差别
Mar 02 Javascript
vue实现简单的登录弹出框
Oct 26 Javascript
JavaScript实现10秒后再次获取验证码
Dec 02 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
php 日期时间处理函数小结
2009/12/18 PHP
php下将图片以二进制存入mysql数据库中并显示的实现代码
2010/05/27 PHP
使用PHP遍历文件目录与清除目录中文件的实现详解
2013/06/24 PHP
PHP header()函数常用方法总结
2014/04/11 PHP
php数组比较实现查找连续数的方法
2015/07/29 PHP
php类的自动加载操作实例详解
2016/09/28 PHP
详谈php ip2long 出现负数的原因及解决方法
2017/04/05 PHP
jQuery当鼠标悬停时放大图片的效果实例
2013/07/03 Javascript
完美解决IE低版本不支持call与apply的问题
2013/12/05 Javascript
ie8下修改input的type属性报错的解决方法
2014/09/16 Javascript
AngularJS控制器继承自另一控制器
2016/05/09 Javascript
js 毫秒转天时分秒的实例
2017/11/17 Javascript
nodejs多版本管理总结
2018/04/03 NodeJs
在vue中给列表中的奇数行添加class的实现方法
2018/09/05 Javascript
VUE+Element UI实现简单的表格行内编辑效果的示例的代码
2018/10/31 Javascript
vue项目配置 webpack-obfuscator 进行代码加密混淆的实现
2021/02/26 Vue.js
[03:08]Ti4观战指南上
2014/07/07 DOTA
Python读大数据txt
2016/03/28 Python
Python使用当前时间、随机数产生一个唯一数字的方法
2017/09/18 Python
Python字符编码与函数的基本使用方法
2017/09/30 Python
python实现微信跳一跳辅助工具步骤详解
2018/01/04 Python
Python里字典的基本用法(包括嵌套字典)
2019/02/27 Python
你还在@微信官方?聊聊Python生成你想要的微信头像
2019/09/25 Python
Python 过滤错误log并导出的实例
2019/12/26 Python
TensorFLow 数学运算的示例代码
2020/04/21 Python
CSS3 animation实现逐帧动画效果
2016/06/02 HTML / CSS
国际商务系学生个人的自我评价
2013/11/26 职场文书
幼儿园教师备课制度
2014/01/12 职场文书
警察正风肃纪剖析材料
2014/10/16 职场文书
离职报告格式
2014/11/04 职场文书
产品质量保证书范本
2015/02/27 职场文书
生死牛玉儒观后感
2015/06/11 职场文书
初中历史教学反思
2016/02/19 职场文书
python 下载文件的几种方式分享
2021/04/07 Python
解决go在函数退出后子协程的退出问题
2021/04/30 Golang
使用springMVC所需要的pom配置
2021/09/15 Java/Android