深入理解ES6|深入理解ES6 ---- 迭代器

迭代器

遍历器(Iterator)是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。
(1) Iterator 的作用
  • 为各种数据结构,提供一个统一的、简便的访问接口
  • 使得数据结构的成员能够按某种次序排列
  • ES6 创造了一种新的遍历命令for...of循环,Iterator 接口主要供for...of消费。
(2) Iterator的迭代过程 所有的迭代器对象都会有一个next方法,每次调用都会返回一个对象:{done: boolean, value: any}value表示当前成员的值,done表示是否还有更多的数据。迭代器内部会维护一个指针,指向当前成员的位置,每次调用next都会指向下一个成员。
// es5实现迭代器 function creatIterator(arr){ let index = 0; return { next: () =>{ return { done: index > arr.length - 1, value: this.done ? undefined : arr[index++] } } } } const iterator = creatIterator([1, 2, 3]) console.log(a.next())// { done: false, value: 1 } console.log(a.next())// { done: false, value: 2 } console.log(a.next())// { done: false, value: 3 } console.log(a.next())// { done: true, value: undefined } // 之后的调用都会返回相同的内容 console.log(a.next())// { done: true, value: undefined }

生成器
生成器是一种返回迭代器的函数。通过function后面的*来表明它是一个生成器。
  • yield关键字是es6的新特性,它可以指定调用next方法时的返回值以及调用顺序。
  • 每当执行完yield语句,函数就会停止执行,直到再次调用next方法才会继续执行
  • yield关键字只能在生成器内部使用,其他地方会导致语法错误
function * createIterator(){ yield 1 yield 2 } // 生成器返回的是一个迭代器 const iterator = createIterator() console.log(iterator .next())// { done: false, value: 1 } console.log(iterator .next())// { done: false, value: 2 } console.log(iterator.next())// { done: true, value: undefined }

使用函数表达式:const createIterator = function *(){ yield 1 },但不能使用箭头函数来创建生成器。
这里仅仅介绍了生成器可以返回迭代器,但它主要的作用是流程管理,因此可以使用同步的方式书写异步代码,generator详情
可迭代对象
(1) Symbol.iterator属性 一个数据结构只要具有Symbol.iterator属性,就可以认为是可迭代的
  • 所有的集合对象array、set、map以及string都是可迭代对象,都有默认的迭代器,即Symbol.iterator属性
  • 生成器生成的对象就是可迭代的,生成器会默认为他们的Symbol.iterator属性赋值
  • 通过执行 Symbol.iterator方法 来获取对象的迭代器
访问数组的默认迭代器:
const arr = [1, 2, 3] // 通过执行数组的 `Symbol.iterator`方法来获取 `arr` 的迭代器 const arrIterator = arr[Symbol.iterator]()console.log(arrIterator.next())// { value: 1, done: false } console.log(arrIterator.next())// { value: 2, done: false } console.log(arrIterator.next())// { value: 3, done: false } console.log(arrIterator.next())// { value: undefined, done: true }

(2) for...of for...of封装了next的重复调用过程,来自动执行迭代器,遍历访问可迭代对象的成员。它通过对象的Symbol.iterator方法来获取迭代器,并在每次循环中调用可迭代对象的next方法,将返回值根据迭代器的规则赋给中间变量,一直循环到done属性为true。即
const arr = [1, 2, 3] for(let item of arr){ console.log(item) } // 1 2 3

(3) 创建可迭代对象 可以通过给一个不可迭代对象设置Symbol.iterator属性,使它变成可迭代的。Symbol.iterator必须是迭代器生成函数,否则使用for...of会报错。
// es5 const list = { items: [1,2,3], [Symbol.iterator](){ let index = 0; return { next: () =>{ return { done: index > this.items.length - 1, value: this.done ? undefined : this.items[index++] } } } } } for (const item of list) { console.log(item) } // 1 2 3// es6 const list = { items: [1,2,3], *[Symbol.iterator](){ for(item of this.items){ yield item } } } for (const item of list) { console.log(item) } // 1 2 3

(3) 内置迭代器 entries、values、keys都是es6新增的迭代器。下表为各种集合类型的各种迭代器在for...of循环中的中间变量:
集合类型/迭代器 entries values keys
array [[index, value]] [value]? [index]
map [[key, value]]? [value] [key]
set [[value, value]] [value]? [value]
每个集合类型都有默认的迭代器,在for... of循环中,如果没有显式的指定,则使用默认的迭代器。上表中的?表示各集合类型的for... of默认迭代器
const arr = [1,2,3] // 没有显示指定迭代器,则使用array默认的迭代器,即values迭代器 for (const item of arr) { console.log(item) } // 1 2 3// 使用显示指定的entries迭代器 for (const item of arr.entries()) { console.log(item) } // [0,1][1,2][2,3]

需要注意的是:entries、values、keys请勿与Object.entries、Object.values、Obejct.keys混淆。前者返回的是迭代器,用于for...of循环;后者返回的是根据原对象格式化后的数组。
(4) 字符串迭代器 在es5中也可以使用for循环遍历字符串,但却以编码为单元而非字符,因此无法正确访问双字节字符。es6中全面支持unicode,所以for...of循环就可以正确遍历字符串中的双字节字符
const str = '1' for (let i = 0; i < text.length; i++) { console.log(text[i]); } // " " // " " // "1"// 可以正确识别双字节字符 for (let i of text) { console.log(i); } // "" // "1"

(5) NodeList迭代器 DOM标准中有一个NodeList类型,用于表示页面文档元素的集合。它含有length属性;可以通过[number]访问元素,跟数组的格式和操作方法很类似,但其实它是对象,也就是我们俗称的伪数组,比如:{0: domObject, 1: domObject, 2: domObject, length:3}
es6NodeList类型也拥有了默认迭代器,可以使用for...of迭代
const nodeList = document.getElementsByClassName('abc') for (const node of nodeList) { console.log(node) }

(6) 展开运算符 展开运算符可以操作所有可迭代对象,并根据默认迭代器来选取要引用的值,然后读取所有值
const nodeList = document.getElementsByClassName('abc') console.log([...nodeList]) // [node, node, node]const map = new Map() map.set('name','li yang') map.set('age', 18) console.log([...map])// 调用了map的默认迭代器 entries // [["name", "li yang"], ["age", 18]]

【深入理解ES6|深入理解ES6 ---- 迭代器】由于展开运算符可以作用于任意可迭代对象,因此要将可迭代对象装换成数组,这是最简单的方法

    推荐阅读