红绿灯无限循环的 3 种实现,哪种更受面试官青睐?

🚦 掘金前端场景题:红黄绿灯的无限循环之旅

嘿,各位前端的"程序猿"们!今天咱们来聊一个前端面试中经常遇到的经典场景题------如何让红黄绿灯无限循环地亮起来?别看这只是个小小的红绿灯,它背后可是藏着前端异步编程的"大秘密"呢!

💡 问题重现:交通灯的"灵魂拷问"

想象一下,你站在一个十字路口,面前的红绿灯正在有节奏地闪烁:

  • 红灯:亮3秒
  • 绿灯:亮1秒
  • 黄灯:亮2秒

现在问题来了:如何用代码实现这种红黄绿灯交替重复亮起的逻辑? 是不是听起来很简单?但要实现"无限循环",可就没那么容易了。这道题的精髓就在于,它不是让你亮一次就完事儿,而是要让它们像永动机一样,永远地亮下去!

为了模拟亮灯效果,我们先准备三个简单的函数:

javascript 复制代码
function red() {
  console.log("红灯亮了!");
}

function green() {
  console.log("绿灯亮了!");
}

function yellow() {
  console.log("黄灯亮了!");
}

🔄 Callback 实现:从"回调地狱"到"递归天堂"

第一次尝试:单次亮灯的"小把戏"

刚开始,你可能会想到用 setTimeout 来控制时间,然后用回调函数来衔接不同的灯光。我们先封装一个 task 函数,它负责在指定时间后亮起对应的灯,并执行下一个动作:

javascript 复制代码
const task = (timer, light, callback) => {
  setTimeout(() => {
    if (light === 'red') {
      red();
    } else if (light === 'green') {
      green();
    } else if (light === 'yellow') {
      yellow();
    }
    callback(); // 执行下一个回调
  }, timer);
};

// 这样写,灯只会亮一次,然后就"熄火"了
task(3000, 'red', () => {
  task(2000, 'green', () => {
    task(1000, 'yellow', Function.prototype); // Function.prototype 是一个空函数,表示没有后续操作
  });
});

运行这段代码,你会发现红黄绿灯确实亮了一遍,但也就仅此而已了。它并没有实现我们想要的"无限循环"。就像你玩了一次过山车,然后就下车了,不能一直坐下去。

递归大法:让灯光"永不熄灭"

那么,如何让这三个小灯泡"永不熄灭"呢?答案就是------递归 !我们需要在黄灯亮起之后,再次触发整个亮灯的流程。我们可以定义一个 step 函数,它包含了红黄绿灯亮起的一个完整周期,然后在黄灯亮起的回调中再次调用 step 函数,形成一个完美的闭环:

javascript 复制代码
const step = () => {
  task(3000, 'red', () => {
    task(2000, 'green', () => {
      task(1000, 'yellow', step); // 关键在这里!黄灯亮完后,再次调用step,实现循环
    });
  });
};

step(); // 启动我们的红绿灯循环!

是不是感觉有点意思了? 通过巧妙地在回调函数中调用自身,我们成功地让红黄绿灯进入了"无限循环"模式。不过,这种层层嵌套的回调函数,是不是让你想起了"回调地狱"(Callback Hell)?别急,接下来我们有更好的解决方案!

✨ Promise 实现:告别"回调地狱"的救星

"回调地狱"让人头大,代码可读性差,维护起来更是噩梦。这时候,Promise 就闪亮登场了!Promise 是一种更优雅的异步编程解决方案,它能让你更好地管理异步操作的流程。

首先,我们需要改造一下 task 函数,让它不再接受 callback 参数,而是返回一个 Promise 对象:

javascript 复制代码
const task = (timer, light) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (light === 'red') {
        red();
      } else if (light === 'green') {
        green();
      } else if (light === 'yellow') {
        yellow();
      }
      resolve(); // 亮灯结束后,调用 resolve() 表示任务完成
    }, timer);
  });
};

