news 2026/9/19 12:46:49

前端面试高频手撕代码清单:从防抖节流到Promise实现全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
前端面试高频手撕代码清单:从防抖节流到Promise实现全解析

面试这事儿,一旦聊到“手撕代码”,很多人的第一反应就是紧张。明明平时项目写得挺顺,一到白板阶段脑子就空白,Promise.all 怎么写来着?防抖是立即执行还是延迟执行?其实面试官问你手撕题,真不是想看你背了多少API,而是想通过几道高频题,快速判断你的编程基本功、边界思维和沟通习惯。这份“面试官最爱问的高频手撕清单”,我按照近几年一线大厂的面试反馈和身边朋友的面经整理了一遍,把最常出现的题目和底层逻辑都拆开揉碎讲清楚。

这份清单主要面向准备前端中高级岗位面试的同学,也适合刚转行、想系统补基础的新人。里面每道题我都给了破题思路、参考实现和面试官真正想看到的加分点,照着练,比你盲目刷100道题有用得多。

1. 为什么面试官都爱让候选人手撕代码

1.1 手撕题到底在考察什么

很多人以为手撕题考的是“API背得熟不熟”,这是最大的误解。面试官在短短四五十分钟里,能考察的维度其实很有限。项目经历可以包装,系统设计可以背八股,但手撕代码这个环节,最能真实暴露一个工程师的代码习惯、思维方式和解决问题的路径。

我做过几次模拟面试官,也跟不少面试官聊过。大家普遍一致的看法是,手撕题主要看四点:第一,基础功底扎不扎实,比如原型链、闭包、事件循环这些底层概念,是不是真理解而不是只会背概念;第二,边界思维够不够强,参数传空、类型不对、超大输入这些情况有没有考虑到;第三,代码风格好不好,变量命名是否清晰、是否过度设计、有没有写无用逻辑;第四,沟通协作能力,遇到题目不会做,是闷头硬想还是会主动跟面试官交流思路、提出假设。

1.2 高频手撕题的核心分类

“高频手撕清单”不是一道题一道题去背,而是要掌握类型。我观察下来,面试官最爱问的手撕题大致能分三类:第一类是工具函数实现,比如防抖节流、深拷贝、柯里化、数组去重、对象扁平化,这类题源于日常开发,考察基本功;第二类是框架底层原理简化版,比如手写 Promise、实现一个简易的 Vue 响应式或 Redux,考察对框架的理解深度;第三类是经典算法题,比如快排、二分查找、LRU 缓存等,考察数据结构和算法的基本功。

这三类题目都有一个共性,就是“题面短、解法多、边界多”。题面越短,越能考察你的思维广度;解法越多,越能考察你会不会选型;边界越多,越能考察你的工程经验。这也是为什么面试官青睐这类题——不是难题,但特别能看出水平。

1.3 从一道手撕题看面试官的思维

给你模拟一个场景。面试官说:“手写一个防抖函数。”看似简单,但里面的门道特别多:this 指向要不要处理?参数能不能透传?是立即执行还是延迟执行?返回值怎么办?取消功能要不要做?

我见过不少候选人,很快写出一个 basic 版本:

function debounce(fn, delay) { let timer = null; return function() { clearTimeout(timer); timer = setTimeout(() => fn(), delay); }; }

写完就结束了。代码是对的,但面试官内心毫无波澜,因为这跟背课文没区别。但如果你能在这个基础上继续聊“this 绑定需要保留”“参数要透传”“如果需要立即执行版本怎么办”“怎么支持 cancel 取消”,这题的分数就完全不一样了。面试官看的是你有没有把这些问题想清楚,而不是那句“let timer = null”。

2. 高频手撕清单全景图:按题目类型拆解

2.1 必须能默写的8道必考题

根据近几年面经和一线的反馈,下面这8道题的出现频率非常高,可以说是“面试官最爱问”的第一梯队。

第一梯队,工具函数类:防抖、节流、深拷贝、柯里化。为什么是这四个?因为它们覆盖了闭包、递归、引用类型、高阶函数这些 JS 最核心的概念。一道题能考察多个知识点,面试官自然爱用。

第二梯队,手写实现类:Promise(尤其是 Promise.all 和 Promise.race)、new 操作符模拟、call/apply/bind 实现。这四个涉及原型链、this 绑定、异步流程控制,是前端进阶的关键分水岭。

