腾讯校招必考题:实现一个hardMan函数!本文介绍两种满分解法!

654 阅读3分钟

大家好,我是潘潘。

今天分享一道腾讯从18年考到现在的校招算法题。去年面试腾讯的时候我也有幸遇到过,不出意外地没写出来:(,如果是在时间紧急的情况下想要顺利写出来还是有难度的,网上很多人吐槽过这道题,但是至今我还没看到过完整无误的解题答案,这次我介绍两种满分解法。

先看题目:

写一个hardMan函数,满足控制台打印效果如下:
hardMan('潘潘')
//> Hi! I am 潘潘.
hardMan('潘潘').study('Project')
//> Hi! I am 潘潘.
//> I am studying 敲码.
hardMan('潘潘').rest(3).study('敲码')
//> Hi! I am 潘潘.
// 此时等待三秒钟
//> Wait 3 seconds.
//> I am studying 敲码.
hardMan('潘潘').restFirst(3).study('敲码')
// 此时等待三秒钟
//> Wait 3 seconds.
//> Hi! I am 潘潘.
//> I am studying 敲码.

这道题理解起来很简单,无非就是链式调用函数满足打印结果而已,我们依次实现一下。

第一个很好写出来:

function hardMan(name) {
    console.log(`Hi! I am ${name}.`);
}

hardMan('潘潘')
//> Hi! I am 潘潘. 

接下来是实现链式调用study(),我们需要一个对象,这个对象身上包含链式调用的下一个方法,通常我们会想到返回this

function hardMan(name) {
    console.log(`Hi! I am ${name}.`);
    return this
}

this上添加study()并且同样返回this,此时已经能够实现第二个链式调用:

function hardMan(name) {
    console.log(`Hi! I am ${name}.`);
    this.study = function (project) {
        console.log(`I am studying ${project}.`);
        return this
    }
    return this
}

hardMan('潘潘').study('敲码')
//> Hi! I am 潘潘.
//> I am studying 敲码.

接下来继续实现第三个,调用rest(3)方法让链式调用的中间停留三秒钟。实现代码阻塞的方法有很多种,这里有很多种方法,比如用async/await或者利用while循环等,这里我采用while的方式,写一个阻塞程序执行的sleep函数:

function sleep(duration) {
    var start = Date.now()
    while (Date.now() - start < duration * 1000) { }
    console.log(`Wait ${duration} seconds.`);
}

console.log("Before sleep"); // Before sleep
sleep(3); // 等待三秒后打印:Wait 3 seconds.
console.log("After sleep"); // After sleep

把这个sleep方法放到hardMan函数中,并在rest方法中调用sleep函数即可实现链式调用的停顿执行:

function hardMan(name) {
    function sleep(duration) {
        var start = Date.now()
        while (Date.now() - start < duration * 1000) { }
        console.log(`Wait ${duration} seconds.`);
    }
    console.log(`Hi! I am ${name}.`);
    this.study = function (project) {
        console.log(`I am studying ${project}.`);
        return this
    }
    this.rest = function (duration) {
        sleep(3)
        return this
    }
    return this
}

hardMan('潘潘').rest(3).study('敲码')
//> Hi! I am 潘潘.
// 等待三秒钟
//> Wait 3 seconds.
//> I am studying 敲码.

接下来实现最后一个链式调用,这是本题的考察重点,我们要写一个restFirst(3)方法,实现链式调用的整体打印时间往后推迟3秒钟,而且restFirst方法的调用位置是不固定的。

提到函数的调用和执行顺序不一致,我们很容易想到执行栈、微任务和宏任务等概念。在JavaScript的异步代码中,可能会出现函数的执行顺序和调用顺序不一致的情况。当遇到异步操作时,比如setTimeout、Promise等,这些异步操作会被放入任务队列中等待执行。一旦当前执行栈为空,事件循环会从任务队列中取出任务执行。在这个过程中,微任务和宏任务的执行顺序会影响函数的实际执行顺序。

所以,我们可以给除restFirst方法以外的其余方法代码改成异步代码,比如套在setTimeout里:

function hardMan(name) {
    // sleep函数
    function sleep(duration) {
        var start = Date.now()
        while (Date.now() - start < duration * 1000) { }
        console.log(`Wait ${duration} seconds.`);
    }
    setTimeout(() => {
        console.log(`Hi! I am ${name}.`); // 异步代码
    }, 0);
    this.study = function (project) {
        setTimeout(() => {
            console.log(`I am studying ${project}.`); // 异步代码
        }, 0);
        return this
    }
    this.rest = function (duration) {
        setTimeout(() => {
            sleep(duration) // 异步代码
        }, 0);
        return this
    }
    this.restFirst = function (duration) {
        sleep(duration) // 异步代码
        return this
    }
    return this
}

hardMan('潘潘').restFirst(3).study('敲码')
// 等待三秒钟
//> Wait 3 seconds.
//> Hi! I am 潘潘.
//> I am studying 敲码.

第二种解法:

采用任务队列的方式,当调用方法时放到队列最前面执行,具体实现直接看代码

function hardMan(name) {
    this.name = name;
    this.queue = []; // 队列
    const fn = () => {
        setTimeout(() => {
            console.log(`Hi! I am ${this.name}.`);
            this.next();
        }, 0);
    };
    this.queue.push(fn); // push,放到任务队列最末端
    this.study = (name) => {
        const fn = () => {
            console.log(`I'm studying ${name}`);
            this.next();
        };
        this.queue.push(fn); // push,放到任务队列最末端
        return this;
    };
    this.rest = (second) => {
        const fn = () => {
            setTimeout(() => {
                console.log(`Wait ${second} seconds.`);
                this.next();
            }, second * 1000);
        };
        this.queue.push(fn); // push,放到任务队列最末端
        return this;
    };
    this.restFirst = (second) => {
        const fn = () => {
            setTimeout(() => {
                console.log(`Wait ${second} seconds.`);
                this.next();
            }, second * 1000);
        };
        this.queue.unshift(fn); // 这里使用的是unshift,把任务放在队列最开始
        return this;
    };
    this.next = () => {
        if (this.queue.length === 0) return;
        this.queue.shift()();
    };
    setTimeout(() => {
        this.next();
    }, 0);
    return this;
}
hardMan('潘潘').restFirst(3).study('敲码');

至此,这道题你已经可以拿满分了。