const step = () => {
  task(3000, 'red') // 红灯亮3秒
    .then(() => task(2000, 'green')) // 接着绿灯亮2秒
    .then(() => task(1000, 'yellow')) // 然后黄灯亮1秒
    .then(step); // 再次调用 step,实现循环
};

step(); // 启动 Promise 版本的红绿灯循环!

看,是不是清爽多了? 通过 Promise 的链式调用 .then(),我们把异步操作的流程扁平化了,避免了层层嵌套。每个 task 调用都会返回一个 Promise,当前一个 task 完成后(即 resolve() 被调用),.then() 中的下一个 task 就会被执行。最后,我们依然在链的末尾调用 step 函数,实现了红黄绿灯的无限循环。这就像是把一根长长的线团,整理成了一串清晰的珠链,一目了然!

🚀 Async/Await 实现:异步编程的"终极武器"

如果说 Promise 让你告别了"回调地狱",那么 async/await 简直就是前端异步编程的"终极武器"!它能让你用同步的思维去编写异步代码,让代码逻辑更加清晰,可读性更强,简直是"妈妈再也不用担心我写异步代码了"系列!

javascript 复制代码
const taskRunner = async () => {
  await task(3000, 'red'); // 等待红灯亮3秒
  await task(2000, 'green'); // 等待绿灯亮2秒
  await task(1000, 'yellow'); // 等待黄灯亮1秒
  taskRunner(); // 再次调用自身,实现循环
};

taskRunner(); // 启动 Async/Await 版本的红绿灯循环!

是不是感觉代码像在讲故事? await 关键字会暂停 taskRunner 函数的执行,直到它后面的 Promise 被解决(也就是对应的灯亮完)。这样,红灯亮完再亮绿灯,绿灯亮完再亮黄灯,一切都变得那么自然,就像你在读一本按顺序执行的说明书。最后,我们再次调用 taskRunner(),就实现了无限循环亮灯的效果。这种写法,简直是把异步代码写出了同步代码的"丝滑感"!

总结:前端异步编程的"三驾马车"

通过这个"红黄绿灯"的小例子,我们对比了前端异步编程的三种常见实现方式:

  1. Callback (回调函数):最原始的异步处理方式,简单直接,但容易陷入"回调地狱",代码可读性和维护性较差。
  2. Promise (承诺):对回调函数的一种改进,通过链式调用解决了"回调地狱"的问题,让异步流程更加清晰。
  3. Async/Await (异步/等待):基于 Promise 的语法糖,用同步的写法处理异步操作,极大地提高了代码的可读性和可维护性,是现代前端开发的首选。

每种方式都有其特点和适用场景,但毫无疑问,async/await 以其优雅的语法和强大的可读性,成为了现代前端开发的首选。希望这篇博客能让你对前端异步编程有更深入的理解,下次再遇到类似的面试题,你就能轻松应对啦!

祝你在前端的道路上,一路绿灯! 🚀

相关推荐
Andy_GF2 分钟前
纯血鸿蒙 HarmonyOS Next 调试证书过期解决流程
前端·ios·harmonyos
现实与幻想~9 分钟前
Linux:企业级WEB应用服务器TOMCAT
linux·前端·tomcat
mit6.82410 分钟前
[AI React Web]`意图识别`引擎 | `上下文选择算法` | `url内容抓取` | 截图捕获
前端·人工智能·react.js
赛博切图仔16 分钟前
React useMemo 深度指南:原理、误区、实战与 2025 最佳实践
前端·react.js·前端框架
YiuChauvin1 小时前
vue2中页面数据及滚动条缓存
前端·vue.js
摸着石头过河的石头1 小时前
微信h5页面开发遇到的坑
前端·微信
zabr1 小时前
AI时代,为什么我放弃Vue全家桶,选择了Next.js + Supabase
前端·aigc·ai编程
egghead263161 小时前
React常用hooks
前端·react.js
whysqwhw1 小时前
Http与Https
面试
科粒KL1 小时前
前端学习笔记-浏览器渲染管线/一帧生命周期/框架更新
前端·面试