ES6深入浅出之迭代器&生成器

·  阅读 29

这是我参与11月更文挑战的第2天,活动详情查看:2021最后一次更文挑战

1, 前言

ECMAScript是标准,JavaScript是这个标准的实现和扩展。

ECMAScript定义了:语言语法,类型, 原型和继承,内建对象和函数的标准库,ECMAScript涵盖了各种环境中JS的使用场景,无论是浏览器环境还是类似nodejs的非浏览器环境。

ECMAScript标准的历史版本分别是:1,2,3,5

2009年发布的ES5引入了新的特性: Object.create(), Object.defineProperty(), getters, setters, 严格模式, 以及JSON对象,还有最实用的数组方法: .map(), .filter()方法 

经过持续几年的磨砺, 2015年发布的ES6,让JS得到有史以来最实质的升级,它的新特性和语法糖更是让人爱不释手,箭头函数,字符串插值,代理,生成器,ES6已经彻底的改变了我们编写JS的习惯。

2, 数组遍历

  • (1) JavaScript刚萌生时,最常用的方法
for(var i = 0; i < myArry.length; i++) {
     console.log(myArry[i])
}
复制代码
  • (2) ES5发布之后,你会想到内建方法:forEach遍历数组
myArry.forEach(function(value) {
      console.log(value)
})
复制代码

forEach有个缺点: 不能使用breack语句中断循环,也不能使用return语法返回到外层函数

  • (3) 你有可能向尝试一下for-in循环, 切记:千万不要这样
for(var index in myArray) { // 千万不要这样
   console.log(myArray[index])
}
复制代码

首先:index的值实际上不是一个数字,而是字符串 "0", "1", "2",,,,有可能无意之间进行了字符串算数计算

其次: for-in 循环除了遍历数组元素外,还会遍历自定义属性,甚至连数组原型链上的属性都能被访问到

最后: for-in 是为普通对象设计的,你可以遍历得到字符串类型的健,不适用数组的遍历

  • (4) ES6为两解决for-in遍历数组的问题,新增了一个语法for-of
for(var value of myArray) {
   console.log(value)
}
复制代码

这种循环看起来是否很眼熟,当然for-of还有其他强大的功能

1, 这是最简洁,最直接的遍历数组元素的方法

**2, 这个方法避开了for-in循环的所有缺陷 **

3, 与forEach()不同,for-of可以正确响应break, continue, return 语句

4, for-in循环用来遍历对象属性

5,for-of循环用来遍历数据,数组中的值,当然,不仅如此

for-of其他特性

for-of循环不仅支持数组,也支持类数组对象,也支持字符串遍历,同样支持Map, Set对象遍历

举个例子1:Set对象

// Set对象可以自动排除重复项
let myArray = [1,0,0,2,9,8,3,1];
var uniqueItem = new Set(myArray)
for(var value of uniqueItem) {
   console.log(value)
}
复制代码

举个例子2: Map对象

// Map对象中的数据是有键值对组成,所以需要使用解构
for(var [key, value] of MapObject) {
   console.log(key, value)
}
复制代码

总之: 你只需要记住,未来的JS可以使用很多新型的集合类,而for-of就是为遍历所有这些集合特别设计的循环语句, for-of循环不支持普通对象,如果你想循环遍历一个对象的属性,可以使用for-in或者内建方法 Object.keys()方法

3,Symbol.interator

你可以给任意类型的对象添加迭代器方法。

比如:你给对象添加myObject.toString()方法,就可以将对象转化为字符串

当你给任意对象添加myObject[Symbol.interator]()方法,就表示:这个对象可以被for-of循环遍历啦。

4, 迭代器

简单的理解:迭代器是一个拥有.next()方法的特殊对象,每次调用next()方法都返回一个结果对象。

注意: 迭代器对象可以是任意具有.next()方法的对象。

举个例子: 通过for-of遍历的时候,首先调用集合的[Symbol.interator]()方法,紧接着返回一个新的迭代器对象,这个迭代器对象可以是任意具有.next()方法的对象, for-of循环将重复调用这个.next()方法

var myIterator = {
    [Symbol.interator]: function() {
       return this;
    },
    next: function() {
       return { done: false, value: 0 }
    }
}
复制代码

在ES6中,迭代器的编写规则比较复杂,为了让创建迭代器的过程变得简单,ES6同时还引入了一个生成器对象,它可以让创建迭代器对象的过程变的更简单。

5, 生成器

普通函数使用function声明, 而生成器函数使用function*声明

在生成器函数内部,有一种类似return的语法:关键字yield。二者的区别: 普通函数只可以return一次,而生成器函数可以yield多次,在生成器执行的过程中,遇到yield表达式立即暂停,后续可回复执行状态,普通函数不能自动暂停,生成器函数可以。

举个例子:一个简单的生成器

function* quips(name) {
   yield "你好" + name
   yield  "你也好"
   yield  "我们下次再见"
}
复制代码

6, 生成器就是迭代器

​ 因为:所有的生成器都有内建.next()[Symbol.iterator]()方法的实现。开发过程你只需编写循环部分的行为。不借助生成器实现的迭代器会变的难以理解。

如何让作为迭代器的生成器发挥最大的作用?

​ 编写生成器函数遍历这个对象,运行时yield每一个值,然后将这个生成器函数作为这个对象的Symbol.interator方法. 当你面对一个复杂的循环时,你可以拆分出生成数据的代码,将其独立转为独立的生成器函数,然后使用for(var data of myGenerator()) 遍历我们所需要的数据

ES6不提供用了过滤,映射以及针对任意可迭代数据集进行特殊操作的扩展库。借助生成器,很容易实现。举例:实现一个等效于Array.prototype.filter, 并支持DOM NodeLists方法

function* filter(test, iterable) {
     for(var item of iterable) {
        if(test(item))
          yield item;
     }
}
复制代码

迭代器贯穿ES6的始终,它是数据和循环的新标准。

7, 简单的例子

生成器函数配合for-or实现数据的遍历

function* generator() {
    yield 1;
    yield 2;
    yield 3;
    yield 4;
}

for (const itemr of generator()) {
     console.log(itemr)
}
复制代码

输出结果:

1
2
3
4
复制代码

总结:

当你调用一个生成器的时候,它并非立即执行,在遇到的最顶端第一行代码的时候,就停止了,立即冻结,而且返回了一个已暂停的生成器对象。每当你调用生成器对象的.next()方法时,函数调用将其自身解冻并一直运行到下一个yield表达式,再次暂停, 调用最后一个.next()时,返回结果中donetrue, valueundefined

分类:
前端
分类:
前端