面试被问「红绿灯问题」?这样回答让面试官眼前一亮!

95 阅读4分钟

异步编程与流程控制的终极考验,这样回答直接拿到Offer!

引子:一场突如其来的面试题

"假设我们要实现一个红绿灯功能,红灯亮1秒,黄灯亮3秒,绿灯亮2秒,循环往复。你会怎么实现?"

面试官推了推眼镜,嘴角带着一丝不易察觉的微笑。这看似简单的前端面试题,却暗藏着对异步编程流程控制乃至内存管理的全面考察。

我深吸一口气,开始了我的回答...

基础版:从setTimeout到Promise

最初的直觉:回调地狱

"最直观的做法可能是使用setTimeout嵌套:"

function trafficLight() {
  console.log('红灯');
  setTimeout(() => {
    console.log('黄灯');
    setTimeout(() => {
      console.log('绿灯');
      setTimeout(() => {
        trafficLight(); // 循环
      }, 2000);
    }, 3000);
  }, 1000);
}

"但这种做法会陷入可怕的'回调地狱',代码难以维护且容易出错。"

Promise化:异步编程的救星

"更优雅的方式是使用Promise:

const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));

async function trafficLight() {
  while(true) {
    console.log('红灯');
    await sleep(1000);
    console.log('黄灯');
    await sleep(3000);
    console.log('绿灯');
    await sleep(2000);
  }
}

"这里我们创建了一个sleep函数,它返回一个在指定时间后resolve的Promise,然后使用async/await以同步的方式编写异步代码。"

进阶版:可配置与可扩展

面试官点点头,但显然期待更多:"如果红绿灯的时间需要动态配置呢?"

配置化设计

"我们可以将配置抽离出来,使代码更加灵活:

const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));

async function trafficLight(config) {
  const { red = 1000, yellow = 3000, green = 2000 } = config || {};
  
  while(true) {
    console.log('红灯');
    await sleep(red);
    console.log('黄灯');
    await sleep(yellow);
    console.log('绿灯');
    await sleep(green);
  }
}

函数式链式调用

"或者采用更函数式的写法:

function light(color, ms) {
  console.log(color);
  return new Promise(resolve => setTimeout(resolve, ms));
}

function runTrafficLight() {
  light('红灯', 1000)
    .then(() => light('黄灯', 3000))
    .then(() => light('绿灯', 2000))
    .then(runTrafficLight); // 递归调用实现循环
}

"这种方法利用了Promise的链式调用特性,代码更加清晰。"

高级版:可控性与内存安全

面试官眼中闪过一丝赞许,但抛出了更棘手的问题:"如果用户突然离开页面,如何避免内存泄漏?如何中止红绿灯?"

AbortController:异步操作的中断机制

"这是一个非常重要的问题!在React等前端框架中,组件卸载后如果异步操作仍在继续,确实会导致内存泄漏。现代浏览器提供了AbortController来解决这个问题:

const sleep = (ms, signal) => new Promise((resolve, reject) => {
  const timer = setTimeout(resolve, ms);
  // 监听中止信号
  signal.addEventListener('abort', () => {
    clearTimeout(timer);
    reject(new DOMException('Aborted', 'AbortError'));
  }, { once: true });
});

async function trafficLight(signal) {
  const seq = [
    { color: 'red', ms: 1000 },
    { color: 'yellow', ms: 3000 },
    { color: 'green', ms: 2000 }
  ];
  
  while (!signal.aborted) {
    for (const { color, ms } of seq) {
      if (signal.aborted) return; // 检查是否已中止 
      console.log(color);
      try {
        await sleep(ms, signal);
      } catch (e) {
        if (e.name === 'AbortError') {
          console.log('红绿灯已停止');
          return;
        }
        throw e;
      }
    }
  }
}

// 使用示例
let controller = new AbortController();
trafficLight(controller.signal);

// 需要停止时
controller.abort();

总结与反思

面试官满意地点点头,我知道这道题我答得不错。

红绿灯问题考察的核心知识点

  1. 异步编程能力:对Promise、async/await的掌握程度
  2. 代码设计能力:如何组织代码使其可维护、可扩展
  3. 内存安全意识:对内存泄漏的认识和防范措施
  4. API熟悉度:对现代浏览器API(如AbortController)的了解
  5. 系统设计思维:如何将需求转化为合理的代码结构

实战建议

  1. 从简单开始:先实现基础功能,再逐步添加高级特性
  2. 考虑边界情况:中止、错误处理等都是展示你全面思维的机会
  3. 紧跟现代API:AbortController等新API能极大提升代码质量
  4. 理解问题本质:红绿灯问题本质是状态管理和异步流程控制

结语

红绿灯问题看似简单,实则内涵丰富。它不仅能考察候选人的编码能力,还能反映其代码设计思维和对现代WebAPI的掌握程度。下一次遇到这个问题,希望你能像交通指挥员一样,从容不迫地指挥异步流程的"车辆"有序通行!