面试官:听说过LazyMan吗,手写一个试试

·  阅读 653
面试官:听说过LazyMan吗,手写一个试试

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第20天,点击查看活动详情

我们都知道,JavaScript是单线程、基于异步的编程语言,执行代码时是非阻塞的。如果执行的是同步的代码,则按照顺序执行完毕;如果是异步的代码,则按照异步调用执行,并不会阻塞在某一个异步函数中。最近遇到了这样一个面试:LazyMan(懒汉):

const lazyMan = new LazyMan('张三')

lazyMan.eat('dinner').sleep(10).eat('super').sleepFirst(5).sleep(5);

其实主要就是要实现可阻塞代码执行的sleep函数,当然还有链式调用。接下来就看看怎么具体实现吧

思路

不管是写什么代码,在此之前,都应该有一个大致的设计思路。回到面试题,主要由以下特点:

  1. 需要支持链式调用,那么eatsleep等方法内需要返回当前对象this
  2. 需要支持sleep功能,那么函数就不能在直接调用时就触发。js是没有内置的sleep方法的,需要我们自己实现
  3. 初始化一个列表,将需要的函数都添加进去,然后依次执行
  4. 由于是链式调用,因此我们需要实现一个next的功能,在列表中的每一个函数都执行后能自动调用下一个任务来执行

思路看起来也不是很复杂,接下来就动手试试。

开始

简单版

先实现一个简单的LazyMan,为了便于理解,我们在最后一个链式调用中增加了done()方法,用来打印当前的任务列表:

class LazyMan {
    tasks = []; // 任务列表
    name = ''; // 懒汉
    constructor(name) {
        this.name = name;
        // 采用异步,确保所有的链式调用函数都添加到列表中后,自动执行第一个任务
        setTimeout(() => {
            this.next();
        }, 0)
    }
    // 执行下一个任务,每次执行完都会从任务列表中删除
    next() {
        const task = this.tasks.shift(); // 从头部删除,会直接影响原数组
        task && task();
    }
    // 阻塞,使得程序延后time执行
    sleep(time) {
        // 定义一个任务函数
        const task = () => {
            console.log(`sleep ${time}s 开始`)
            setTimeout(() => {
                console.log(`sleep ${time}s 结束`)
                this.next(); // 执行完当前函数后,继续执行下一个任务,直到任务执行完毕
            }, time * 1000); // 使用定时器来模拟实现阻塞功能
        }
        // 添加到任务列表中
        this.tasks.push(task);
        // 返回当前对象,保持链式调用
        return this;
    }
    eat(food) {
        // 定义一个任务函数
        const task = () => {
            console.log(`eat ${food}`)
            this.next(); // 执行完当前函数后,继续执行下一个任务,直到任务执行完毕
        }
        // 添加到任务列表中
        this.tasks.push(task);
        // 返回当前对象,保持链式调用
        return this;
    }
    done() {
        const task = () => {
            console.log(`当前待处理的任务:`)
            console.log(`${this.tasks}`)
            this.next();
        }
        this.tasks.unshift(task);
        return this;
    }
}
const lazyMan = new LazyMan('张三');
lazyMan.eat('dinner').sleep(10).eat('super').sleep(5).done();
复制代码

实现效果:

image-20220618213825336.png

任务列表中其实就是一个个可执行函数

image-20220618213925712.png

实现效果基本上和LazyMan要求基本一致了。

看面试题中,除了sleep外,还有一个sleepFirst,这个其实就是说先执行sleep操作,即使这个函数调用并没有放在第一个。既然都已经实现了sleep了,要实现sleepFirst也并不复杂。

升级版

我们保持其他不变,仅来改动一下sleep函数:

class LazyMan {
    ...
    _sleep(time, isFirst) {
        const task = () => {
            console.log(`sleep ${time}s 开始`)
            setTimeout(() => {
                console.log(`sleep ${time}s 结束`)
                this.next(); // 执行完当前函数后,继续执行下一个任务,直到任务执行完毕
            }, time * 1000); // 使用定时器来模拟实现阻塞功能
        }
        if (isFirst) {
            this.tasks.unshift(task)
        } else {
            this.tasks.push(task)
        }
    }
    sleep(time) {
        this._sleep(time, false)
        return this
    }
    sleepFirst(time) {
        this._sleep(time, true)
        return this
    }
}
lazyMan.eat('dinner').sleep(10).eat('super').sleepFirst(5).sleep(5);
复制代码

运行结果:

image-20220618220021843.png

懒汉会先执行sleepFirst

总结

LazyMan就简简单单实现了,如果不了解这个可能在一开始会有点蒙,其实看下来并不复杂。总结一下核心知识点:

  • 成员方法中返回this,可实现链式调用
  • 将调用的函数注册到列表中
  • 按照先进先出的原则,实现next方法,保证链式调用可以链式执行
  • 使用setTimeout的延后执行,来实现sleep
分类:
前端
分类:
前端
收藏成功!
已添加到「」, 点击更改