迭代器
迭代器和可迭代协议
在认识迭代器与可迭代协议之前,我们来区分下一下两个概念
- 遍历 由多个数据组成的数据集合(map、set、array等),本质来说,它们将一个动作重复了零次或者多次,也可以说依次从数据结构中取出数据进行某种处理。
- 迭代 按照某种逻辑依次进行处理。
个人认为迭代包含遍历,遍历依托于数据结构,而迭代并不要求有数据结构。
for of、for in的区别
- for in会遍历该数据的原型链上的所有属性,而
for of并不会 如下:定义了一个变量str,并且在变量str的原型上添加了一个方法say,在for in函数体内,可以看到say方法的属性名被输出。
// for in
var str = "123"
String.prototype.say = function(){}
for(let i in str){
console.log(str[i]) // 1 2 3 say
}
// for of
var string = "123"
String.prototype.say = function(){}
for(let i of string){
console.log(i) //依次输出 1 2 3
}
如果在这方面达到一样的效果,可以使用hasOwnProperty方法进行数据的筛选
let obj = {name:111}
Object.prototype.say = function(){}
for(let key in obj){
if(obj.hasOwnProperty(key)){
// 过滤原型身上的属性和方法
console.log(key)
}
}
for...in循环遍历的结果是数组元素的下标,而for...of遍历的结果是元素的值
let arr = [3, 5, 7];
arr.foo = "hello";
for (let i in arr) {
console.log(i); // 输出 "0", "1", "2", "foo"
}
for (let i of arr) {
console.log(i); // 输出 "3", "5", "7"
}
// 注意 for...of 的输出没有出现 "hello"
for of是在可迭代对象(包括Array、Map、Set、argements等等)上创建一个循环 如下,在一个普通对象上使用for of循环会报错
for(let i of {name:111}){
console.log(i) //Uncaught TypeError: {(intermediate value)} is not iterable
}
必须满足以下条件:for of循环必须是可迭代的,满足可迭代协议
可迭代协议的约束如下:
- 必须要要有一个知名符号(Symbol.iterator)
- 该属性值必须是一个无参的迭代器创建函数
普通对象上并没有Symbol.iterator,因此并不能被for of所迭代
// 普通对象上
let obj = {name:111}
obj[Symbol.iterator] //undefined
// 数组
let arr = []
arr[Symbol.iterator]
// ƒ values() { [native code] }
迭代器iteator
JS语言规定,如果一个对象具有next方法,并且next方法满足一定的约束,则该对象是一个迭代器(iterator)
next约束的方法:该方法必须返回一个对象,该对象至少具有两个属性
- value: any类型,下一个数据的值,如果
done属性为true,通常会将value设置为undefined - done: boolean类型,是否已经完成迭代
通过迭代器的
next方法,可以一次取出数据,并可以根据返回的done属性,判定是否迭代结束。
- 如下创建了一个简单的iterator,对象上有两个属性
total、current,前者是用来计算调用next方法的次数,后者用来记录当前的第几次,第一次调用iterator.next()时,此时的done属性false,只有当current属性的值大于total时,为false。
// https://codesandbox.io/s/priceless-keller-hn9ebk?file=/index.html:281-762
var iterator = {
total: 3,
current: 1,
next() {
var obj = {
value: this.current > this.total ? undefined : Math.random(),
done: this.current > this.total
};
this.current++;
return obj;
}
};
console.log(iterator.next()); // {value: 0.9512107146780628, done: false}
console.log(iterator.next()); // {value: 0.24093622657136615, done: false}
console.log(iterator.next()); // {value: 0.11543283780714986, done: false}
console.log(iterator.next()); // {value: undefined, done: true}
- 输出斐波那契数列的前
N位
如下代码 用a、b表示第一项和第二项的初始值,currentIndex用来记录第一项和第二项的值,当是第一项和第二项值时,调用next方法value值都是1,当currentIndex值变成3,
前两项的值被变量c给记录下来,b、c的值赋值给a、b,调用next方法此时返回的对象为a、b两项之和的值。
var iterator = {
a: 1,
b: 1,
currentIndex: 1,
next() {
if (this.currentIndex == 1 || this.currentIndex == 2) {
this.currentIndex++;
return {
value: 1,
done: false
};
}
var c = this.a + this.b;
this.a = this.b;
this.b = c;
return {
value: c,
done: false
};
}
};
for (let i = 0; i < 10; i++) {
console.log(iterator.next().value);
}
- 通常,当
done为true时,表示不能进行迭代
var iterator = {
total: 3, //可迭代3次
i: 1, //当前迭代的次数
next() {
var obj = {
value: this.i > this.total ? undefined : Math.random(),
done: this.i > this.total
};
this.i++;
return obj;
}
};
var next = iterator.next();
while (!next.done) {
//done为false调用该函数体,会调用该函数体
console.log(next.value);
next = iterator.next();
}
迭代器创建函数
它是一个函数,调用函数后,返回一个迭代器,则称该函数为迭代器创建函数,可以简称迭代器函数
//一个简单的迭代器创建函数,当迭代结束此时的i变成数组的长度
function createArrayInterator(arr) {
var i = 0;
var obj = {
next() {
return {
value: arr[i++],
done: i > arr.length
};
}
};
return obj;
}
var iterator = createArrayInterator([3, 4, 4, 5, 6, 6]);
console.log(iterator.next()); // {value: 3, done: false}
console.log(iterator.next()); // {value: 4, done: false}
for of循环的原理
调用对象的[Symbol.iterator]方法,得到一个迭代器。不断调用next方法,只有返回的done为false,则将返回的value传递给变量,然后进入下一次循环体。
// 让普通对象支持可以被for of遍历
var obj = {
[Symbol.iterator]: function() {
var total = 3;
i = 1;
return {
next() {
var obj = { //当前这一次迭代到的数据
value: i > total ? undefined : Math.random(),
done: i > total
}
i++;
return obj;
}
}
}
}
//模拟for-of循环
var iterator = obj[Symbol.iterator]();
var result = iterator.next();
while (!result.done) {
//有数据
const item = result.value;
console.log(item); //执行循环体
result = iterator.next();
}
for (const item of obj) {
console.log(item)
}
生成器
生成器:由构造函数Generator创建的对象,该对象既是一个迭代器,同时也是一个可迭代对象(满足可迭代协议的要求)
function* generator() {
yield 1;
yield 2;
}
const iterator = generator();
for (let item of iterator) {
console.log("item", item); // 1 2
}
//伪代码
var generator = new Generator()
generator.next() //具有next方法
var iterator = generator[Symbol.iterator] // 它也是一个可迭代对象
注意Generator构造函数,不提供给开发者使用,仅作为JS引擎使用
生成器函数(生成器创建函数):该函数用于创建一个生成器函数
es6新增了一个特殊的函数,叫做生成器函数,只要在函数名与function之间加上一个*号,则该函数会自动返回一个生成器
生成器函数的特点
1. 调用生成器函数,会返回一个生成器,而不是执行函数体(因为生成器的函数体受到函数生成器控制)
function* get(){
console.log("我被调用了") //这句话并没有执行
}
get()
// 返回生成器
function* generator() {
console.log("generator函数开始");
yield 1;
console.log("generator1111");
yield 2;
console.log("generator222");
}
const iterator = generator();
2. 每当调用了生成器的next方法,生成器的函数体会从上一次yield的位置(或开始位置)运行到下一个yield
- yield关键字只能在生成器内部使用,不可以在普通函数内部使用
- 它表示暂停,并返回一个当前迭代的数据
- 如果没有下一个yield,到了函数结束,则生成器的next方法得到的结果中的done为true,生成器函数的返回值作为value的属性值
function* generator() {
console.log("generator函数开始");
yield 1;
console.log("generator1111");
yield 2;
console.log("generator222");
}
const iterator = generator();
iterator.next() //返回值{value: 1, done: false}
iterator.next() //返回值{value: 2, done: false}
iterator.next() //返回值{value: undefined, done: true}
3. yield关键字后面的表达式返回的数据,会作为当前迭代的数据
迭代第一次时yield表达式的值作为调用iterator.next()返回对象的value属性值
function* generator() {
console.log("generator函数开始");
yield 1;
console.log("generator1111");
yield 2;
console.log("generator222");
}
const iterator = generator();
iterator.next() //返回值{value: 1, done: false}
4. 生成器的返回值,会作为迭代结束的value值
- 但是在结束之后仍然调用
next,则value值变成undefined
//下面这种情况
function* generator() {
console.log("generator函数开始");
yield 1;
console.log("generator1111");
yield 2;
console.log("generator222");
return 111 //这里有返回值
}
const iterator = generator();
iterator.next() //返回值{value: 1, done: false}
iterator.next() //返回值{value: 2, done: false}
iterator.next() //返回值{value: 111, done: true} //此时已经结束,取generator函数的返回值,value属性值变成 111
iterator.next() //返回值{value: undefined, done: true} //此时已经结束
5. 生成器调用next的时候,可以传递参数,该参数会作为生成器函数体上一次函数yield表达式的值
第一次调用next函数时,传递参数没有任何意义
function* generator() {
console.log("generator函数开始");
let result = yield 1;
console.log("yield1的结果", result); // 3
result = yield 2;
console.log("yield2的结果", result); // 4
}
const iterator = generator();
iterator.next()
iterator.next(3);
iterator.next(4);
6.生成器带有一个throw方法,该方法与next效果相同,唯一的区别在于:
- next方法传递的参数会被返回成一个正常值
- throw方法传递的是一个错误对象,会导致生成器函数内部发生一个错误
function* generator() {
console.log("generator函数开始"); //generator函数开始
let result = yield 1;
console.log("yield1的结果", result); // yield1的结果 3
result = yield 2; //到这一步报错 Uncaught 4
console.log("yield2的结果", result);
}
const iterator = generator();
iterator.next();
iterator.next(3);
iterator.throw(4);
7.生成器带有一个return方法,该方法会直接结束生成器函数
function* generator() {
console.log("generator函数开始");
let result = yield 1;
console.log("yield1的结果", result);
result = yield 2;
console.log("yield2的结果", result);
}
const iterator = generator();
iterator.next() //{value:1,done:false}
iterator.return() //{value:undefined,done:true} //直接结束
8. 若需要在生成器内部调用其它生成器,注意如果需要直接调用,得到的是一个生成器,如果加*调用,则进入生成器内部执行。如果是yield* 函数()调用,则返回该函数的返回结果,为表达式的结果。
function* g2() {
console.log("g2开始被调用"); //2
yield 1; //2返回{value:1,done:false}
console.log("该g2 111"); //3
yield 2; //3 返回{value:2,done:false}
console.log(333); // 4
return "true"
}
function* generator() {
console.log("generator函数开始"); // 1
let result = yield 1;
console.log("yield1的结果", result); //2
result = yield* g2()
console.log(result) //result的值为
result = yield 2;
console.log("yield2的结果", result);
}
const iterator = generator();
iterator.next();
iterator.next();
iterator.next();
iterator.next();
iterator.next();
第一次调用iterator.next()时,此时已经注释为1的这句话被执行,并且返回{value:1,done:false},第二次调用iterator.next()时,此时result变量的值为undefined,并且进入了g2的函数体,遇到了yield 1,这时候返回{value:1,done:false},第三次调用时,注释2被输出,并返回{value:2,done:false},第4次被调用时,此时的注释4被输出,并且result的值是g2函数的返回值,为字符串'true',并且得到一个返回值{value:2,done:false},第5次被调用时,此时由于next方法并没有传递参数,此时result的值为undefined