迭代器(iterator)、生成器(generator)

初次邂逅

  • 迭代器:具备遍历的功能;
  • 一般可迭代对象具有Symbol.iterator属性
  • 可迭代(可被遍历)的内置对象:数组(# Array.prototype[Symbol.iterator]())、字符串(String.prototype[Symbol.iterator]())等等;
let arr = [1,2,3]
for(let item of arr){
    console.log(item)
}

let str = 'lxx'
for(let char of arr){
    console.log(char)
}
  • 自定义可遍历对象,具备Symbol.iterator属性
let obj = {
  items: [6, 7, 8],
  [Symbol.iterator]() {
    let idx = 0;
    let next = () => {
      if (idx < this.items.length) {
        return { value: this.items[idx++], done: false };
      } else {
        return { done: true };
      }
    };
    return { next };
  },
};
// 遍历对象时会调用自动调用Symbol.iterator方法
for (let item of obj) {
  console.log(item);
}
  • 生成器函数,有*标识并且和关键字yield搭配使用,可用来控制函数的执行,可暂停函数的执行
// 相当于gen函数中有三个待执行的任务,希望挨个进行执行处理
function* gen() {
  yield 1;
  yield 2;
  yield 3;
}
let it = gen()
console.log(it)
  • 调用生成器函数生成迭代器对象,具有nextreturnthrow等方法
  • 调用一次next方法,就会执行一个yield,有nyield,则需要调用n + 1next
  • 每调用一次next时,执行的代码内容(范围)是上边的代码到yield右侧(不包含左边的赋值)
let res1 = it.next();
console.log(res1); // {value: 1, done: false}, value相当于是yield产生的值,done代表是否已经迭代完

let res2 = it.next();
console.log(res2); // {value: 2, done: false}

let res3 = it.next();
console.log(res3); // {value: 3, done: false}

let res4 = it.next();
console.log(res4); // {value: 3, done: false}
  • 除第一个调用的next之外,后面调用时传递的参数,都会传(赋值)给相应位置的yield左侧的变量
function* gen() {
  let r1 = yield 1;
  console.log(r1); // b
  let r2 = yield 2;
  console.log(r2); // c
  let r3 = yield 3;
  console.log(r3); // d
}
let it = gen()
it.next('a');
it.next('b');
it.next('c');
it.next('d');
  • 因为生成器函数的执行结果是个迭代器对象,所以可以使用for...of进行遍历,得到的每一项是yield产生的值,有几个yield就会循环几项
for(let item of it){
    console.log(item)
}
  • 因为生成器函数中不确定有几个yield(不确定有几个待执行的任务),但每执行完一个任务会知道是否所有任务已经完全执行完了,所以可以使用do...while来执行
let res;
do {
    res = it.next(); // {value: 1, done: false}
    console.log(res.value);
} while (!res.done);
  • 还可以自定义函数递归的来执行迭代器对象
function next() {
    // 可以拿到每一个任务的结果,及是否全部执行完了
    let { value, done } = it.next();
    if (done) {
      console.log(所有任务执行完毕");
    } else {
      console.log(value);
      next(); // 继续向后执行
    }
}
next();
  • yield产生异步任务
function* gen() {
    yield 1;
    yield new Promise((resolve) => {
      setTimeout(() => {
        resolve(2);
      }, 2000);
    });
    yield 3;
}
let it = gen(); // 调用生成器函数,产生迭代器对象
console.log(it); // 拥有next方法,控制函数的执行
for (let item of it) {
    console.log(item); // 迭代(遍历)迭代器对象产生:1,promise,3
}
  • 可以借助自定义迭代方法,检测出产生值的类型,进行特殊处理
function next() {
    let { value, done } = it.next();
    if (done) {
      console.log("函数执行完毕");
    } else {
      console.log(value);
      if (value instanceof Promise) {
        // 如果值是一个promise,就调用then方法 
        value.then((res) => {
          console.log(res);
          next(); // 处理完promise后再继续执行后续任务
        });
      } else {
        next(); // 产生的普通值,就直接进行后续的任务
      }
    }
}

温故而知新

  • 自定义发布订阅模型,将任务存储起来,供后面调用
class PubSub {
  constructor() {
    // 存储不同类型的任务(事件)
    // 数据格式:{event1: [fn1, fn2, ...], event2: [cb1, cb2, ...]}
    this.events = {};
  }
  on(eventName, callback) {
    if (this.events[eventName]) {
      this.events[eventName].push(callback);
    } else {
      this.events[eventName] = [callback];
    }
  }
  emit(eventName) {
    this.events[eventName].forEach((callback) => callback());
  }
}
  • 按顺序执行每个任务
// 任务集合,有三个任务
function* tasks(){
    yield 111; // 任务一,最终产生的结果为111
    yield 222; // 任务二,最终产生的结果为222
    yield 333; // 任务三,最终产生的结果为333
}
// 调用生成器函数,获取迭代器对象
let it = tasks()
// 自定义遍历迭代对象的方法
function next(){
    // 调用next方法获取一个任务的结果value,及是否所有任务执行完的标识done
    let {value, done} = it.next()
    if(done){
        console.log('所有任务执行完毕')
    }else{
        // 输出任务结果,继续下一个任务
        console.log(value)
        next()
    }
}
// 从第一个任务开始执行
next()
  • 有异步任务,同样希望按顺序执行,执行完上一个任务,才会执行下一个任务
// 任务集合,有三个任务(包含异步任务)
function* tasks() {
  yield 111;
  yield new Promise((resolve) => {
    setTimeout(() => {
      resolve(222);
    }, 2000);
  });
  yield 333;
}

let it = tasks();

function next() {
  let { value, done } = it.next();
  if (done) {
    console.log("所有任务执行完毕");
  } else if (typeof value.then === "function") {
    // 是一个promise任务,等待执行完异步任务后,再继续向下执行其他任务
    value.then((res) => {
      console.log(res);
      next();
    });
  } else {
    console.log(value);
    next();
  }
}
next();
  • 暂停、恢复任务的执行
<button id="btn">btn</button>
<script>
  btn.onclick = () => {
    pubSub.emit("send"); // 触发订阅事件的执行,会恢复总任务的继续执行
  };
  class PubSub {
    constructor() {
      this.events = {};
    }
    on(eventName, callback) {
      if (this.events[eventName]) {
        this.events[eventName].push(callback);
      } else {
        this.events[eventName] = [callback];
      }
    }
    emit(eventName) {
      this.events[eventName].forEach((callback) => callback());
    }
  }
  let pubSub = new PubSub();

  // 任务集合,有三个任务(包含异步任务)
  function* tasks() {
    yield 111;
    yield new Promise((resolve) => {
      setTimeout(() => {
        resolve(222);
      }, 2000);
    });
    yield 333;
    yield "pause"; // 遇到该任务(标识)时暂停任务的执行
    yield 444;
  }

  let it = tasks();

  function next() {
    let { value, done } = it.next();
    if (done) {
      console.log("所有任务执行完毕");
    } else if (value === "pause") {
      // 遇到pause标识时,先订阅一个事件,不继续往下执行任务
      pubSub.on("send", () => {
        console.log("send事件触发了");
        next(); // 当该订阅的事件被执行了,则继续往下执行任务
      });
    } else if (typeof value.then === "function") {
      // 是一个promise任务,等待执行完异步任务后,再继续向下执行其他任务
      value.then((res) => {
        console.log(res);
        next();
      });
    } else {
      console.log(value);
      next();
    }
  }
  next();
</script>
  • 嵌套的生成器函数,执行时,一层层进去,再一层层出来,最后执行完所有任务
function isCustomIterable(obj) {
  // 检查对象是否有[Symbol.iterator]方法,且不能是字符串
  if (typeof obj[Symbol.iterator] === "function" && typeof obj !== "string") {
    // 获取迭代器
    const iterator = obj[Symbol.iterator]();
    // 检查迭代器是否有next方法
    if (typeof iterator.next === "function") {
      // 排除内置的数组迭代器
      if (!Array.isArray(obj)) {
        return true;
      }
    }
  }
  return false;
}
function* task2() {
  yield "任务二开始";
  yield new Promise((resolve) => {
    setTimeout(() => {
      resolve("任务三:异步任务");
    }, 2000);
  });
  yield "任务二结束";
}
function* task1() {
  yield "任务一开始";
  yield task2(); // 任务二
  yield "任务一结束";
}
function* tasks() {
  yield "总任务开始";
  yield task1(); // 任务一
  yield "总任务结束";
}

// 入口函数,执行生成器函数(generator)的函数(run)
function run(generator, cb) {
  const it = typeof generator === "function" ? generator() : generator; // 执行生成器函数,获取迭代器对象
  // 自定义挨个执行任务的函数,作用就是挨个执行任务,每个任务就是一个生成器函数,每个生成器函数可以生成一个迭代器对象
  // 每执行一次run方法,就会产生一个myNext方法
  function myNext() {
    const { value, done } = it.next(); // 执行next方法获取一个任务的结果value,及是否所有任务都完成的标识done
    if (done) {
      console.log("当前任务所有步骤都完成");
      cb && cb(); // 每个生成器函数中有n个yield,那么需要执行n+1次next,该生成器函数才能执行完(即该函数才能执行完)
    } else if (isCustomIterable(value)) {
      // 自定义迭代器对象
      // console.log(`结果的数据类型,${typeof value},结果(自定义迭代器值):${value}`);
      run(value, myNext); // 递归调用,执行生成器函数
    } else if (value instanceof Promise && typeof value.then === "function") {
      value.then((res) => {
        console.log(res);
        myNext();
      });
    } else {
      // 非自定义迭代器对象
      // console.log(`结果的数据类型:${typeof value},结果(普通值):${value}`);
      console.log(value);
      myNext();
    }
  }
  myNext();
}

run(tasks);

再见来不及挥手

  • 迭代器对象具有可被遍历的特性
  • 生成器函数可产生迭代器对象
  • 生成器函数里面的代码可被控制执行
  • 生成器函数中有nyield,那么需要调用n+1next方法,生成器函数才会执行完
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 222,183评论 6 516
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,850评论 3 399
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 168,766评论 0 361
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,854评论 1 299
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,871评论 6 398
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,457评论 1 311
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,999评论 3 422
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,914评论 0 277
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,465评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,543评论 3 342
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,675评论 1 353
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,354评论 5 351
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 42,029评论 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,514评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,616评论 1 274
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 49,091评论 3 378
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,685评论 2 360

推荐阅读更多精彩内容