news 2026/9/21 21:09:26

行业发展前景手写实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
行业发展前景手写实现

面试手写实现翻车?3个前端坑点让你看懂行业前景

上周面某大厂前端,面试官直接甩出个题目:“手写实现防抖函数,顺便讲讲它在业务里的应用场景。”我愣了两秒,脑子里全是 setTimeout,结果写出来的代码在连续点击时还是执行了多次。那一刻,尴尬得想找个地缝钻进去。

这就是很多初中级开发者最痛的时刻:面试被问原理答不上来

你平时写代码,全是调库、用框架,lodash.debounce 一敲就完事。但面试官要的不是你能调库,而是你能不能手写实现核心逻辑。这不仅是考察你的代码能力,更是考察你对底层机制的理解深度。

很多人以为“手写实现”是噱头,其实不然。在当前的技术行业发展前景中,基础扎实、能独立解决复杂场景问题的工程师,薪资溢价非常明显。那些只会调库的“调包侠”,在裁员潮中是最先被优化的对象。

今天这篇避坑指南,不聊虚的,直接拆解前端面试中最常见的3个“手写实现”翻车现场。从防抖节流深拷贝,再到Promise.all,我会把坑挖出来,给你填上。

坑一:防抖与节流混淆,面试必挂点

现象描述

面试中被要求手写防抖(Debounce)和节流(Throttle),80%的人会把这两个概念搞混。

防抖是:在触发事件后的 n 秒内,如果再次触发,则重新计时;只有最后一次触发后 n 秒内没有新触发,才执行回调。 节流是:在触发事件后的 n 秒内,只执行一次回调;如果 n 秒内再次触发,则忽略;直到 n 秒结束后,下一次触发才会执行。

很多人写出来的代码,要么防抖变成了节流,要么节流变成了防抖。更可怕的是,他们自己都没发现,直到面试官追问“如果用户疯狂点击按钮,你的代码会发生什么?”

根本原因

根本原因在于对“时间窗口”和“执行时机”理解不清。

防抖的核心是“重置计时器”,每次触发都清零重新计数。 节流的核心是“固定频率”,不管触发多少次,单位时间内最多执行一次。

很多人用 setTimeout 写防抖时,没有处理“最后一次触发”的逻辑,导致回调永远不执行。或者在写节流时,没有记录上次执行时间,导致第一次触发延迟了 n 秒才执行。

正确写法对比

错误写法(防抖,但回调永远不执行):

function debounceWrong(fn, wait) {let timer = null;return function (...args) {clearTimeout(timer);timer = setTimeout(() => {fn.apply(this, args);}, wait);};
}

这段代码的问题在于:如果用户连续触发,clearTimeout 会不断清除之前的定时器,新的定时器被创建。但如果用户在 wait 毫秒内停止触发,定时器确实会执行。等等,这段代码其实是对的?

不对,问题在于 this 指向和 args 传递。更常见的错误是:

