大家好,我是潘潘。
今天分享一道腾讯从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('敲码');
至此,这道题你已经可以拿满分了。