目录

迭代器生成器

# 什么是迭代器

在JavaScript中,迭代器是一个具体的对象,这个对象需要符合迭代器协议

  • 迭代器协议定义了产生一系列值的标准方法
  • 在JavaScript中这个标准就是一个特定的next方法

next方法的要求

  • 一个无参数或者一个参数的函数,返回一个应当拥有一下两个属性的对象
  • done(Boolean类型)
    • 如果迭代器可以产生序列中的下一个值,则为false。(相当于没有指定done这个属性)
    • 如果迭代器已将序列迭代完毕,则为true。这种情况下value是可选的,如果它依然存在,即为迭代结束之后的默认返回值
  • value:迭代器返回的任何JavaScript值,done为true时可以省略
const arr = ["ls", "zs", "ws"];
let index = 0;
const arrIterator = {
  next: function () {
    if (index < arr.length) {
      return {
        done: false,
        value: arr[index++],
      };
    } else {
      return { done: true, value: undefined };
    }
  },
};
console.log(arrIterator.next());
console.log(arrIterator.next());
console.log(arrIterator.next());
console.log(arrIterator.next());
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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 arr = ["ls", "zs", "ws"];
const arrIterator = createArrayIterator(arr);
console.log(arrIterator.next());
console.log(arrIterator.next());
console.log(arrIterator.next());
console.log(arrIterator.next());
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 可迭代对象

当我们获取一个数组的时候,需要自己创建一个index变量,再创建一个所谓的迭代器对象;事实上可以进一步封装让一个不可迭代对象变成一个可迭代对象

# 什么是可迭代对象

  • 和迭代器是不同的概念
  • 当一个对象实现了iterator protocol协议时,它就是一个可迭代对象
  • 这个对象的要求是必须实现iterator方法,在代码中使用Symbol.iterator访问该属性

转换成为可迭代对象的好处

  • 当一个对象变成一个可迭代对象的时候,就可以进行某些迭代操作
  • 例如for...of操作时,其实就是在调用它的iterator方法。
