从 LazyMan 需求再次了解队列这种数据结构

110 阅读2分钟

趣味解读,实现一个 LazyMan,可以链式调用,它有sleep(time)方法,可以等待指定时间后,再继续执行后续的调用。

LazyMan 的分析

LazyMan('Jack').eat('lunch').sleepFirst(2).sleep(20).eat('dinner').sleep(40).eat('breakfast');

// (等待 2s) I'm sleep 2s
// Hi, I'm Jack
// I'm eating lunch
// (等待 20s) I'm sleep 20s
// I'm eating dinner
// (等待 40s) I'm sleep 40s
// I'm eating breakfast

看一下题目要求,可以很快知道,LazyMan 是一个对象,有 eat、sleep、sleepFirst 方法。其中链式调用,也就是在其方法最后,将对象 return 回来,于是就可以继续访问执行其方法了。

那么,这个 sleep 方法内部应该就是 setTimeout 的来实现。

可是,那个 sleepFirst 明显是要搞事情的,它不仅会延迟执行,还能跑到前面执行???

怎么办?

让我们回顾一下这个链式调用,离远点看,你看看它像不像一串糖葫芦,哦不,像不像是茶颜悦色前面苦苦排队的人。诶,排队,诶,队列?要悟了...

bingo,队列是什么?队列是一个先进先出(FIFO)的数据结构。

队列闪亮登场

OK,队列这种特性,刚好就符合我们对 LazyMan 的链式调用的编写。排在前面的方法,先执行;对于 sleepFirst 这种搞事情的方法,就好像是有(luan)特(cha)权(dui)的人,得特殊处理。

队列在代码中,可以用链表或者数组来实现,这里我们用数组来简单使用。

// 创建一个任务队列,用来存放链式调用的方法
this.taskQueue = [];
// next 方法呢,就是在任务队列中,取一个任务来执行
next() {
     let fn = this.taskQueue.shift();
     fn && fn();
}
// 声明 LazyMan 的方法,如 eat,在其函数体内,创建一个函数 fn,将要做的事情写到里面,随后将 fn 添加到任务队列中。
// 有两个一定要注意的点:1. 被添加到任务队列的函数中最后要执行 next(),也就是说,它执行完了,不能结束,还要去执行后面的任务。2. 返回 this,也就是 LazyMan 自身,不然怎么调用后面任务呢。
eat(food) {
    let fn = () => {
        console.log(`I'm eating ${food}`);
        this.next(); // 执行完代码后,将控制权给后续任务
    };
    this.taskQueue.push(fn); // 添加到 taskQueue 队列中
    // 返回 this,让函数可以继续链式调用。
    return this;
};

至于 sleep 方法,和上述的差不多,就是在 setTimeout 中再调用 next()。而 sleepFirst 这种特殊的,在将其往任务队列tashQueue中添加时,不再是 push 到最后,而是 unshift 到最前面.

完整代码

function LazyMan(name) {
    class _LazyMan {
        constructor(name) {
            this.name = name;
            this.taskQueue = [];
            this.hi();
            // 这里使用 setTimeout 来异步执行 next 函数,此时链式调用的各个方法已经在 taskQueue 队列中了。
            setTimeout(() => {
                console.log(this.taskQueue);
                this.next();
            }, 0);
        }
        hi() {
            const fn = () => {
                console.log(`Hi, I'm ${name}`);
                this.next();
            };
            this.taskQueue.push(fn);
            return this;
        }

        next() {
            let fn = this.taskQueue.shift();
            fn && fn();
        }

        eat(food) {
            let fn = () => {
                console.log(`I'm eating ${food}`);
                this.next();
            };
            this.taskQueue.push(fn); // 添加到 taskQueue 队列中
            // 返回 this,让函数可以继续链式调用。
            return this;
        }

        sleep(time) {
            return this.lazy(time);
        }
        sleepFirst(time) {
            return this.lazy(time, true);
        }

        lazy(time, isFirst = false) {
            const text = isFirst ? 'sleepFist' : 'sleep';
            const fn = () => {
                console.group(`I'm ${text} ...`);
                setTimeout(() => {
                    console.group(`I'm ${text} ${time}s`);
                    console.groupEnd();
                    this.next();
                }, time * 1000);
            };

            if (isFirst) {
                this.taskQueue.unshift(fn);
            } else {
                this.taskQueue.push(fn);
            }
            return this;
        }
    }

    return new _LazyMan(name);
}

LazyMan('Jack').eat('lunch').sleepFirst(2).sleep(4).eat('dinner').sleep(6).eat('breakfast');

结语

这是一道常见的面试题,有人可能选择背就完事了,不过我觉得吧,稍微想一想,其实也就是考察队列这种的数据结构,知道了这些后,哪怕题目再稍微变化一些,我们也是可以很快给出答案的吧。

死记硬背,终究不如深入理解的。

原文:bran-nie.com/2021/04/12/…