第三梯队,数据结构类:LRU 缓存、快排 / 二分查找变体。这两类题目能考察你是否具备基本的算法思维。

我给的排序建议是:先练工具函数类,再练手写实现类,最后专门攻数据结构类。原因是工具函数类最容易建立信心和手感,手写实现类能帮你打通 JS 的底层逻辑,有了前面两类积累,数据结构类才能真正理解而不是死记。

2.2 这些题为什么出现频率这么高

细心的人会发现,这些题都是平时写业务代码时频繁用的东西。防抖节流是搜索框、滚动监听的基本操作;深拷贝是处理接口返回数据的常规操作;Promise 更是异步编程的核心。面试官本质上是在问:你每天都在用这些 API,那你能不能自己造一个出来?

能造出来,说明你“to understand how it works under the hood”。不能造出来,说明你只是“会用”,至于内部的实现机制,你其实没有真正搞懂。大厂看重的,恰恰是前者。

2.3 从清单反推面试准备策略

之前有个学弟问我,说刷了 300 道 LeetCode,手撕题还是被挂。原因是方向错了。前端面试手撕题的算法难度,不要求你做到竞赛水平,除了少数明确考算法的岗位之外,通常维持在 LeetCode easy 到 medium 前半段的水平。

真正的重点是“实现类题目”,也就面试官非常关注“你能不能理解原理并实现出来”。所以正确策略应该是:用20%时间刷常规算法保持手感,用80%时间围绕上面三类高频题反复打磨,每一步都要能讲清楚为什么要这么做。我见过太多人刷题只刷不总结,刷了 100 道还是老样子。按照这份清单来,每道题吃透了,比盲目刷 100 道都管用。

3. 必考手写实现:每道都要能默写

3.1 防抖与节流:高频中的高频

这道题几乎每三场面试就会出现一次,因为它在实际开发里的使用频率实在太高。防抖和节流都用于限制函数的执行频率,但适用场景不同:

  • 防抖(debounce):在事件被触发 N 秒后再执行回调,如果在这 N 秒内又被触发,则重新计时。典型场景:搜索框输入、窗口 resize 后等用户停止操作再执行。
  • 节流(throttle):规定一个单位时间内,只能触发一次函数。典型场景:滚动加载、拖拽移动、按钮频繁点击。

手写防抖的完整版本,应该把 this、参数、立即执行、取消防抖都考虑进来:

function debounce(fn, wait = 500, immediate = false) { let timer = null; let isImmediateCalled = false; // 区别于 lodash 的 immediate 处理方式 return function(...args) { const context = this; if (timer) clearTimeout(timer); if (immediate) { if (!isImmediateCalled) { fn.apply(context, args); isImmediateCalled = true; } timer = setTimeout(() => { isImmediateCalled = false; }, wait); } else { timer = setTimeout(() => { fn.apply(context, args); }, wait); } }; }

这里用了一个isImmediateCalled标志位来处理“立即执行”的场景,同时确保在等待窗口结束后可以再次立即执行。这种写法比单纯用callNow布尔值更清晰,也比 lodash 里复杂的lastCallTime方案更好理解。

节流的实现,比较经典的时间戳版本和定时器版本各有优劣。时间戳版本能保证首次立即执行,但停止触发后不会再执行;定时器版本保证停止后最后再执行一次,但首次有延迟。实际使用中,我推荐把两者结合起来:

function throttle(fn, wait = 500) { let previous = 0; let timer = null; return function(...args) { const now = Date.now(); const remaining = wait - (now - previous); if (remaining <= 0) { // 距上次执行间隔已超过 wait,立即执行 if (timer) { clearTimeout(timer); timer = null; } previous = now; fn.apply(this, args); } else if (!timer) { // 保证停止触发后还能再执行一次 timer = setTimeout(() => { previous = Date.now(); timer = null; fn.apply(this, args); }, remaining); } }; }

面试的时候,写出来 time+flag 和 timer+flag 的版本之后,面试官往往会追问一句:如果希望第一次点击不触发,最后一次触发后还能执行一次,你怎么处理?这就是上面这个“双保险”版本解决的问题,把两个版本的优势都合进来。

3.2 深拷贝:递归是基础,边界才是分水岭

深拷贝几乎是前端面试必考题,它考察的是递归、引用类型、循环引用、特殊对象处理等知识点。一个大而全的深拷贝函数,可以覆盖非常多的考察点:

function deepClone(target, map = new WeakMap()) { // 基本类型直接返回 if (target === null || typeof target !== 'object') { return target; } // 处理循环引用 if (map.has(target)) { return map.get(target); } // 处理 Date、RegExp if (target instanceof Date) { return new Date(target.getTime()); } if (target instanceof RegExp) { return new RegExp(target.source, target.flags); } const result = Array.isArray(target) ? [] : {}; map.set(target, result); // 处理 Symbol 属性 const symbolKeys = Object.getOwnPropertySymbols(target); symbolKeys.forEach(symKey => { result[symKey] = deepClone(target[symKey], map); }); // 处理普通可枚举属性 Reflect.ownKeys(target).forEach(key => { result[key] = deepClone(target[key], map); }); return result; }

很多候选人能写出递归版本,但会在循环引用这里卡住——比如源对象里有一个属性指向自身,不处理就会无限递归导致栈溢出。用 WeakMap 记录已经克隆过的对象,遇到同一个引用直接返回,这个细节一写出来,面试官心里就会点头。

还有个容易忽略的点是 Symbol 属性的克隆。很多人用Object.keys()遍历,但Object.keys()拿不到 Symbol 属性。如果你用Reflect.ownKeys()一次性拿所有键,或者在遍历普通键之后再单独处理 Symbol,说明你对 ES6 的掌握是扎实的。另外,Object.create(null)创建的对象、Map、Set 这类特殊结构,如果能主动提到并说明怎么处理,都是加分项。

3.3 手写 Promise 与 Promise.all:面试官最爱追问的异步题

手写 Promise 是前端面试的一个分水岭。能把 Promise 基本结构写出来的人不少,能把 Promise.all 写对的人不到一半。

先看 Promise 核心结构:

class MyPromise { constructor(executor) { this.state = 'pending'; this.value = undefined; this.reason = undefined; this.onFulfilledCallbacks = []; this.onRejectedCallbacks = []; const resolve = (value) => { if (this.state === 'pending') { this.state = 'fulfilled'; this.value = value; this.onFulfilledCallbacks.forEach(cb => cb(value)); } }; const reject = (reason) => { if (this.state === 'pending') { this.state = 'rejected'; this.reason = reason; this.onRejectedCallbacks.forEach(cb => cb(reason)); } }; try { executor(resolve, reject); } catch (err) { reject(err); } } then(onFulfilled, onRejected) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value; onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }; const promise2 = new MyPromise((resolve, reject) => { if (this.state === 'fulfilled') { setTimeout(() => { try { const result = onFulfilled(this.value); this.resolvePromise(promise2, result, resolve, reject); } catch (e) { reject(e); } }); } // ... 处理 rejected 和 pending 状态 }); return promise2; } resolvePromise(promise2, result, resolve, reject) { if (promise2 === result) { reject(new TypeError('Chaining cycle detected')); return; } // 处理 result 是 Promise 或 thenable 的情况 if (result instanceof MyPromise) { result.then(resolve, reject); } else { resolve(result); } } static all(promises) { return new MyPromise((resolve, reject) => { if (!Array.isArray(promises)) { reject(new TypeError('arguments must be an array')); return; } const results = []; let count = 0; if (promises.length === 0) { resolve(results); return; } promises.forEach((p, index) => { MyPromise.resolve(p).then(value => { results[index] = value; count++; if (count === promises.length) { resolve(results); } }, reject); }); }); } }

面试时如果没有让你完整实现 Promise A+ 规范,手写all已经能应付 90% 的情况。这里的关键点是:用 count 计数并按下标存储结果,而不是往数组里 push。很多人习惯用results.push(value),这会导致返回结果的顺序和传入顺序不一致——先完成的放前面,但Promise.all必须保持顺序。这一点面试官特别爱考,细节决定成败。

还有一个加分点是promises.length === 0的情况直接resolve([])。能主动补上这个边界判断,说明你确实理解了这个 API 的语义,而不是背了个模板。

3.4 call / apply / bind:this 指向的底层逻辑

这道题考察的是对 this 机制的理解。核心思路都是:把函数挂到目标对象上,通过对象调用函数,让 this 自然指向目标对象。

Function.prototype.myCall = function(context = globalThis, ...args) { const fn = Symbol(); // 用 Symbol 避免覆盖对象上的已有属性 context[fn] = this; const result = context[fn](...args); delete context[fn]; return result; }; Function.prototype.myApply = function(context = globalThis, args = []) { const fn = Symbol(); context[fn] = this; const result = context[fn](...args); delete context[fn]; return result; }; Function.prototype.myBind = function(context = globalThis, ...bindArgs) { const originalFn = this; return function(...callArgs) { return originalFn.myApply(context, [...bindArgs, ...callArgs]); }; };

这里有两个值得关注的细节:一是用Symbol()作为临时属性名,避免可能与目标对象原属性冲突的问题;二是用globalThis代替windowglobal,兼容 Node 环境和浏览器环境,这个细节不少面试官会在心里默默加分。

bind 的实现还有一种更严谨的写法,需要考虑new调用的情况(new 出来的实例不能继承绑定上下文)。但前端中高级岗位面试考到这个深度的不多,能写出上面这版基本就够用了。

3.5 new 操作符模拟:经典但不能只会背

手写 new 也是高频题,它考察的是对构造函数、原型链、返回值处理的理解:

function myNew(Fn, ...args) { // 1. 创建一个新对象,原型指向构造函数的 prototype const obj = Object.create(Fn.prototype); // 2. 执行构造函数,this 绑定到新对象上 const result = Fn.apply(obj, args); // 3. 如果构造函数返回了对象,返回那个对象;否则返回新对象 if (result !== null && (typeof result === 'object' || typeof result === 'function')) { return result; } return obj; }

最常见的坑是忘了处理第三步——构造函数显式返回一个对象的情况。比如构造函数里写了return { a: 1 },那么 new 出来的一定是那个{ a: 1 },而不是实例本身。凡是能说出这个细节的人,面试官基本可以确定你理解了 new 的完整语义。

4. 数据结构与算法型手撕:不是背题,是理解

4.1 手写 LRU 缓存

LRU(Least Recently Used,最近最少使用)这道题,在一些高频手撕清单里常年霸榜。它考察的是双向链表加哈希表(Map)的数据结构组合能力,以及你对“时间复杂度”的敏感度。

JavaScript 里的 Map 天然维护了插入顺序,可以巧妙利用这一点实现 LRU:

class LRUCache { constructor(capacity) { this.capacity = capacity; this.map = new Map(); } get(key) { if (!this.map.has(key)) return -1; const value = this.map.get(key); // 删除重新插入,让该 key 排到最新位置 this.map.delete(key); this.map.set(key, value); return value; } put(key, value) { if (this.map.has(key)) { this.map.delete(key); } else if (this.map.size >= this.capacity) { // 删除最久未使用的 key(Map 的第一个 key) const oldestKey = this.map.keys().next().value; this.map.delete(oldestKey); } this.map.set(key, value); } }

这里的核心技巧是“删掉再插入”,利用 Map 的插入顺序模拟访问时间的刷新。this.map.keys().next().value取到第一个 key,也就是最久没有被访问的那个,直接删除。这个方案在思路和实现上都比手写双向链表更简洁,面试时先写出这个版本,再跟面试官聊“如果要求严格 O(1) 的 get / put 呢”,再引申到双向链表版本,既有梯度又有深度。

4.2 手写快速排序和二分查找

快排在算法手撕里的出现频率很高。面试官通常会加一个前提:不用 API,手写排序算法。对于中小规模数据,快排的效率确实很能打。

function quickSort(arr) { if (arr.length <= 1) return arr; const pivot = arr[0]; const left = []; const right = []; for (let i = 1; i < arr.length; i++) { arr[i] < pivot ? left.push(arr[i]) : right.push(arr[i]); } return [...quickSort(left), pivot, ...quickSort(right)]; }

但必须提醒一点:这是最直观的写法,空间复杂度偏高(每次递归都新建数组)。如果你能在写完这个版本之后主动说“这是最容易理解的版本,但空间复杂度是 O(n log n),面试中如果要求原地排序,可以改成荷兰国旗问题的双指针分区方式”,面试官会认为你不仅会写,还真的对比过不同实现。

二分查找的模板也比较固定,但边界条件一定要记牢:

function binarySearch(nums, target) { let left = 0; let right = nums.length - 1; while (left <= right) { const mid = Math.floor((left + right) / 2); if (nums[mid] === target) return mid; if (nums[mid] < target) { left = mid + 1; } else { right = mid - 1; } } return -1; }

边界是left <= right还是left < rightmid加一还是减一,这里错了就直接死循环。我建议你把这个模板背下来,然后用二分查找的变体题(比如查找第一个大于等于 target 的位置、旋转数组最小值)去巩固,就能形成条件反射。

4.3 数组去重与扁平化:看似简单,实则多解

数组去重是一道特别适合“引导式面试”的题。面试官会不断追问:去重还能怎么写?时间复杂度是多少?能不能不改变顺序?如果是对象呢?

基础版本,用 Set:

const unique = arr => [...new Set(arr)];

进阶版本,用 reduce 加 includes / indexOf,保持原数组顺序:

const unique = arr => arr.reduce((acc, cur) => acc.includes(cur) ? acc : [...acc, cur], []);

数组扁平化也一样,从flat(Infinity)到递归实现,再到用栈预处理:

function flatten(arr) { return arr.reduce((acc, cur) => Array.isArray(cur) ? [...acc, ...flatten(cur)] : [...acc, cur], [] ); }

这类题目考的不是能不能写出来,而是你能写出几种解法,能不能对比它们的复杂度。每多一层思考,在面试官那里的评分就会高一档。

5. 面试现场:从思考到敲完的完整答题套路

5.1 先想清楚再动手,先说思路再敲代码

很多候选人看题之后直接上手写,写了一半发现方向不对,又删了重来。面试官并不会因为你“答得快”就给高分,相反,他们更欣赏那种先花一两分钟把问题和边界想清楚的人。

我推荐的现场答题流程是:第一步,口述你要怎么实现,比如“我打算用 Map 来存,get 的时候删掉再重插,put 的时候先查有没有,有就删,没有就判断容量”;第二步,确认关键边界条件,比如“传入的是空数组怎么办”“负数怎么处理”;第三步,动手写代码,写的时候边写边解释;第四步,写完主动梳理一遍,指出哪些地方可能有边界问题。

这套流程打下来,面试官会明显感觉到你是有方法论的人,而不是背题的考生。

5.2 三道最常见的追问方式

  • “还有没有更优的解法?”——考察你掌握多少种方案,以及你是否会主动做复杂度分析。
  • “如果输入变了,这段代码还成立吗?”——考察你的边界思维和防御性编程意识。
  • “能不能把这段代码里没考虑到的内存问题指出来?”——考察你对工程化质量的敏感度。

面对这些追问,别慌。追问答得不好并不全是坏事,很多面试官故意用追问来看你面对压力时的反应。你可以诚实说“这里我没有想到特别好的优化方案”,比强行编一个错的方案要好得多。

5.3 不同基础的候选人怎么用这份清单

如果你是刚准备面试的新人:先用两天把工具函数类全部默写一遍,再花三天把 Promise 和 call/apply/bind 搞清楚,最后用周末把 LRU 和快排练熟。先求完整,再求深入。如果你的时间有限:优先保防抖节流、深拷贝、Promise 这三道,出现概率真的太高了。如果你有基础想冲高分:把重点放在“每个题的最优解”和“边界问题”上,再配合 LeetCode 的 medium 难度题练手感。

6. 我的刷题顺序与避坑心得

6.1 刷题顺序千万别乱来

我最开始准备面试时,犯过一个严重错误:上来就啃 Promise A+ 规范,结果看了几天连 resolvePromise 的递归逻辑都没吃透,自信心差点被打崩。后来调整策略,按照“工具函数类 → 手写实现类 → 数据结构类”的顺序来刷,效果立竿见影。

为什么这个顺序有效?因为工具函数类是最好上手的,防抖节流深拷贝,写个两三遍就熟了,能积累“我能行”的正反馈。有了这个底子,再去看 Promise 这种复杂逻辑,就能理解为什么要用回调数组、为什么 then 要返回新的 Promise。等前面两类都顺了,再去碰 LRU、快排这些算法题,思路会开阔很多,因为你已经建立了“先想边界、再想解法”的习惯。

6.2 记住我踩过的三个坑

第一个坑是只写不练。看答案觉得自己会了,真要自己从头写就卡住。后来我定了规矩:看完一道题,合上手机,空手写出来,写不出来就是不会,重新学。这个方法很笨,但效果是真的好。

第二个坑是不总结共性。我很长一段时间是“今日学防抖,明日学深拷贝,后日学 Promise”,学完就忘。后来我给自己做了张表,把每道题考察的 JS 核心知识点标出来,发现它们高度重叠:闭包、this 绑定、递归、事件循环。从那以后我理解了,手撕题刷的不是题,而是对 JS 核心机制的理解程度。

第三个坑是忽视了“说”的练习。手撕题不但要会写,还要会讲。面试时一边写一边说思路,面试官才能跟上你的节奏。我建议你练题的时候念出声来,模拟面试场景,既能帮自己理清思路,又能缓解真实面试时的紧张感。

6.3 后续怎么扩展

这份高频清单覆盖的是基础盘,如果你还想往上冲,可以继续往下扩展:手写一个简版 redux-thunk、实现 Promise.retry(重试机制)、手写一个简易的模板引擎、实现一个支持并发限制的调度器。这些都是“进阶手撕题”,面试官问出来往往能区分候选人水平。

但我还是想强调,基础盘没打牢,别急着追新题。把这份清单吃透了,再往外扩,你的面试稳定性会远超大多数人。

最后再分享一个我复盘面试时的小习惯。每次面试结束,我会第一时间把被问到的题目和当时自己的思考过程记录下来,特别是那些答得不好的题,过两天再重新独立写一遍。面试不只是找工作的手段,更是暴露自己漏洞的最快方式。上面这份“高频手撕清单”你可以直接照着练,但真正的功夫在练的过程里——理解每个实现背后的为什么,再反复答题直到形成肌肉记忆。前几次面试翻车太正常了,多复盘,多补漏,你的手感会一次比一次好。

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

FPGA入门:可重构芯片的原理、开发流程与学习路线

你大概率在B站、知乎或者CSDN上刷到过“FPGA”这个词&#xff0c;旁边还总跟着"年薪高""底层""数字IC""硬件编程"这些标签&#xff0c;点进去看了十分钟&#xff0c;关掉页面还是没太想明白&#xff1a;它到底是个芯片&#xff0c;还是…

作者头像 李华
网站建设 2026/9/19 12:44:55

SPSS单因素方差分析全解析:原理、操作、多重比较与Bootstrap

简介&#xff1a;这份PPT系统讲解第5章SPSS的方差分析&#xff0c;面向需要掌握统计检验方法的学生、科研人员或数据分析初学者。内容从方差分析概念入手&#xff0c;厘清因素、水平、单元、元素与交互作用等关键术语&#xff0c;再说明其基本思想、系统性差异与随机性差异的区…

作者头像 李华
网站建设 2026/9/19 12:44:53

软件系统应急预案与快速恢复方案:从RTO/RPO到故障切换实战

简介&#xff1a;面向互联网行业的软件系统应急预案及快速恢复方案&#xff0c;适合运维工程师、技术支持及系统管理员用于故障响应与风险管控。文档以网络基础设施、服务器集群、IVR、CTI、软话机控件等常见故障场景为主线&#xff0c;逐一说明网络中断、网关异常、IVR/CTI服务…

作者头像 李华
网站建设 2026/9/19 12:44:13

Multi-Head Attention工程实践:从原理到稳定训练的完整解剖

1. 这不是“讲清楚”的问题&#xff0c;而是“用明白”的门槛多头注意力&#xff08;Multi-Head Attention&#xff09;——这个词在深度学习圈里&#xff0c;已经快被说烂了。你打开任意一篇Transformer相关教程&#xff0c;十有八九第一段就写着“它由多个并行的自注意力头组…

作者头像 李华
网站建设 2026/9/19 12:43:15

JS逆向实战:前端参数加密与算法还原全攻略

做前端数据采集和技术研究的朋友&#xff0c;对JS逆向这个词肯定不陌生。尤其是这两年&#xff0c;你会发现越来越多的网站把核心参数做了加密处理&#xff0c;登录态、搜索参数、翻页参数&#xff0c;几乎每一个关键请求都带着一串看不透的密文。我最初接触JS逆向的时候&#…

作者头像 李华