const info = {
  friends: ["ls", "Zs", "we"],
  [Symbol.iterator]: function () {
    let index = 0;
    return {
      next: () => {
        // 这里使用箭头函数是因为里面需要使用this,为了不改变this的指向
        if (index < this.friends.length) {
          return { done: false, value: this.friends[index++] };
        } else {
          return { done: true, value: undefined };
        }
      },
    };
  },
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

可迭代对象中函数名[Symbol.iterator]是不能为其它名称的

# 原生迭代对象

是平时开发中很多原生对象已经实现可迭代协议,会生成一个迭代对象。

String,Array,Map,Set,arguments对象, NodeList集合

const str = "Hello World";
for (const s of str) {
  console.log(s);
}
const arr = ["abc", "cba", "nba"];
for (const item of arr) {
  console.log(item);
}
function foo(x, y, z) {
  for (const arg of arguments) {
    console.log(arg);
  }
}
foo(20, 30, 40);
const set = Array.from(str);
console.log(set);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const names = ["abc", "cba", "nba"];
console.log(names[Symbol.iterator]);
const iterator = names[Symbol.iterator]();
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
1
2
3
4
5
6
7

# 可迭代对象应用

可迭代对象应用场景

  • 在JavaScript中:for...of,展开语法,yield,解构赋值
  • 创建对象时:new Map([Iterator]),new WeakMap([Iterator]),new Set([Iterator]),new WeakSet([Iterator])
  • 一些对象调用:Promise.all(iterator),Promise.race(iterator),Array.from(iterator)
const info = ["abc", "cba"];
for (const item of info) {
  console.log(item);
}
console.log([...info, "bnf"]);
const [name1, name2] = info;
console.log(name1, name2);
console.log(new Set(info));
console.log(Array.from(info));
Promise.all(info).then((res) => {
  console.log(res);
});
1
2
3
4
5
6
7
8
9
10
11
12

# 自定义类的迭代

在面向对象开发中,可以通过class定义一个自己的类,这个类可以创建很多对象;如果希望自己的类创建出来的对象是可迭代的,那么在设计类的时候可以添加@@iterator方法

class Person {
  constructor(name, age, friends) {
    this.age = age;
    this.name = name;
    this.friends = friends;
  }
  [Symbol.iterator]() {
    let index = 0;
    return {
      next: () => {
        if (index < this.friends.length) {
          return { done: false, value: this.friends[index++] };
        } else {
          return { done: true };
        }
      },
    };
  }
}
const p1 = new Person("zs", 20, ["sd", "ls"]);
const p2 = new Person("ls", 20, ["ww", "ws"]);
for (const f1 of p1) {
  console.log(f1);
}
for (const f2 of p2) {
  console.log(f2);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# 迭代器中断

迭代器在某些情况下会在没有完全迭代的情况下中断;比如遍历的过程中通过breakreturnthrow中断了循环操作;比如在结构的时候,没有解构所有的值。

如果想要监听中断,可以添加return方法

class Person {
  constructor(name, age, friends) {
    this.age = age;
    this.name = name;
    this.friends = friends;
  }
  [Symbol.iterator]() {
    let index = 0;
    return {
      next: () => {
        if (index < this.friends.length) {
          return { done: false, value: this.friends[index++] };
        } else {
          return { done: true };
        }
      },
      return() {
        console.log("迭代器中断");
        return { done: true };
      },
    };
  }
}
const p1 = new Person("zs", 20, ["sd", "ls"]);
for (const f1 of p1) {
  console.log(f1);
  if (f1 === "ls") {
    break;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
## 生成器

生成器是ES6中新增的一种函数控制,使用的方案,可以让我们更加灵活的控制函数什么时候继续执行、暂停执行

生成器函数也是一个函数,但是和普通函数有些些区别

  • 生产器函数需要在function后面添加一个*
  • 生成器函数可以通过yield关键字来控制函数的执行流程
  • 生成器函数的返回值是一个生成器;生成器是一种特殊的迭代器
    • 要想执行函数内部的代码。需要生成器对象,调用next操作
    • 遇到yield时,中断操作
function* foo(){
  console.log('1111')
  console.log('2222')
  yield
  console.log('3333')
  console.log('4444')
  yield
  console.log('5555')
  console.log('6666')
  console.log('7777')
const generator = foo() // 这里函数不会执行
generator.next() // 执行到第一个yield后面,第一个yield后面的代
}码也会执行
generator.next() // 继续执行到第二个yield后面的代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 生成器函数执行

直接调用生成器函数名并没有执行,只是返回了一个生成器对象;如果想要执行函数中的东西需要调用next函数;next是有返回值的;很多时候不希望next返回的是一个undefined,这个时候可以通过yield来返回结果

function* foo() {
  console.log("函数执行~");
  const value1 = 100;
  console.log(value1);
  yield value1;
  const value2 = 200;
  console.log(value2);
  yield value2;
  const value3 = 300;
  console.log(value3);
  yield value3;
  console.log("函数执行完毕");
}
const generator = foo();
// 执行到第一个yield,并暂停
console.log(generator.next()); // {value: 100, done: false}
// 执行到第二个yield,并暂停
console.log(generator.next()); // {value: 200, done: false}
// 执行到第三个yield,暂停
console.log(generator.next()); // {value: 300, done: false}
// 函数执行完毕
console.log(generator.next()); // {value: undefined, done: true}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 生成器传递参数

# next函数

生成器函数可以暂停分段来执行,因此也可以分段来传递参数。在调用next函数的时候,可以传递参数,这个参数会作为上一个yield语句的返回值;即为本次的函数代码块执行提供了一个值

function* foo(initial) {
  console.log("函数执行");
  const value1 = yield initial + "aaa";
  const value2 = yield value1 + "bbb";
  const value3 = yield value2 + "ccc";
}
// 第一次传递参数通过调用函数的时候传入
const generator = foo("zs");
const result1 = generator.next();
console.log("result1: ", result1); // result1:  {value: 'zsaaa', done: false}
const result2 = generator.next(result1.value);
console.log("result2: ", result2); // result2:  {value: 'zsaaabbb', done: false}
const result3 = generator.next(result2.value);
console.log("result3: ", result3); // result3:  {value: 'zsaaabbbccc', done: false}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 生成器提前结束---return函数

还可以通过return函数给生成器函数传递参数,return传值后这个生成器函数就会结束,之后调用next函数不会继续生成值了

function* foo() {
  const value1 = yield "sdd";
  console.log("value1: ", value1);
  const value2 = yield value1;
  const value3 = yield value2;
}
const generator = foo();
console.log(generator.next()); // {value: 'sdd', done: false}
console.log(generator.return("123")); // {value: '123', done: true}
console.log(generator.next()); // {value: undefined, done: true}
1
2
3
4
5
6
7
8
9
10

# 生成器抛出异常---throw函数

除了给生成器函数内部传递参数之外,也可以给生成器函数内部抛出异常;抛出异常后可以在生成器函数中捕获异常,但是在catch语句中不能继续yield新的值了,但是可以在catch语句外使用yield继续中断函数的执行。

function* foo() {
  console.log("函数开始执行");
  try {
    yield "zs";
  } catch (err) {
    console.log("函数内部捕获异常:", err);
  }
  yield 2222;
  console.log("函数执行结束");
}
const generator = foo();
const result = generator.next();
console.log(result); // {value: 'zs', done: false}
const result2 = generator.throw("throw error"); // 函数内部捕获异常: throw error
console.log(result2); // {value: 2222, done: false}
console.log(generator.next());
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 生成器代替迭代器

生成器是一种特殊的迭代器,在某些情况下可以使用生成器来代替迭代器

// 迭代器
const arr = ["abc", "cba", "cnc"];
function createArrIterator(arr) {
  let index = 0;
  return {
    next: () => {
      if (index < arr.length) {
        return { done: false, value: arr[index++] };
      } else {
        return { done: true, value: undefined };
      }
    },
  };
}
const arrIterator = createArrIterator(arr);
/* console.log(arrIterator.next());
console.log(arrIterator.next());
console.log(arrIterator.next());
console.log(arrIterator.next()); */

// 生成器
function* createArrIteratorGen(arr){
  for(const item of arr){
    yield arr
  }
}
const arrGenerator = createArrIteratorGen(arr)
console.log(arrGenerator.next());
console.log(arrGenerator.next());
console.log(arrGenerator.next());
console.log(arrGenerator.next());
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
function* createRangeIterator(start, end) {
  for (let i = start; i < end; i++) {
    yield i;
  }
}
const rangeIterator = createRangeIterator(10, 20);
console.log(rangeIterator.next());
console.log(rangeIterator.next());
console.log(rangeIterator.next());
console.log(rangeIterator.next());
console.log(rangeIterator.next());
console.log(rangeIterator.next());
console.log(rangeIterator.next());
1
2
3
4
5
6
7
8
9
10
11
12
13

也可以使用yield*来产生一个可迭代对象,相当于是一个语法糖,只不过会依次迭代这个可迭代对象,每次迭代其中的一个值。

function* createArrIteratorGen(arr){
  yield* arr
}
1
2
3

# 自定义类迭代--生成器实现

class Classroom {
  constructor(name, address, initialStudent) {
    this.name = name;
    this.address = address;
    this.students = initialStudent || [];
  }
  push(student) {
    this.students.push(student);
  }
  *[Symbol.iterator]() {
    yield* this.students;
  }
}
const classRoom = new Classroom("zs", "sd", ["as", "cd", "fg"]);
const generator = classRoom[Symbol.iterator]();
console.log(generator.next());
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 生成器操作

生成器是一个迭代器,那么可以对生成器做一些操作

const nameIterator1 = createArrayIterator(names)
for(const item of nameIterator1){
    console.log(item)
}

const namesIterator2 = createArrayIterator(names)
const set = new Set(nameIterator2)
console.log(set)

const nameIterator3 = createArrayIterator(names)
Promise.all(nameIterator3).then(res => {
    console.log(res)
})
1
2
3
4
5
6
7
8
9
10
11
12
13

# 异步处理方法

假设一个场景,需要向服务器发送网络请求获取数据,一共需要发送三次请求

function requestData(url) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(url)
        }, 2000)
    })
}
1
2
3
4
5
6
7

