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 相关文章推荐
iframe的父子窗口之间的对象相互调用基本用法
Sep 03 Javascript
Seajs的学习笔记
Mar 04 Javascript
Bootstrap框架动态生成Web页面文章内目录的方法
May 12 Javascript
jQuery插件简单学习实例教程
Jul 01 Javascript
最丑的时钟效果!js canvas时钟制作方法
Aug 15 Javascript
利用Vue.js指令实现全选功能
Sep 08 Javascript
微信小程序 两种为对象属性赋值的方式详解
Feb 23 Javascript
ES6新特性之Symbol类型用法分析
Mar 31 Javascript
vue.js之vue-cli脚手架的搭建详解
May 05 Javascript
vue动态路由配置及路由传参的方式
May 23 Javascript
Vue开发环境中修改端口号的实现方法
Aug 15 Javascript
js实现简单音乐播放器
Jun 30 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中使用substr()截取字符串出现中文乱码问题该怎么办
2015/10/21 PHP
php+ajax制作无刷新留言板
2015/10/27 PHP
详解laravel安装使用Passport(Api认证)
2018/07/27 PHP
jquery获取table中的某行全部td的内容方法
2013/03/08 Javascript
JQuery筛选器全系列介绍
2013/08/27 Javascript
setTimeout自动触发一个js的方法
2014/01/15 Javascript
JavaScript Function函数类型介绍
2015/04/08 Javascript
jQuery插件pagination实现分页特效
2015/04/12 Javascript
微信小程序之前台循环数据绑定
2017/08/18 Javascript
jquery在启动页面时,自动加载数据的实例
2018/01/22 jQuery
详解vue在项目中使用百度地图
2019/03/26 Javascript
微信小程序实现卡片层叠滑动效果
2019/06/21 Javascript
js计算最大公约数和最小公倍数代码实例
2019/09/11 Javascript
javascript实现贪吃蛇经典游戏
2020/04/10 Javascript
一篇文章带你搞懂Vue虚拟Dom与diff算法
2020/08/25 Javascript
[05:01]3.19DOTA2发布会 我们都是刀塔人
2014/03/25 DOTA
[01:01:24]LGD vs Fnatic 2018国际邀请赛小组赛BO2 第一场 8.18
2018/08/19 DOTA
python脚本作为Windows服务启动代码详解
2018/02/11 Python
Python爬豆瓣电影实例
2018/02/23 Python
Python实现购物评论文本情感分析操作【基于中文文本挖掘库snownlp】
2018/08/07 Python
python批量下载抖音视频
2019/06/17 Python
Python使用sklearn实现的各种回归算法示例
2019/07/04 Python
详解用python计算阶乘的几种方法
2019/08/14 Python
jupyter notebook读取/导出文件/图片实例
2020/04/16 Python
如何利用python正则表达式匹配版本信息
2020/12/09 Python
纯CSS3实现自定义Tooltip边框涂鸦风格的教程
2014/11/05 HTML / CSS
New Balance英国官方网站:始于1906年,百年慢跑品牌
2016/12/07 全球购物
英国在线药房:Chemist.co.uk
2019/03/26 全球购物
Linux如何命名文件--使用文件名时应注意
2014/05/29 面试题
医学毕业生自荐信
2013/10/11 职场文书
道德模范先进事迹
2014/02/14 职场文书
行政副总岗位职责
2014/02/23 职场文书
同事离别感言
2015/08/04 职场文书
《珍珠鸟》教学反思
2016/02/16 职场文书
浅谈:电影《孔子》观后感(范文)
2019/10/14 职场文书
详解Redis实现限流的三种方式
2021/04/27 Redis