JS面试手写:实现一个类,实例的方法可以链式调用且会sleep

366 阅读2分钟

实现一个类,其实例的方法可以链式调用,它有一个 sleep 方法,可以 sleep 一段时间后再后续调用,还有一个firstSleep,只能链式调用一次,优先级最高,优先sleep一段时间再执行后续代码。

案例:

const boy = new PlayBoy("coder");
boy
  .sayHi()
  .play("王者")
  .firstSleep(3000)
  .sleep(1000)
  .play("原神")
  .sleep(2000)
  .play("明日方舟");
  
// 执行结果:
// firstSleep:3000
// 大家好我是coder
// 我在玩王者
// sleep:1000
// 我在玩原神
// sleep:2000
// 我在玩明日方舟

一看到链式调用就想到Promise或者async/await方向去了,思维定式,事实是思路走不通,而是要使用任务队列。

思路其实是用一个任务队列去收集所有链式调用的函数,这个收集的过程就是在实例化之后的链式调用过程,同时在构造函数中,开启一个宏任务事件,当同步代码(也就是子类的链式调用收集函数)执行完成之后,开始顺序执行队列收集的函数

实现:


class PlayBoy {
  constructor(name) {
    this.name = name;
    this.queue = []; // 记录调用的函数,全部是同步收集然后依次执行
    this.index = 0; // 执行函数的索引
    this.firstSleepWatch = false; // 检查 firstSleep 调用次数,最多调用一次
    this.init();
  }

  // 因为执行需要在函数收集之前,因此需要利用事件机制,先收集后执行
  init() {
    setTimeout(() => {
      console.log("此时函数收集完了,开始调用任务队列中的函数了:", this.queue);
      this.run();
    }, 0);
  }

  run() {
    const fn = this.queue[this.index++];
    // 最后一个 fn 为 undefined, 因此需要做处理
    fn && fn();
  }

  firstSleep(delay) {
    if (this.firstSleepWatch) {
      throw Error("Already declared firstSleep!!");
    }
    // 将该方法添加到队头
    this.queue.unshift(() => {
      setTimeout(() => {
        console.log(`firstSleep:${delay}`);
        this.run();
      }, delay);
    });
    this.firstSleepWatch = true;
    return this;
  }

  sleep(delay) {
    this.queue.push(() => {
      setTimeout(() => {
        console.log(`sleep:${delay}`);
        this.run();
      }, delay);
    });
    // 返回当前的实例对象,以便后续链式调用
    return this;
  }

  sayHi() {
    this.queue.push(() => {
      console.log(`大家好我是${this.name}`);
      this.run();
    });
    return this;
  }

  play(game) {
    this.queue.push(() => {
      console.log(`我在玩${game}`);
      this.run();
    });
    return this;
  }
}

// 实例在链式调用之前会先收集所有的调用函数,按照顺序放入队列中,收集完成后顺序执行
const boy = new PlayBoy("coder");
boy
  .sayHi()
  .play("王者")
  .firstSleep(3000)
  .sleep(1000)
  .play("原神")
  .sleep(2000)
  .play("明日方舟");