LazyMan 面试题

Clloz · · 1,103次浏览 ·

前言

开门见山,今天朋友问了我一道面试题。

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

LazyMan('Hank'),输出:

Hi, This is Hank!

LazyMan('Hank').sleep(5).eat('dinner'),输出:

Hi, This is Hank!
// 等待5秒
Weak 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

思路

看到这个题目首先看到链式调用,那么自然想到的是构造函数,原型方法,每个方法调用后再返回调用对象,这样可以实现链式调用,LazyMan 可以直接调用那么我们可以检测一下是否是 new 调用,不是则返回 new 调用。基本结构如下

function LazyMan(name) {
    if (!(this instanceof LazyMan)) {
        return new LazyMan(name);
    }
}

LazyMan.prototype.sleep = function (time) {
  return this
};

LazyMan.prototype.eat = function (food) {
  return this;
};

LazyMan.prototype.sleepFirst = function (time) {
  return this;
};

实现链式调用之后我们继续看题目,可以看到需要有等待,自然想到 setTimeout,但是这里 setTimeout 无法满足我们的要求,因为 setTimeout 本身也是同步执行的,其回调函数是异步,并且 setTimeout 没法返回 this 对象。那么我们自然想到了 Promise,async/await。

使用 Promise 或 async/await 有一个和 setTimeout 一样的问题,就是他们的返回值都是 Promise,没法返回对象。同时我们注意题目中的最后一个调用,sleepFirst 方法最后调用但是却先执行,这给了一些提示。链式调用中肯定是前面的执行完了才执行后面,sleepFirst 看上去好像先执行,只有一种可能就是前面的函数执行没有执行 console,只是将 console 放到了某个地方等待执行。

所以我们需要在对象上增加一个 tasks 队列来存放任务,每个函数执行只是将要执行的任务放到 tasks 队列中,sleepFirst 的任务则放到队列最前面。

那么这个队列怎么执行呢,我们需要一个执行函数,这里我第一时间想到的是防抖函数,在 eat,sleep 和 sleepFirst 里面都执行这个执行函数,利用防抖的机制在链式调用结束才会真正执行这个执行函数。

根据上面的思路我写了下面的代码

function LazyMan(name) {
    if (!(this instanceof LazyMan)) {
        return new LazyMan(name);
    }
    this.name = name;
    console.log(`this is ${this.name}`);
    this.firstSleepTime = 0;
    this.tasks = [];
    // this.execute();
}

function sleep(time) {
    return new Promise(resolve => setTimeout(resolve, time));
}

function debounce(fn, interval) {
    let timeout = null;
    return function () {
        clearTimeout(timeout);
        timeout = setTimeout(() => {
            fn.apply(this, arguments);
        }, interval);
    };
}

LazyMan.prototype.execute = debounce(function () {
    sleep(this.firstSleepTime).then(async () => {
        for (let i = 0; i < this.tasks.length; i++) {
            if (typeof this.tasks[i] === 'string') {
                console.log(`Eat ${this.tasks[i]}`);
            } else {
                await sleep(this.tasks[i]);
            }
        }
    });
}, 0);

LazyMan.prototype.sleep = function (time) {
    this.tasks.push(time);
    this.execute();
    return this;
};

LazyMan.prototype.eat = function (food) {
    this.tasks.push(food);
    this.execute();
    return this;
};

LazyMan.prototype.sleepFirst = function (time) {
    this.firstSleepTime += time;
    this.execute();
    return this;
};

LazyMan('clloz').eat('orange').sleep(5000).eat('apple').sleepFirst(3000);

效果是达到了,不过有点投机取巧,我这里 tasks 存的不是任务,并且 sleepFirst 也是特殊处理。后来我去网上看了看别人的实现,将所有任务逻辑统一。修改后的实现如下

function LazyMan(name) {
    if (!(this instanceof LazyMan)) {
        return new LazyMan(name);
    }
    this.name = name;
    this.sayName();
    this.tasks = [];
}

function sleep(time) {
    return new Promise(resolve => setTimeout(resolve, time));
}

function debounce(fn, interval = 0) {
    let timeout = null;
    return function () {
        clearTimeout(timeout);
        timeout = setTimeout(() => {
            fn.apply(this, arguments);
        }, interval);
    };
}

LazyMan.prototype.sayName = function () {
    console.log(`this is ${this.name}`);
};

LazyMan.prototype.execute = debounce(async function () {
    for (const task of this.tasks) {
        await task();
    }
});

LazyMan.prototype.sleep = function (time) {
    this.tasks.push(async () => {
        console.log(`sleep ${time}ms`);
        await sleep(time);
    });
    this.execute();
    return this;
};

LazyMan.prototype.eat = function (food) {
    this.tasks.push(async () => {
        console.log(`Eat ${food}`);
    });
    this.execute();
    return this;
};

LazyMan.prototype.sleepFirst = function (time) {
    this.tasks.unshift(async () => {
        console.log(`first sleep ${time}ms`);
        await sleep(time);
    });
    this.execute();
    return this;
};

LazyMan('clloz').eat('orange').sleep(5000).eat('apple').sleepFirst(3000);

总结

这个题目考察了构造函数,原型,Promise,防抖等知识点,是一道不错的题目,希望本文对你有所帮助

参考文章

  1. 如何实现一个 LazyMan

Clloz

人生をやり直す

发表评论

电子邮件地址不会被公开。 必填项已用*标注

我不是机器人*

 

00:00/00:00