都ES13了,迭代器、生成器还没搞懂?
前言
大家好,我是侃如,最近在复习ES6,打算扫清一些自己之前学习忽略的知识点。看了一下,不知不觉都到ES13了(类加了#关键字、await也可以顶层调用了......) 新特性暂且不提(有机会后面总结下)。跟身边的小伙伴沟通了下,发现部分同学对迭代器、生成器还比较模糊,怎么用?为什么用?完全是一头雾水,借这个机会跟大家分享下自己的复习成果,希望也能帮大家扫除一些知识盲点。
不要在面试官问你时,才想起来被自己忽略的知识点。
迭代器
Iterator被称为迭代器,在JavaScript中,迭代器是一个函数对象,它存在于可迭代对象的原型链中,可以通过可迭代对象的生成方法Symbol.iterator来访问。
迭代器可以通过使用next() 方法实现迭代,next()方法会返回具有两个属性的对象:value和 done,value表示当前迭代的值,done是一个布尔值表示迭代是否结束。如果迭代结束,则done返回true否则返回false。
const arr = ["a","b","c"];
//Array Iterator {}
console.log(arr[Symbol.iterator]());
const it = arr[Symbol.iterator]();
//{value: "a", done: false}
console.log(it.next());
//{value: "b", done: false}
console.log(it.next());
//{value: "c", done: false}
console.log(it.next());
//{value: undefined, done: true}
console.log(it.next());
迭代协议
迭代协议分为两个:可迭代协议、迭代器协议。
可迭代协议:定义迭代行为的协议,一个对象本身或原型链上存在一个可以通过Symbol.iterator调用的iterator属性,那么这个对象就是可迭代对象。
迭代器协议:定义了迭代器的迭代方式,调用next()方法,返回对应结构。
内置可迭代对象
自身或原型链上存在Symbol.iterator方法的对象被称为可迭代对象,自身或原型链本就内置Symbol.iterator方法的对象被称为内置可迭代对象。 常见的内置可迭代对象:
- Array
- string
- Set
- Map
- 类数组对象
这块不清楚的可以阅读往期文章: Set、Map、类数组,傻傻区分不清楚?
生成器
生成器允许我们创建一个可以自动维护自己的状态的迭代函数,它是可迭代对象也是迭代器。
生成器函数的特点:
生成器通过 function* 创建,和普通函数相比它多了一个 * ,调用生成器函数时它不会执行任何代码,仅会返回一个叫 Generator 的迭代器,可以按迭代器协议进行迭代。
function* fun() {
console.log(1);
}
fun();//不执行内部打印
//1
//{value: undefined, done: true}
console.log(fun().next());
生成器函数多次调用该函数,每次都会返回一个新的 Generator,每个 Generator 仅可以迭代一次。
yield 表达式
生成器函数中可以书写yield 表达式用来定义函数的内部状态, Generator会指向这个状态。
function* fun() {
console.log(1)
yield 'a'
console.log(2)
yield 'b'
console.log(3)
yield 'c'
}
const g = fun()
//1
// {value: "a", done: false}
console.log(g.next());
//2
// {value: "b", done: false}
console.log(g.next());
//3
// {value: "c", done: false}
console.log(g.next());
// {value: undefined, done: true}
console.log(g.next());
next传参
next 方法可以传入参数,传入的参数会作为上一步yield的返回值。
function* fun() {
console.log("start")
const a = yield 'a'
console.log(a)
const b = yield 'b'
console.log(b)
const c = yield 'c'
console.log(c)
}
const g = fun()
//start
// {value: "a", done: false}
console.log(g.next("d"));
//e
// {value: "b", done: false}
console.log(g.next("e"));
//f
// {value: "c", done: false}
console.log(g.next("f"));
//g
// {value: undefined, done: true}
console.log(g.next("g"));
看一下上面这段代码,可能不是那么好理解,可以多捋捋。
第一次给next()传的参数是"d",但是上一步没有yield,也没有打印,打印start,value为"a",done为false;
第二次给next()传的参数是"e",上一步存在yield,打印"e",value为"b",done为false;
第三次给next()传的参数是"f",上一步存在yield,打印"f",value为"c",done为false;
第四次给next()传的参数是"g",上一步存在yield,打印"g",此时不存在yield,value为undefined,done为true,结束迭代;
return方法
当生成器函数使用return方法时,会返回其携带的参数,同时结束Generator。
function* fun() {
console.log(1)
yield 'a'
console.log(2)
yield 'b'
console.log(3)
yield 'c'
}
const g = fun()
//1
// {value: "a", done: false}
console.log(g.next());
// {value: "return", done: true}
console.log(g.return("return"));
// {value: undefined, done: true}
console.log(g.next());
// {value: undefined, done: true}
console.log(g.next());
yield* 表达式
yield* 会返回一个迭代器对象,相当于在Generator内部再调用另一个Generator。
function* fun() {
yield 'a';
yield 'b';
yield 'c';
}
function* fun1() {
yield* fun();
}
const g = fun1()
// {value: "a", done: false}
console.log(g.next());
// {value: "b", done: false}
console.log(g.next());
// {value: "c", done: false}
console.log(g.next());
// {value: undefined, done: true}
console.log(g.next());
为什么要使用迭代器、生成器?
大家都知道在JavaScript中,遍历的方法很多:遍历数组的for循环、遍历Set、Map的forEach、遍历对象的for...in等。那为什么要再加一个迭代器呢? 迭代器是一个统一的遍历方法,无论是数组、字符串还是Set、Map只要是可迭代对象都可以用它遍历。
当然,next()方法过于繁琐,需要频繁调用,我们通常会使用for...of来进行遍历,可以使用for...of的必定是可迭代对象。
使用生成器函数可以将一个不可迭代的对象变成一个可迭代对象:
const obj = {};
obj[Symbol.iterator] = function* () {
yield "a";
yield "b";
yield "c";
};
for (const value of obj) {
//a
//b
//c
console.log(value);
}
转载自:https://juejin.cn/post/7174794945453097017