富途前端面试手撕代码:三道经典题目解析
最近面了富途的前端,主要业务是官网的开发和维护。面试给了三道代码题:打印执行结果、红绿灯交替、买卖股票的最佳时机。
一、宏任务与微任务的执行顺序
题目描述
给定以下代码,预测其输出结果:
javascript
console.log('script start');
setTimeout(() => {
console.log('setTimeout');
}, 0);
async function async1() {
console.log('async1 start');
await Promise.resolve().then(() => {
console.log('promise1');
});
console.log('async1 end');
}
async1();
console.log('script end');
思路分析
这道题考察的是 JavaScript 的事件循环机制,尤其是宏任务(macrotask)和微任务(microtask)的执行顺序。JavaScript 的执行机制可以分为以下几个阶段:
- 主线程执行:代码从上到下依次执行。
- 微任务队列 :
Promise
的回调函数、async/await
等属于微任务,会在当前宏任务执行完后立即处理。 - 宏任务队列 :
setTimeout
、setInterval
等属于宏任务,会在当前事件循环的微任务处理完后,进入下一个事件循环时处理。
在代码中:
console.log('script start')
和console.log('script end')
是主线程直接执行的代码,会立即输出。setTimeout
是宏任务,会被放入宏任务队列。async1()
是一个async
函数,其内部的await
会将后续代码拆分为微任务。
执行顺序
- 主线程先执行
console.log('script start')
,输出script start
。 - 遇到
setTimeout
,将其回调函数放入宏任务队列。 - 调用
async1()
,执行到console.log('async1 start')
,输出async1 start
。 - 遇到
await
,将Promise.resolve().then(() => { console.log('promise1'); })
的回调函数放入微任务队列。 await
后的console.log('async1 end')
会被拆分为微任务,放入微任务队列。- 主线程继续执行
console.log('script end')
,输出script end
。 - 主线程执行完毕后,处理微任务队列:
- 先执行
Promise.resolve().then()
的回调函数,输出promise1
。 - 然后执行
async1
中await
后的代码,输出async1 end
。
- 先执行
- 微任务处理完后,进入下一个事件循环,处理宏任务队列,输出
setTimeout
。
最终输出结果
sql
script start
async1 start
script end
promise1
async1 end
setTimeout
扩展思考
如果代码中有多个微任务和宏任务嵌套,如何预测输出顺序?例如:
javascript
console.log('script start');
setTimeout(() => {
console.log('setTimeout1');
Promise.resolve().then(() => {
console.log('promise2');
});
}, 0);
async function async1() {
console.log('async1 start');
await Promise.resolve().then(() => {
console.log('promise1');
});
console.log('async1 end');
}
async1();
console.log('script end');
输出结果为:
sql
script start
async1 start
script end
promise1
async1 end
setTimeout1
promise2
二、用 async
和 Promise
实现红绿灯交替展示
题目描述
实现一个红绿灯交替展示的功能,要求红灯亮 3 秒,绿灯亮 2 秒,黄灯亮 1 秒,循环往复。使用 async
和 Promise
实现。
思路分析
红绿灯的交替展示可以通过 async
函数和 Promise
来实现。具体步骤如下:
- 使用
Promise
和setTimeout
来控制灯的切换时间。 - 定义一个状态变量来记录当前灯的状态(红、绿、黄)。
- 使用
async
函数和await
来等待灯的切换。
代码实现
javascript
// 定义灯的状态
const RED = 'red';
const GREEN = 'green';
const YELLOW = 'yellow';
// 当前灯的状态
let currentState = RED;
// 灯的容器
const lightContainer = document.getElementById('light-container');
// 更新灯的状态
function updateLight(state) {
// 清除所有灯的亮状态
lightContainer.querySelectorAll('.light').forEach(light => {
light.classList.remove('active');
});
// 根据状态点亮对应的灯
const lightElement = lightContainer.querySelector(`.${state}`);
if (lightElement) {
lightElement.classList.add('active');
}
}
// 创建一个延迟的 Promise
function delay(time) {
return new Promise(resolve => {
setTimeout(resolve, time);
});
}
// 切换灯的状态
async function switchLight() {
while (true) {
updateLight(RED);
await delay(3000); // 红灯亮 3 秒
updateLight(GREEN);
await delay(2000); // 绿灯亮 2 秒
updateLight(YELLOW);
await delay(1000); // 黄灯亮 1 秒
}
}
// 初始化
switchLight();
HTML 和 CSS
html
<div id="light-container">
<div class="light red"></div>
<div class="light yellow"></div>
<div class="light green"></div>
</div>
<style>
.light-container {
display: flex;
flex-direction: column;
gap: 10px;
}
.light {
width: 50px;
height: 50px;
border-radius: 50%;
background-color: #333;
}
.light.active.red {
background-color: red;
}
.light.active.yellow {
background-color: yellow;
}
.light.active.green {
background-color: green;
}
</style>
扩展思考
如何优化红绿灯的实现?可以考虑以下几点:
- 使用状态机来管理灯的状态切换。
- 添加按钮控制红绿灯的暂停和继续。
- 动态调整灯的亮灭时间。
三、股票最佳买卖时间
题目描述
给定一个数组 prices
,其中 prices[i]
表示某股票第 i
天的价格。假设你最多可以完成两笔交易(即买入和卖出一次为一笔交易),设计一个算法来计算你能获得的最大利润。
思路分析
这道题可以通过动态规划来解决。我们需要记录以下四个状态:
buy1
:第一次买入后的最大利润。sell1
:第一次卖出后的最大利润。buy2
:第二次买入后的最大利润。sell2
:第二次卖出后的最大利润。
具体步骤如下:
- 初始化四个状态变量。
- 遍历每一天的价格,更新四个状态变量。
- 最后返回
sell2
,即两次交易后的最大利润。
代码实现
javascript
function maxProfit(prices) {
if (prices.length === 0) return 0;
// 初始化状态变量
let buy1 = -prices[0];
let sell1 = 0;
let buy2 = -prices[0];
let sell2 = 0;
for (let i = 1; i < prices.length; i++) {
// 更新第一次买入和卖出的状态
buy1 = Math.max(buy1, -prices[i]);
sell1 = Math.max(sell1, buy1 + prices[i]);
// 更新第二次买入和卖出的状态
buy2 = Math.max(buy2, sell1 - prices[i]);
sell2 = Math.max(sell2, buy2 + prices[i]);
}
return sell2;
}
// 示例
console.log(maxProfit([3,3,5,0,0,3,1,4])); // 输出 6
扩展思考
如果允许最多完成 k
笔交易,如何扩展算法?可以使用二维动态规划:
dp[i][j]
表示前i
天完成j
笔交易的最大利润。- 状态转移方程:
dp[i][j] = max(dp[i-1][j], dp[i-1][j-1] + prices[i] - prices[k])
,其中k
是买入的天。
总结
富途前端面试中的三道代码题考的还是很全面,涉及到浏览器事件循环机制、js异步流、leetcode算法题.