记迭代器与生成器的用法

161 阅读8分钟

迭代器

迭代器和可迭代协议

在认识迭代器与可迭代协议之前,我们来区分下一下两个概念

  • 遍历 由多个数据组成的数据集合(map、set、array等),本质来说,它们将一个动作重复了零次或者多次,也可以说依次从数据结构中取出数据进行某种处理。
  • 迭代 按照某种逻辑依次进行处理。

个人认为迭代包含遍历,遍历依托于数据结构,而迭代并不要求有数据结构。

未命名文件.png

for offor in的区别

  1. 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)
    }
}
  1. 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"

  1. 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属性,判定是否迭代结束。
  1. 如下创建了一个简单的iterator,对象上有两个属性totalcurrent,前者是用来计算调用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}
  1. 输出斐波那契数列的前N

如下代码 用ab表示第一项和第二项的初始值,currentIndex用来记录第一项和第二项的值,当是第一项和第二项值时,调用next方法value值都是1,当currentIndex值变成3, 前两项的值被变量c给记录下来,bc的值赋值给ab,调用next方法此时返回的对象为ab两项之和的值。

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);
}
  1. 通常,当donetrue时,表示不能进行迭代
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方法,只有返回的donefalse,则将返回的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创建的对象,该对象既是一个迭代器,同时也是一个可迭代对象(满足可迭代协议的要求)

未命名文件 (1).png

换句话说迭代器是生成器的子集,生成器是迭代器的超集。
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

  1. yield关键字只能在生成器内部使用,不可以在普通函数内部使用
  2. 它表示暂停,并返回一个当前迭代的数据
  3. 如果没有下一个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