编程题:实现一个 LazyMan 方法

简介: 前端西瓜哥

大家好,我是前端西瓜哥。今天我们来看一道 JS 编程题。

问题

实现一个LazyMan,可以按照以下方式调用:

LazyMan("Hank")
输出:
Hi! This is Hank!
LazyMan("Hank").sleep(10).eat("dinner")
输出
Hi! This is Hank!
//等待10秒..
Wake up after 10
Eat dinner
LazyMan("Hank").eat("dinner").eat("supper")
输出
Hi! This is Hank!
Eat dinner
Eat supper
LazyMan(“Hank”).sleepFirst(5).eat(“supper”)输出
//等待5秒
Wake up after 5
Hi! This is Hank!
Eat supper

以此类推。

需要实现的功能

我们先分析一下需要的效果。

首先是 Lazy('Hank') ,能够输出 Hi! This is Hank

然后是 .sleep(10),会延迟 10 秒后,执行 Wake up after 10。之后的 eat 之类的会跟着延迟执行。

.eat('dinner'),是直接输出 Eat dinner

最后是一个比较特殊的 .sleepFirst(5),它会被放到最前面提前执行,再执行其他事情。

思路

这道题不简单,它考察了多个知识点。

首先是它的使用形式为 链式调用,即对象的方法调用完后会返回这个对象,然后就可以继续调用这个对象的其他方法,形成一条链条一样的调用写法。

所以,这个 LazyMan('Hank') 应该返回一个对象,这个对象还必须有  sleepeatsleepFirst 这些方法。

所以,我先这样写:

function LazyMan(name) {
  const solver = {
    sleep(second) {
      return solver;
    },
    eat(something) {
      return solver;
    },
    sleepFirst(second) {
      return solver;
    },
  };
  return solver;
}

solver 的方法也可以返回 this。这里我没有返回 this,因为担心有 this 指向丢失的问题。

还有一种是使用 ES6 的 class 写法。

function LazyMan(name) {
 return new MyLazyMan(name);
}
class MyLazyMan {}

多了一层封装,但可以更好地维护属性。否则就像我的写法那样,需要用闭包来维护变量。

回到正题。

然后我们再实现依次输出的效果,因为其中的 sleep,是异步的,而且 sleepFirst 还会前置输出,所以我们不能每执行一个方法,就立即输出。而是要先缓存一下,等到所有方法都调用完之后,再执行。

此外,因为要收集好所有任务才开始执行,所以我们要用 setTimeout 构造一个异步的宏任务,确保任务的执行在同步代码后执行。

function LazyMan(name) {
  setTimeout(() => { // 确保不会过早执行
    run();
  });
  function run() {
    // 依次执行任务
  }
}

所以,我们需要一个 队列 来保存。队列是一种先进先出的线性表,我们用数组实现,理论上性能更好是用链表,但要自己实现很麻烦,通常数据量也不大,所以开发中我们用数组就完事了。

一个重要的分歧点出现了,这个队列,保存什么?

一种想法是 queue 存一个对象,里面有 msg 和 t,记录输出内容,和延迟执行的时间。然后我们自己在对象内部执行业务逻辑。

{
  msg: `Wake up after 10`,
  t: 10,
}

还有一种想法是,queue 里存的是函数,将它们依次执行就好。

实现上类似 中间件 的写法,本质是设计模式的 责任链模式。执行完当前函数,我们调用 next 去执行下一个函数。如果你用过 Express 框架,可能就觉得比较熟悉。

run 是一个递归函数,不停地执行自身,从 queue 里取出第一个 task,执行它,然后再执行 run 方法,直到 queue 为空。

代码实现

function LazyMan(name) {
  const queue = [
    {
      msg: `Hi! This is ${name}`,
      t: undefined,
    },
  ];
  setTimeout(() => { // 确保在同步代码后执行
    run();
  });
  function run() { // 依次执行任务
    if (queue.length === 0) return;
    const { msg, t } = queue.shift();
    // 不需要延迟执行的任务,我把它们转为同步执行了
    // 让它们都一致用异步执行也是可以的
    if (t === undefined) {
      console.log(msg);
      run(); // 执行
    } else {
      setTimeout(() => {
        console.log(msg);
        run();
      }, t * 1000);
    }
  }
  const solver = {
    sleep(second) {
      queue.push({
        msg: `Wake up after ${second}`,
        t: second,
      });
      return solver;
    },
    eat(something) {
      queue.push({
        msg: `Eat ${something}`,
        t: undefined,
      });
      return solver;
    },
    sleepFirst(second) {
      // 比较特殊,要放到队列开头
      queue.unshift({
        msg: `Wake up after ${second}`,
        t: second,
      });
      return solver;
    },
  };
  return solver;
}

对于 sleep 这些方法,我只是负责让它们加入队列,具体的执行我都是在 run 里统一处理的。

我去网上看了下其他人的写法,发现比较多的是 Express 的 next 这种风格,那我也写一个吧。