方案一

function getData(){
    requestData('zs').then(res1 => {
        requestData(res1+ 'aaa').then(res2 => {
            requestData(res2+'bbb').then(res3 => {
                console.log('res3: ', res3)
            })
        })
    })
}
1
2
3
4
5
6
7
8
9

方案二

function getData(){
    requestData('zs').then(res1 => {
        return requestData(res1+'aaa')
    }).then(res2 => {
        return requestData(res2+'bbb')
    }).then(res3 => {
        console.log('res3: ', res3)
    })
}
1
2
3
4
5
6
7
8
9

# 生成器的解决方案

上面的代码阅读性很差,因此需要进一步对代码进行优化

function* getData(){
    const res1 = yield requestData('zs')
    const res2 = yield requestData(res1+'aaa')
    const res3 = yield requestData(res2+'bbb')
    const res4 = yield requestData(res3+'ccc')
    console.log(res4)
}

const generator = getData()
generator.next().value.then(res => {
    generator.next(res).value.then(res => {
        generator.next(res).value.then(res => {
            generator.next(res)
        })
    })
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 自动执行generator函数

上面的方案中仍然存在问题

  • 不能确定需要调用多少层Promise关系
  • 如果还有其它需要这样执行的函数,如何操作

因此需要封装一个工具函数用于自动执行生成器函数

function execGenerator(genFn) {
    const generator = genFn()
    function exec(res) {
        const result = generator.next(res)
        if(result.done) return result.value
        result.value.then(res => {
            exec(res)
        })
    }
}
1
2
3
4
5
6
7
8
9
10
上次更新: 2023/01/11, 08:37:08
最近更新
01
防抖和节流
02-06
02
正则表达式
01-29
03
async_await函数
12-30
更多文章>