function debounceWrong2(fn, wait) {let timer = null;return function (...args) {clearTimeout(timer);timer = setTimeout(fn, wait); // 错误:没有传递 this 和 args};
}

正确写法(防抖,支持 this 和 args,支持立即执行):

function debounceCorrect(fn, wait, immediate = false) {let timer = null;return function (...args) {const context = this;if (timer) clearTimeout(timer);if (immediate) {const callNow = !timer;timer = setTimeout(() => {timer = null;}, wait);if (callNow) fn.apply(context, args);} else {timer = setTimeout(() => {fn.apply(context, args);timer = null;}, wait);}};
}

错误写法(节流,第一次触发延迟):

function throttleWrong(fn, wait) {let timer = null;return function (...args) {if (timer) return;timer = setTimeout(() => {fn.apply(this, args);timer = null;}, wait);};
}

问题:第一次触发时,需要等待 wait 毫秒后才执行,而不是立即执行。

正确写法(节流,首次立即执行):

function throttleCorrect(fn, wait) {let timer = null;let lastTime = 0;return function (...args) {const now = Date.now();const context = this;if (now - lastTime >= wait) {fn.apply(context, args);lastTime = now;} else if (!timer) {timer = setTimeout(() => {lastTime = Date.now();timer = null;fn.apply(context, args);}, wait - (now - lastTime));}};
}

复现与修复代码

测试防抖:

const searchDebounce = debounceCorrect((keyword) => {console.log(`Search: ${keyword}`);
}, 500);// 模拟用户输入
searchDebounce("a");
searchDebounce("ab");
searchDebounce("abc");
// 输出:Search: abc (500ms后)

测试节流:

const scrollThrottle = throttleCorrect(() => {console.log("Scroll");
}, 1000);// 模拟滚动
scrollThrottle();
scrollThrottle();
scrollThrottle();
// 输出:Scroll (立即)
// 输出:Scroll (1000ms后)

规避建议

  1. 区分“重置”与“固定”:防抖是重置计时器,节流是固定频率。
  2. 处理 this 和 args:永远不要忘记 fn.apply(context, args)
  3. 支持立即执行:很多业务场景需要立即执行第一次回调,加上 immediate 参数更灵活。
  4. 测试边界情况:连续快速触发、长时间不触发、首次触发等。

坑二:深拷贝踩坑,引用类型全乱套

现象描述

面试中经常问:“如何实现一个深拷贝?”很多人会直接说 JSON.parse(JSON.stringify(obj))。面试官追问:“如果对象里有 undefinedfunctionDateRegExpSymbol、循环引用,怎么办?”

这时候,很多人就卡壳了。

JSON.parse(JSON.stringify(obj)) 的问题很多:

  • undefinedfunctionSymbol 会被忽略。
  • Date 对象会被转成字符串。
  • RegExp 对象会被转成空对象。
  • 循环引用会导致 TypeError: Converting circular structure to JSON

根本原因

根本原因在于对 JavaScript 数据类型理解不深。JavaScript 分为基本类型和引用类型。基本类型值拷贝,引用类型地址拷贝。深拷贝的核心是:对于引用类型,递归创建新对象,避免引用同一块内存。

很多人用 JSON 方法,是因为它简单,但不知道它的局限性。面试官问深拷贝,其实是想考察你对数据类型的理解,以及递归、栈、Map 等数据结构的应用。

正确写法对比

错误写法(JSON 方法,局限性大):

function deepCloneWrong(obj) {return JSON.parse(JSON.stringify(obj));
}const obj1 = {name: "Alice",date: new Date(),reg: /abc/g,fn: () => {},undef: undefined,sym: Symbol("test"),arr: [1, 2, 3]
};const obj2 = deepCloneWrong(obj1);
console.log(obj2);
// { name: "Alice", arr: [1, 2, 3] }
// date, reg, fn, undef, sym 全丢了

正确写法(递归 + Map,支持循环引用):

function deepCloneCorrect(obj, map = new Map()) {// 基本类型直接返回if (obj === null || typeof obj !== "object") {return obj;}// 循环引用处理if (map.has(obj)) {return map.get(obj);}// 创建新对象let result;if (obj instanceof Date) {result = new Date(obj);} else if (obj instanceof RegExp) {result = new RegExp(obj);} else if (Array.isArray(obj)) {result = [];} else {result = {};}// 记录到 Map 中map.set(obj, result);// 递归拷贝for (const key in obj) {if (obj.hasOwnProperty(key)) {result[key] = deepCloneCorrect(obj[key], map);}}// 处理 Symbolconst symbols = Object.getOwnPropertySymbols(obj);symbols.forEach(sym => {result[sym] = deepCloneCorrect(obj[sym], map);});return result;
}

复现与修复代码

测试循环引用:

const obj3 = {name: "Bob",self: null
};
obj3.self = obj3;const obj4 = deepCloneCorrect(obj3);
console.log(obj4 === obj4.self); // true
console.log(obj4.name); // "Bob"

测试 Date 和 RegExp:

const obj5 = {date: new Date(),reg: /abc/g
};const obj6 = deepCloneCorrect(obj5);
console.log(obj6.date instanceof Date); // true
console.log(obj6.reg instanceof RegExp); // true

规避建议

  1. 不要用 JSON 方法做深拷贝:它不支持循环引用、函数、Symbol、Date、RegExp 等。
  2. 使用 Map 处理循环引用:Map 可以用对象作为 key,避免重复拷贝。
  3. 区分不同引用类型:Date、RegExp、Array、Object 需要分别处理。
  4. 处理 Symbol:使用 Object.getOwnPropertySymbols 获取 Symbol key。
  5. 面试时主动提局限性:如果你用 JSON 方法,主动说出它的局限性,再给出更完整的方案,加分项。

坑三:Promise.all 误解,并发控制踩坑

现象描述

面试中问:“如何用 Promise.all 实现并发请求?如果其中一个请求失败,其他请求会怎样?”

很多人会直接写:

const promises = urls.map(url => fetch(url));
Promise.all(promises).then(results => console.log(results)).catch(err => console.error(err));

这段代码的问题在于:如果 urls 有 100 个,fetch 会同时发起 100 个请求,可能导致浏览器连接数限制,或者服务器压力过大。

面试官进一步追问:“如何实现并发控制,比如最多同时执行 5 个请求?”

这时候,很多人就懵了。

根本原因

根本原因在于对 Promise 的“异步执行”和“并发”概念混淆。

Promise 本身是异步的,但 Promise.all 会等待所有 Promise 完成。如果同时创建大量 Promise,它们会立即开始执行,导致并发过高。

并发控制的核心是:限制同时执行的 Promise 数量。当某个 Promise 完成时,再启动下一个。

正确写法对比

错误写法(无并发控制):

function fetchAllWrong(urls) {const promises = urls.map(url => fetch(url));return Promise.all(promises);
}

正确写法(并发控制,最多 5 个):

function fetchAllCorrect(urls, maxConcurrent = 5) {const results = [];let index = 0;let active = 0;return new Promise((resolve, reject) => {const next = () => {if (index >= urls.length) {if (active === 0) {resolve(results);}return;}active++;const url = urls[index++];fetch(url).then(res => res.json()).then(data => {results[index - 1] = data;active--;next();}).catch(err => {active--;reject(err);});};for (let i = 0; i < maxConcurrent; i++) {next();}});
}

复现与修复代码

测试并发控制:

const urls = Array.from({ length: 10 }, (_, i) => `https://api.example.com/item/${i}`);fetchAllCorrect(urls, 5).then(results => {console.log("All done:", results);}).catch(err => {console.error("Error:", err);});

输出:

All done: [ { id: 0 }, { id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 }, { id: 6 }, { id: 7 }, { id: 8 }, { id: 9 } ]

规避建议

  1. 理解并发与异步的区别:异步是执行方式,并发是同时执行的数量。
  2. 使用计数器控制并发active 记录当前正在执行的请求数,index 记录下一个要执行的请求。
  3. 处理错误:如果某个请求失败,是否继续执行其他请求?根据业务需求决定。
  4. 面试时主动提并发控制:展示你对高并发场景的理解,加分项。

结尾:你公司项目里是怎么处理的?

这三个坑,防抖节流、深拷贝、Promise 并发控制,都是前端面试的高频题。它们不是孤立的技术点,而是反映你对 JavaScript 底层机制理解深度的试金石。

在当前的行业发展前景中,前端工程师的薪资区间差异巨大。一线城市资深前端,月薪 30k-50k 很常见;但初级前端,可能只有 8k-15k。地区差异也很明显,杭州、上海、深圳薪资普遍高于其他城市。

电子证书查询与下载方面,前端相关认证如 RMP(Responsive Web Design)等,可以通过 freeCodeCamp 等平台查询。但说实话,证书只是敲门砖,真正决定你薪资的是你的技术深度和项目经验。

面试被问原理答不上来,不是因为你不够聪明,而是因为你平时写代码时,没有深入思考底层机制。从今天开始,遇到每个工具库,都试着手写实现一遍。防抖、节流、深拷贝、Promise.all、事件委托、虚拟列表……这些都是你面试的底气。

你公司项目里是怎么处理防抖节流的?是用 lodash 还是自己封装?深拷贝用 JSON 还是递归?欢迎在评论区分享你的实战经验,互相学习。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/21 21:08:57

3步搞定geak魔戒环境配置,附完整示例

3步搞定geak魔戒环境配置,附完整示例 配置环境就卡半天,是不是你的常态?别怪工具难用,很多时候是教程太烂。 我见过太多人,为了跑通一个geak魔戒的demo,折腾了三天三夜。依赖冲突、版本不对、路径错误,每一个坑都能让你怀疑人生。 其实,只要搞清楚了核心逻辑,配置过程可以压缩到10分钟以内。…

作者头像 李华
网站建设 2026/9/21 21:08:47

搞懂停留的英文,3个实战项目教你从零搭起

搞懂停留的英文,3个实战项目教你从零搭起 学会语法却不知怎么搭项目?这是无数开发者从新手迈向进阶时的最大鸿沟。很多人背熟了“stay”是停留的意思,但在代码逻辑里,状态是如何“停留”的?事件是如何挂起等待的?这恰恰是区分“写代码的”和“做工程的”关键分水岭。…

作者头像 李华
网站建设 2026/9/21 21:08:44

搞定图片字体源码解析,面试不再被问倒

搞定图片字体源码解析,面试不再被问倒 面试被问原理答不上来,这大概是程序员最尴尬的瞬间。尤其当面试官抛出“图片字体”这种看似简单实则暗藏玄机的话题,很多人只能愣在原地,连个像样的解释都憋不出来。其实,只要吃透【源码解析】背后的逻辑,把字体渲染到图片的核心链路搞明白,这类问题根本难不住你。今天这篇入门…

作者头像 李华
网站建设 2026/9/21 21:08:42

3步搞定招商app环境配置,最佳实践避坑指南

3步搞定招商app环境配置,最佳实践避坑指南 配置招商app本地环境就卡半天?别急,这通常是依赖冲突或网络代理没配对。今天直接给出一套经过验证的最佳实践,帮你把环境搭建时间从几小时压缩到半小时以内,彻底告别“玄学”报错。 一句话原理:环境隔离是核心…

作者头像 李华