function LazyMan(name) {
  return new MyLazyMan(name);
}
class MyLazyMan {
  constructor(name) {
    this.queue = [];
    this.queue.push(() => {
      setTimeout(() => {
        console.log(`Hi! This is ${name}`);
      })
      this.next(); // 千万不要忘记执行 next
    })
    // 这里依旧是确保在同步代码后执行
    setTimeout(() => {
      this.next();
    })
  }
  next() {
    setTimeout(() => {
      if (this.queue.length === 0) return;
      const task = this.queue.shift();
      task();
    })
  }
  eat(something) {
    this.queue.push(() => {
      console.log(`Eat ${something}`);
      this.next();
    });
    return this;
  }
  sleep(second) {
    this.queue.push(() => {
      setTimeout(() => {
        console.log(`Wake up after ${second}`);
        this.next();
      }, second * 1000);
    });
    return this;
  }
  sleepFirst(second) {
    this.queue.unshift(() => {
      setTimeout(() => {
        console.log(`Wake up after ${second}`);
        this.next();
      }, second * 1000)
    });
    return this;
  }
}

这里的注意点是,在 setTimeout 里不要忘记加上 this.next,否则执行的链条会在中途断掉。

写法很多,除此之外还可以用 Promise,用上 async/await,甚至用上 rxjs,读者可自行去尝试。

结尾

这道编程题,考察的东西比较多,包括业务代码编写能力、队列、中间件思想(责任链模式)、异步代码。

不知道各位是否学会?

相关文章
|
2月前
|
数据采集 人工智能 自然语言处理
3分钟采集134篇AI文章!深度解析如何通过云无影AgentBay实现25倍并发 + LlamaIndex智能推荐
结合阿里云无影 AgentBay 云端并发采集与 LlamaIndex 智能分析,3分钟高效抓取134篇 AI Agent 文章,实现 AI 推荐、智能问答与知识沉淀,打造从数据获取到价值提炼的完整闭环。
787 93
|
机器学习/深度学习 存储 Linux
CentOS 7 部署 KVM 虚拟化
CentOS 7 部署 KVM 虚拟化
1108 0
|
11月前
|
前端开发 JavaScript 开发工具
【04】鸿蒙实战应用开发-华为鸿蒙纯血操作系统Harmony OS NEXT-正确安装鸿蒙SDK-结构目录介绍-路由介绍-帧动画(ohos.animator)书写介绍-能够正常使用依赖库等-ArkUI基础组件介绍-全过程实战项目分享-从零开发到上线-优雅草卓伊凡
【04】鸿蒙实战应用开发-华为鸿蒙纯血操作系统Harmony OS NEXT-正确安装鸿蒙SDK-结构目录介绍-路由介绍-帧动画(ohos.animator)书写介绍-能够正常使用依赖库等-ArkUI基础组件介绍-全过程实战项目分享-从零开发到上线-优雅草卓伊凡
720 5
【04】鸿蒙实战应用开发-华为鸿蒙纯血操作系统Harmony OS NEXT-正确安装鸿蒙SDK-结构目录介绍-路由介绍-帧动画(ohos.animator)书写介绍-能够正常使用依赖库等-ArkUI基础组件介绍-全过程实战项目分享-从零开发到上线-优雅草卓伊凡
|
人工智能 监控 前端开发
《C++ 与 JavaScript 携手:前端人工智能的创新融合之道》
在数字化时代,人工智能技术正迅速渗透至前端开发,C++与JavaScript的融合为前端智能化开辟新路径。C++的高效计算与JavaScript的灵活交互相结合,通过WebAssembly等技术,实现了复杂任务处理与用户界面的无缝对接,为智能图像识别、语音助手等应用提供了强大支持。面对开发复杂性和兼容性挑战,两者的深度融合仍需持续探索与优化。
241 26
|
存储 Unix Linux
手写操作系统(4)——计算机是如何启动的?BIOS、GRUB、文件系统......
手写操作系统(4)——计算机是如何启动的?BIOS、GRUB、文件系统......
423 1
|
JSON 开发工具 git
基于Python和pygame的植物大战僵尸游戏设计源码
本项目是基于Python和pygame开发的植物大战僵尸游戏,包含125个文件,如PNG图像、Python源码等,提供丰富的游戏开发学习素材。游戏设计源码可从提供的链接下载。关键词:Python游戏开发、pygame、植物大战僵尸、源码分享。
404 6
|
编解码
使用媒体查询动态调整文字大小
【10月更文挑战第24天】通过使用媒体查询动态调整文字大小,我们可以更好地适应不同的屏幕环境,为用户提供更舒适的阅读体验。
|
安全 生物认证 数据安全/隐私保护
多因素认证(MFA)
【8月更文挑战第20天】
2882 1
|
Python
Pandas进阶--map映射,分组聚合和透视pivot_table详解
Pandas进阶--map映射,分组聚合和透视pivot_table详解
527 0
|
数据可视化 大数据 开发者
R语言中值得学习的7个可视化,附代码段&案例数据集
随着数据量的不断增加,不使用可视化来描述事例是不可能的。 数据可视化是一种将数字转化为有用知识的艺术。
13262 0