Iterator-迭代器
认识迭代器
迭代器是帮助我们对某个数据结构进行遍历的对象
在JS中,迭代器也是一个具体的对象,但是这个对象需要符合迭代器协议(iterator protocol)
迭代器协议定义了产生一系列值(无论是有限个还是无限个)的标准方式
在JS中这个标准就是一个特定的 next 方法
next方法有如下要求:
next 是一个无参数或者有一个参数的函数,返回一个有以下两个属性的对象
-
done(boolean)
如果迭代器可以产生序列中的下一个值,则为 false。(这等价于没有指定 done 这个属性。)
-
value
迭代器返回的任何 JavaScript 值。done 为 true 时可省略。
总结:
迭代器是一个对象,符合迭代器协议
const iterator = { next: function() {return {}}}
// 数组
const names = ["aaa", "bbb", "ccc"]
// 创建一个迭代器对象来访问数组
let index = 0
const namesIterator = {
next: function () {
if (index < names.length) {
return { done: false, value: names[index++] }
} else {
return { done: true, value: undefined }
}
},
}
console.log(namesIterator.next())
console.log(namesIterator.next())
console.log(namesIterator.next())
console.log(namesIterator.next())
console.log(namesIterator.next())
console.log(namesIterator.next())
创建一个生成迭代器的函数
function createArrayIterator(arr) {
let index = 0
return {
next: function () {
if (index < arr.length) {
return { done: false, value: arr[index++] }
} else {
return { done: true, value: undefined }
}
},
}
}
const names = ["aaa", "bbb", "ccc"]
const namesIterator = createArrayIterator(names)
console.log(namesIterator.next())
console.log(namesIterator.next())
console.log(namesIterator.next())
console.log(namesIterator.next())
const otherIterator = createArrayIterator([1, 3, 4, 5])
可迭代对象
认识可迭代对象
当一个对象实现了iterable protocol 协议时,它就是一个可迭代对象(注意跟迭代器协议不一样)
这个对象的要求是,必须实现@@iterator方法,在代码中我们使用 Symbol.iterator访问该属性
// 创建一个迭代器对象来访问数组
const iterableObj = {
names: ['aaa', 'bbb', 'ccc'],
[Symbol.iterator]: function() {
let index = 0
// 返回一个迭代器
return {
next: () => { // 为了拿到names,这里需要箭头函数,让this指向iterableObj
if (index < this.names.length) {
return { done: false, value: this.names[index++]}
} else {
return { done: true, value: undefined}
}
}
}
}
}
iterableObj 对象就是一个可迭代对象
console.log(iterableObj[Symbol.iterator]); // 获取到函数
// 调用函数返回一个迭代器
const iterator = iterableObj[Symbol.iterator]()
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
// 每次调用都返回一个新的迭代器对象
const iterator2 = iterableObj[Symbol.iterator]()
for...of 实际上就是一种语法糖
for...of 可以遍历的东西必须是一个可迭代对象
// for...of
const obj = {name: 'xxx', age: 18}
// for (const item of obj) {
// console.log(item); // TypeError: obj is not iterable
// }
for(const item of iterableObj) {
console.log(item); // aaa bbb ccc
}
实际上 for...of 就是在不断调用next,然后返回value,根据done来判断是否遍历结束
原生可迭代对象
事实上我们平时创建的很多原生对象已经实现了可迭代协议,会生成一个迭代器对象
例如:String,Array,Map,Set,arguments对象,NodeList集合
比如我们创建一个数组,打印[Symbol.iterator]属性,可以看到是有返回迭代器的函数的,所以也可以调用next()
const names = ['aaa', 'bbb', 'ccc']
console.log(names[Symbol.iterator]);
// ƒ values() { [native code] }
const iterator1 = names[Symbol.iterator]() // 返回迭代器对象
console.log(iterator1.next()); // 调用next
console.log(iterator1.next());
console.log(iterator1.next());
console.log(iterator1.next());
其他也一样,比如Set
const set = new Set()
set.add(10)
set.add(100)
set.add(1000)
console.log(set[Symbol.iterator]); // ƒ values() { [native code] }
// 可以for...of遍历
for (const item of set) {
console.log(item); // 10 100 1000
}
可迭代对象的应用
- JavaScript中语法:for ...of、展开语法、yield*、解构赋值
- 创建一些对象时:new Map([Iterable])、new WeakMap([iterable])、new Set([iterable])、new WeakSet([iterable])
- 一些方法的调用:Promise.all(iterable)、Promise.race(iterable)、Array.from(iterable)
展开语法
const iterableObj = {
names: ["aaa", "bbb", "ccc"],
[Symbol.iterator]: function() {
let index = 0
return {
next: () => {
if (index < this.names.length) {
return { done: false, value: this.names[index++] }
} else {
return { done: true, value: undefined }
}
}
}
}
}
// 展开语法
const arr = ['ddd']
const newNames = [...arr, ...iterableObj]
console.log(newNames); // ['ddd', 'aaa', 'bbb', 'ccc']
// 对象使用展开语法
const obj = { name: "xxx", age: 18 }
const newObj = { ...obj }
console.log(newObj) // {name: 'xxx', age: 18}
注意:对象可以用展开语法,是ES9新增的特性,用的不是迭代器。是内部做了特殊的处理
解构语法
const titles = ["aaa", "bbb"]
const [t1, t2] = titles
console.log(t1, t2); // aaa bbb
对象的解构同样是ES9新增特性
创建一些对象时要求传入可迭代对象
Set
const set1 = new Set(iterableObj)
const set2 = new Set(titles)
Promise.all,Array.from
const arr1 = Array.from(iterableObj)
Promise.all(iterableObj).then(res => {
console.log(res) // aaa bbb ccc
})
自定义类的迭代
我们定义一个类,希望通过这个类创建出来的对象是可迭代的
案例:创建一个 Person 类
class Person {
constructor(name, age, friends) {
this.name = name
this.age = age
this.friends = friends
}
// 新增friend
entry(newFriend) {
this.friends.push(newFriend)
}
// 定义 [Symbol.iterator]
[Symbol.iterator]() {
let index = 0
return {
next: () => {
if (index < this.friends.length) {
return { done: false, value: this.friends[index++] }
} else {
return { done: true, value: undefined}
}
}
}
}
}
const p1 = new Person('xxx', 18, ["aaa", "bbb"])
p1.entry("ccc")
for (const f of p1) {
console.log(f); // aaa bbb ccc
}
Generator-生成器
前情提要
function foo() { const value1 = 100 console.log(value1) const value2 = 200 console.log(value2) const value3 = 300 console.log(value3) } foo()如果我们想要在打印完value1之后,暂停执行,注意是暂停!,也就是说可以恢复函数的执行的,因此我们不能直接return,return函数执行就终止了
认识生成器
生成器是ES6中新增的一种函数控制、使用的方案,它可以让我们更加灵活的控制函数什么时候继续执行、暂停执行等
生成器函数与普通函数的区别:
-
需要在 function 后面加一个符号:*
-
生成器函数可以通过yield关键字来控制函数的执行流程
-
返回值是一个生成器(Generator)
事实上生成器是一种特殊的迭代器
定义一个生成器函数
function* foo() {
console.log('函数开始执行');
const value1 = 100
console.log("value1: ", value1);
yield // 暂停
const value2 = 200
console.log("value2: ", value2);
yield // 暂停
const value3 = 300
console.log("value3: ", value3);
yield // 暂停
console.log('函数结束执行');
}
生成器函数的执行
调用生成器函数时, 会给我们返回一个生成器对象
// 直接foo() 函数并没有执行
const generator = foo()
console.log(generator); // foo {<suspended>}
如果我们想让函数执行,需要调用 next
// 开始执行第一段代码
generator.next() // 函数开始执行 value1: 100
// 执行第二段代码
generator.next() // value2: 200
// 执行第三段代码
generator.next() // value3: 300
generator.next() // 函数结束执行
我们知道迭代器的next是有返回值的,一个对象
const generator = foo()
console.log("返回值1: ", generator.next());
// 返回值1: {value: undefined, done: false}
如果我们不希望next返回的是一个undefined,我们可以通过yield来返回结果
function* foo() {
console.log('函数开始执行');
const value1 = 100
console.log("value1: ", value1);
yield value1
const value2 = 200
console.log("value2: ", value2);
yield value2
const value3 = 300
console.log("value3: ", value3);
yield value3
console.log('函数结束执行');
}
生成器传递参数
我们在调用next函数的时候,可以给它传递参数,这个参数会作为上一个yield语句的返回值
return函数
让生成器提前结束,用的少
function* foo(num) {
console.log('函数开始执行');
const value1 = 100 * num
console.log("value1: ", value1);
const n = yield value1
// 不会执行了
const value2 = 200 * n
console.log("value2: ", value2);
yield value2
const value3 = 300
console.log("value3: ", value3);
yield value3
console.log('函数结束执行');
}
const generator = foo(5)
console.log("返回值1: ", generator.next());
// 第二段代码的执行, 使用了return
// 那么就意味着相当于在第一段代码的后面加上return, 就会提前终端生成器函数代码继续执行
console.log("返回值2: ", generator.return()); // 使用return
console.log("返回值3: ", generator.next());
throw函数
用的很少
在生成器函数内部抛出异常
function* foo() {
console.log("代码开始执行")
const value1 = 100
try { //
yield value1
} catch (error) {
console.log("捕获到异常情况:", error)
yield "abc"
}
console.log("第二段代码继续执行")
const value2 = 200
yield value2
console.log("代码执行结束")
}
const generator = foo()
const result = generator.next()
generator.throw("error message")
// 代码开始执行
// 捕获到异常情况: error message
生成器替代迭代器使用
生成器是一种特殊的迭代器
而且我们还可以使用yield* 来生成一个可迭代对象
// 生成器替代迭代器
function* createArrayIterator(arr) {
// 1. 第一种写法
// yield "aaa"
// yield "bbb"
// yield "ccc"
// 2. 第二种写法
// for (const item of arr) {
// yield item
// }
// 3. 第三种写法(相当于是for ... of 的语法糖)
yield* arr // 后面跟的必须是可迭代对象
}
const names = ['aaa', 'ccc', 'ddd']
const namesIterator = createArrayIterator(names)
console.log(namesIterator.next()); // { value: 'aaa', done: false }
console.log(namesIterator.next()); // { value: 'bbb', done: false }
console.log(namesIterator.next()); // { value: 'ccc', done: false }
console.log(namesIterator.next()); // { value: undefined, done: true }
案例:创建一个函数,这个函数可以迭代一个范围内的数字
function* createRangeIterator(start, end) {
let index = start
while (index < end) {
// 不需要自己手动返回迭代器对象了(return带有next函数的对象)
yield index++
}
}
const rangeIterator = createRangeIterator(10, 20)
console.log(rangeIterator.next());
console.log(rangeIterator.next());
console.log(rangeIterator.next());
// { value: 10, done: false }
// { value: 11, done: false }
// { value: 12, done: false }
异步代码的处理方案
假如说现在有一个需求,我们需要向服务器发送网络请求,一共需要发送三次请求
对上一次请求到的结果做一个处理之后再发送一次新的请求
// 网络请求数据的函数
function requestData(url) {
return new Promise((resolve, reject) => {
// 模拟网络请求
setTimeout(() => {
// 拿到请求的结果
resolve(url)
}, 2000);
})
}
1.第一种方案,多次回调
产生回调地狱
requestData("BASE").then(res => {
// console.log(res); //拿这个结果处理后再发送请求
requestData(res + 'aaa').then(res => {
requestData(res + 'bbb').then(res => {
console.log(res); // 最终拿到我们想要的结果
})
})
})
// 输出:BASEaaabbb
2.第二种方案:Promise中then的返回值来解决,then也返回一个Promise
requestData("BASE").then(res => {
return requestData(res + 'aaa')
}).then(res => {
return requestData(res + 'bbb')
}).then(res => {
console.log(res);
})
// BASEaaabbb
代码阅读性很差
3.第三种方案:Promise + generator实现
// 先定义生成器函数
function* getData() {
const res1 = yield requestData('BASE')
const res2 = yield requestData(res1 + 'aaa')
const res3 = yield requestData(res2 + 'bbb')
console.log(res3);
}
执行上面函数的方式有三种:
- (1)手动执行生成器函数
const generator = getData() // 先调用函数,返回生成器
// 通过next调用
generator.next().value.then(res => {
generator.next(res).value.then(res => {
generator.next(res).value.then(res => {
generator.next(res)
})
})
})
generator.next() 返回结果对象{value: , next: }
并且这个value是promise
-
(2)自己封装了一个自动执行的函数
使用递归
function execGenerator(genFn) {
const generator = genFn() // 拿到生成器
function exec(res) {
const result = generator.next(res)
if (result.done) { // 为true则说明执行结束
return result.value
}
result.value.then(res => {
exec(res) // 只要没有执行结束,就递归调用exec来接着执行
})
}
exec()
}
execGenerator(getData)
- (3)使用第三方包 co,自动执行生成器函数
npm i co
const co = require('co')
co(getData) // BASEaaabbb
在还没有async/await 之前,就是使用上面的几种方法
4.第四种方案:async/await
也就是我们目前使用使用最多的,在ES8新增的
实际上,就是 generator 实现的一种语法糖
*变成 async,yield -> await
async function getData() {
const res1 = await requestData('BASE')
const res2 = await requestData(res1 + 'aaa')
const res3 = await requestData(res2 + 'bbb')
console.log(res3);
}