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 相关文章推荐
passwordStrength 基于jquery的密码强度检测代码使用介绍
Oct 08 Javascript
探索Emberjs制作一个简单的Todo应用
Nov 07 Javascript
JS文本框不能输入空格验证方法
Mar 19 Javascript
js实现鼠标拖动图片并兼容IE/FF火狐/谷歌等主流浏览器
Jun 06 Javascript
jquery操作checked属性以及disabled属性的多种方法
Jun 20 Javascript
深入理解MVC中的时间js格式化
May 19 Javascript
JavaScript必看小技巧(必看)
Jun 07 Javascript
Bootstrap BootstrapDialog使用详解
Feb 17 Javascript
js实现简单的获取验证码按钮效果
Mar 03 Javascript
Vue.js实现在下拉列表区域外点击即可关闭下拉列表的功能(自定义下拉列表)
May 30 Javascript
JS判断Android、iOS或浏览器的多种方法(四种方法)
Jun 29 Javascript
js实现烟花特效
Mar 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
如何批量替换相对地址为绝对地址(利用bat批处理实现)
2013/05/27 PHP
在wamp集成环境下升级php版本(实现方法)
2013/07/01 PHP
php生成复杂验证码(倾斜,正弦干扰线,黏贴,旋转)
2018/03/12 PHP
因str_replace导致的注入问题总结
2019/08/08 PHP
Js中获取frames中的元素示例代码
2013/07/30 Javascript
js禁止页面复制功能禁用页面右键菜单示例代码
2013/08/29 Javascript
Json实现异步请求提交评论无需跳转其他页面
2014/10/11 Javascript
node.js中的console.timeEnd方法使用说明
2014/12/09 Javascript
Javascript中实现String.startsWith和endsWith方法
2015/06/10 Javascript
简单谈谈javascript Date类型
2015/09/06 Javascript
javascript用正则表达式过滤空格的实现代码
2016/06/14 Javascript
微信小程序 底部导航栏目开发资料
2016/12/05 Javascript
js指定步长实现单方向匀速运动
2017/07/17 Javascript
pace.js和NProgress.js两个加载进度插件的一点小总结
2018/01/31 Javascript
Vue 中的compile操作方法
2018/02/26 Javascript
Vuex中mutations与actions的区别详解
2018/03/01 Javascript
layer.close()关闭进度条和Iframe窗的方法
2018/08/17 Javascript
Vue项目pdf(base64)转图片遇到的问题及解决方法
2018/10/19 Javascript
angular6 填坑之sdk的方法
2018/12/27 Javascript
微信小程序实现身份证取景框拍摄
2020/09/09 Javascript
Python下的Mysql模块MySQLdb安装详解
2014/04/09 Python
python爬虫headers设置后无效的解决方法
2017/10/21 Python
Python+matplotlib实现填充螺旋实例
2018/01/15 Python
pygame库实现俄罗斯方块小游戏
2019/10/29 Python
python如何使用socketserver模块实现并发聊天
2019/12/14 Python
Pytest参数化parametrize使用代码实例
2020/02/22 Python
python numpy实现多次循环读取文件 等间隔过滤数据示例
2020/03/14 Python
如何用Python徒手写线性回归
2021/01/25 Python
基于HTML5的WebGL经典3D虚拟机房漫游动画
2017/11/15 HTML / CSS
世界闻名的衬衫制造商:Savile Row Company
2018/07/30 全球购物
周仰杰(JIMMY CHOO)法国官方网站:闻名世界的鞋子品牌
2019/09/27 全球购物
90后毕业生的求职信范文
2013/09/21 职场文书
招聘单位介绍信
2014/01/14 职场文书
社会实践活动报告
2015/02/05 职场文书
酒店员工辞职信范文
2015/02/28 职场文书
道歉信范文
2015/05/12 职场文书