面试常考的高频手写js题汇总
2025.09.19 12:55浏览量:0简介:本文汇总了前端面试中高频出现的手写JavaScript题目,涵盖数组操作、异步编程、数据结构模拟等核心场景,通过代码示例与实现思路解析,帮助开发者系统掌握面试必考知识点。
面试常考的高频手写JS题汇总
在前端技术面试中,手写JavaScript代码是考察开发者基础能力的核心环节。这类题目不仅能检验候选人对语言特性的理解深度,还能体现其逻辑思维与问题解决能力。本文将从数组操作、异步编程、数据结构模拟等维度,系统梳理面试中高频出现的20道手写题,并提供标准实现与优化思路。
一、数组操作类高频题
1. 数组去重(核心考点:Set特性与对象属性判断)
// 方法1:利用Set特性(ES6推荐)
function unique(arr) {
return [...new Set(arr)];
}
// 方法2:对象属性去重(兼容ES5)
function uniqueES5(arr) {
const seen = {};
return arr.filter(item => {
return seen.hasOwnProperty(item) ? false : (seen[item] = true);
});
}
考察点:Set数据结构的哈希存储特性,以及对象属性访问的时间复杂度优化。需注意对象键名会被强制转为字符串的特性。
2. 扁平化嵌套数组(递归与reduce应用)
// 递归实现(支持无限层级)
function flattenDeep(arr) {
return arr.reduce((acc, val) =>
Array.isArray(val) ? acc.concat(flattenDeep(val)) : acc.concat(val),
[]);
}
// 指定深度版本
function flatten(arr, depth = 1) {
return depth > 0
? arr.reduce((acc, val) =>
Array.isArray(val)
? acc.concat(flatten(val, depth - 1))
: acc.concat(val),
[])
: arr.slice();
}
关键点:递归终止条件设计、reduce方法的正确使用,以及深度参数的控制逻辑。
3. 实现数组map方法(闭包与this绑定)
Array.prototype.myMap = function(callback, thisArg) {
const result = [];
for (let i = 0; i < this.length; i++) {
if (i in this) {
result.push(callback.call(thisArg, this[i], i, this));
}
}
return result;
};
实现要点:需处理稀疏数组的跳过逻辑,正确传递参数列表(当前值、索引、原数组),以及thisArg的绑定。
二、异步编程核心题
4. Promise.all实现(状态管理与错误处理)
function promiseAll(promises) {
return new Promise((resolve, reject) => {
const results = [];
let completed = 0;
if (promises.length === 0) return resolve([]);
promises.forEach((promise, index) => {
Promise.resolve(promise)
.then(value => {
results[index] = value;
completed++;
if (completed === promises.length) resolve(results);
})
.catch(reject);
});
});
}
关键逻辑:结果数组的索引保持、完成计数器设计、空数组的特殊处理,以及错误传播机制。
5. 异步队列控制(并发数限制)
function asyncPool(poolLimit, array, iteratorFn) {
const ret = [];
const executing = new Set();
for (const item of array) {
const p = Promise.resolve().then(() => iteratorFn(item));
ret.push(p);
executing.add(p);
const clean = () => executing.delete(p);
p.then(clean).catch(clean);
if (executing.size >= poolLimit) {
await Promise.race(executing);
}
}
return Promise.all(ret);
}
实现难点:并发计数器的精确维护、Promise.race的合理使用,以及资源释放的时机控制。
三、数据结构模拟题
6. 实现LRU缓存(双向链表+哈希表)
class LRUCache {
constructor(capacity) {
this.capacity = capacity;
this.cache = new Map();
this.head = { key: null, value: null, prev: null, next: null };
this.tail = { key: null, value: null, prev: this.head, next: null };
this.head.next = this.tail;
}
get(key) {
if (!this.cache.has(key)) return -1;
const node = this.cache.get(key);
this._moveToHead(node);
return node.value;
}
put(key, value) {
if (this.cache.has(key)) {
const node = this.cache.get(key);
node.value = value;
this._moveToHead(node);
} else {
const newNode = { key, value, prev: null, next: null };
this.cache.set(key, newNode);
this._addToHead(newNode);
if (this.cache.size > this.capacity) {
const removed = this._removeTail();
this.cache.delete(removed.key);
}
}
}
_addToHead(node) {
node.prev = this.head;
node.next = this.head.next;
this.head.next.prev = node;
this.head.next = node;
}
_removeNode(node) {
node.prev.next = node.next;
node.next.prev = node.prev;
}
_moveToHead(node) {
this._removeNode(node);
this._addToHead(node);
}
_removeTail() {
const node = this.tail.prev;
this._removeNode(node);
return node;
}
}
设计要点:Map结构保证O(1)查找,双向链表维护访问顺序,需特别注意指针操作的顺序与边界条件。
7. 深拷贝实现(循环引用处理)
function deepClone(obj, hash = new WeakMap()) {
if (obj === null || typeof obj !== 'object') return obj;
if (hash.has(obj)) return hash.get(obj);
const cloneObj = Array.isArray(obj) ? [] : Object.create(Object.getPrototypeOf(obj));
hash.set(obj, cloneObj);
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
cloneObj[key] = deepClone(obj[key], hash);
}
}
return cloneObj;
}
关键处理:WeakMap解决循环引用,原型链继承的正确处理,以及非自有属性的过滤。
四、函数式编程题
8. 柯里化函数实现(参数累积与占位符支持)
function curry(fn, args = [], holes = []) {
return function(...newArgs) {
const combinedArgs = args.map((arg, i) =>
arg === '_' && holes.includes(i) ? newArgs.shift() : arg
).concat(newArgs);
const newHoles = holes.filter(i => args[i] !== '_');
const remainingArgs = fn.length - combinedArgs.length;
if (remainingArgs <= 0) {
return fn.apply(this, combinedArgs);
} else {
return curry(fn, combinedArgs, newHoles);
}
};
}
进阶特性:支持_
占位符的参数位置控制,精确计算剩余参数数量,实现真正的参数累积。
9. 函数组合(pipe与compose实现)
// 从左到右执行
function pipe(...fns) {
return function(initialValue) {
return fns.reduce((acc, fn) => fn(acc), initialValue);
};
}
// 从右到左执行(类似Redux的compose)
function compose(...fns) {
return function(initialValue) {
return fns.reduceRight((acc, fn) => fn(acc), initialValue);
};
}
应用场景:处理中间件管道、数据流转换等场景,需注意参数传递顺序与错误处理。
五、算法类经典题
10. 二分查找实现(边界条件处理)
function binarySearch(arr, target) {
let left = 0;
let right = arr.length - 1;
while (left <= right) {
const mid = Math.floor((left + right) / 2);
if (arr[mid] === target) return mid;
if (arr[mid] < target) left = mid + 1;
else right = mid - 1;
}
return -1;
}
关键细节:循环条件使用<=
而非<
,中间索引计算使用Math.floor
,以及边界更新的正确性。
11. 快速排序实现(分区策略优化)
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++) {
if (arr[i] < pivot) left.push(arr[i]);
else right.push(arr[i]);
}
return [...quickSort(left), pivot, ...quickSort(right)];
}
性能优化:三向切分的快速排序可处理大量重复元素,随机选择pivot避免最坏情况。
六、手写题备考策略
- 分类练习法:将题目按数据结构、算法、设计模式等维度分类,建立知识图谱
- 代码审查习惯:手写后立即检查边界条件、变量命名、注释完整性
- 变体题训练:如将”实现Promise.all”扩展为”实现Promise.any/race/allSettled”
- 性能优化意识:在实现中主动考虑时间复杂度与空间复杂度
- ES新特性应用:合理使用Proxy、Reflect、Generator等现代语法简化实现
七、常见面试官追问点
- 实现缺陷:如深拷贝未处理Symbol属性、LRU缓存未考虑Map的迭代顺序
- 性能瓶颈:数组去重的O(n²)方案与O(n)方案的对比
- 扩展性:如何将LRU缓存改为TTL缓存
- 错误处理:异步队列中的错误传播机制
- 兼容性:Set/Map在IE11中的polyfill方案
通过系统梳理这些高频手写题,开发者不仅能提升面试通过率,更能深化对JavaScript语言本质的理解。建议结合LeetCode中等难度题目进行同步训练,形成”理论-实践-优化”的完整学习闭环。
发表评论
登录后可评论,请前往 登录 或 注册