logo

面试常考的高频手写js题汇总

作者:公子世无双2025.09.19 12:55浏览量:0

简介:本文汇总了前端面试中高频出现的手写JavaScript题目,涵盖数组操作、异步编程、数据结构模拟等核心场景,通过代码示例与实现思路解析,帮助开发者系统掌握面试必考知识点。

面试常考的高频手写JS题汇总

在前端技术面试中,手写JavaScript代码是考察开发者基础能力的核心环节。这类题目不仅能检验候选人对语言特性的理解深度,还能体现其逻辑思维与问题解决能力。本文将从数组操作、异步编程、数据结构模拟等维度,系统梳理面试中高频出现的20道手写题,并提供标准实现与优化思路。

一、数组操作类高频题

1. 数组去重(核心考点:Set特性与对象属性判断)

  1. // 方法1:利用Set特性(ES6推荐)
  2. function unique(arr) {
  3. return [...new Set(arr)];
  4. }
  5. // 方法2:对象属性去重(兼容ES5)
  6. function uniqueES5(arr) {
  7. const seen = {};
  8. return arr.filter(item => {
  9. return seen.hasOwnProperty(item) ? false : (seen[item] = true);
  10. });
  11. }

考察点:Set数据结构的哈希存储特性,以及对象属性访问的时间复杂度优化。需注意对象键名会被强制转为字符串的特性。

2. 扁平化嵌套数组(递归与reduce应用)

  1. // 递归实现(支持无限层级)
  2. function flattenDeep(arr) {
  3. return arr.reduce((acc, val) =>
  4. Array.isArray(val) ? acc.concat(flattenDeep(val)) : acc.concat(val),
  5. []);
  6. }
  7. // 指定深度版本
  8. function flatten(arr, depth = 1) {
  9. return depth > 0
  10. ? arr.reduce((acc, val) =>
  11. Array.isArray(val)
  12. ? acc.concat(flatten(val, depth - 1))
  13. : acc.concat(val),
  14. [])
  15. : arr.slice();
  16. }

关键点:递归终止条件设计、reduce方法的正确使用,以及深度参数的控制逻辑。

3. 实现数组map方法(闭包与this绑定)

  1. Array.prototype.myMap = function(callback, thisArg) {
  2. const result = [];
  3. for (let i = 0; i < this.length; i++) {
  4. if (i in this) {
  5. result.push(callback.call(thisArg, this[i], i, this));
  6. }
  7. }
  8. return result;
  9. };

实现要点:需处理稀疏数组的跳过逻辑,正确传递参数列表(当前值、索引、原数组),以及thisArg的绑定。

二、异步编程核心题

4. Promise.all实现(状态管理与错误处理)

  1. function promiseAll(promises) {
  2. return new Promise((resolve, reject) => {
  3. const results = [];
  4. let completed = 0;
  5. if (promises.length === 0) return resolve([]);
  6. promises.forEach((promise, index) => {
  7. Promise.resolve(promise)
  8. .then(value => {
  9. results[index] = value;
  10. completed++;
  11. if (completed === promises.length) resolve(results);
  12. })
  13. .catch(reject);
  14. });
  15. });
  16. }

关键逻辑:结果数组的索引保持、完成计数器设计、空数组的特殊处理,以及错误传播机制。

5. 异步队列控制(并发数限制)

  1. function asyncPool(poolLimit, array, iteratorFn) {
  2. const ret = [];
  3. const executing = new Set();
  4. for (const item of array) {
  5. const p = Promise.resolve().then(() => iteratorFn(item));
  6. ret.push(p);
  7. executing.add(p);
  8. const clean = () => executing.delete(p);
  9. p.then(clean).catch(clean);
  10. if (executing.size >= poolLimit) {
  11. await Promise.race(executing);
  12. }
  13. }
  14. return Promise.all(ret);
  15. }

实现难点:并发计数器的精确维护、Promise.race的合理使用,以及资源释放的时机控制。

三、数据结构模拟题

6. 实现LRU缓存(双向链表+哈希表)

  1. class LRUCache {
  2. constructor(capacity) {
  3. this.capacity = capacity;
  4. this.cache = new Map();
  5. this.head = { key: null, value: null, prev: null, next: null };
  6. this.tail = { key: null, value: null, prev: this.head, next: null };
  7. this.head.next = this.tail;
  8. }
  9. get(key) {
  10. if (!this.cache.has(key)) return -1;
  11. const node = this.cache.get(key);
  12. this._moveToHead(node);
  13. return node.value;
  14. }
  15. put(key, value) {
  16. if (this.cache.has(key)) {
  17. const node = this.cache.get(key);
  18. node.value = value;
  19. this._moveToHead(node);
  20. } else {
  21. const newNode = { key, value, prev: null, next: null };
  22. this.cache.set(key, newNode);
  23. this._addToHead(newNode);
  24. if (this.cache.size > this.capacity) {
  25. const removed = this._removeTail();
  26. this.cache.delete(removed.key);
  27. }
  28. }
  29. }
  30. _addToHead(node) {
  31. node.prev = this.head;
  32. node.next = this.head.next;
  33. this.head.next.prev = node;
  34. this.head.next = node;
  35. }
  36. _removeNode(node) {
  37. node.prev.next = node.next;
  38. node.next.prev = node.prev;
  39. }
  40. _moveToHead(node) {
  41. this._removeNode(node);
  42. this._addToHead(node);
  43. }
  44. _removeTail() {
  45. const node = this.tail.prev;
  46. this._removeNode(node);
  47. return node;
  48. }
  49. }

设计要点:Map结构保证O(1)查找,双向链表维护访问顺序,需特别注意指针操作的顺序与边界条件。

7. 深拷贝实现(循环引用处理)

  1. function deepClone(obj, hash = new WeakMap()) {
  2. if (obj === null || typeof obj !== 'object') return obj;
  3. if (hash.has(obj)) return hash.get(obj);
  4. const cloneObj = Array.isArray(obj) ? [] : Object.create(Object.getPrototypeOf(obj));
  5. hash.set(obj, cloneObj);
  6. for (const key in obj) {
  7. if (obj.hasOwnProperty(key)) {
  8. cloneObj[key] = deepClone(obj[key], hash);
  9. }
  10. }
  11. return cloneObj;
  12. }

关键处理:WeakMap解决循环引用,原型链继承的正确处理,以及非自有属性的过滤。

四、函数式编程题

8. 柯里化函数实现(参数累积与占位符支持)

  1. function curry(fn, args = [], holes = []) {
  2. return function(...newArgs) {
  3. const combinedArgs = args.map((arg, i) =>
  4. arg === '_' && holes.includes(i) ? newArgs.shift() : arg
  5. ).concat(newArgs);
  6. const newHoles = holes.filter(i => args[i] !== '_');
  7. const remainingArgs = fn.length - combinedArgs.length;
  8. if (remainingArgs <= 0) {
  9. return fn.apply(this, combinedArgs);
  10. } else {
  11. return curry(fn, combinedArgs, newHoles);
  12. }
  13. };
  14. }

进阶特性:支持_占位符的参数位置控制,精确计算剩余参数数量,实现真正的参数累积。

9. 函数组合(pipe与compose实现)

  1. // 从左到右执行
  2. function pipe(...fns) {
  3. return function(initialValue) {
  4. return fns.reduce((acc, fn) => fn(acc), initialValue);
  5. };
  6. }
  7. // 从右到左执行(类似Redux的compose)
  8. function compose(...fns) {
  9. return function(initialValue) {
  10. return fns.reduceRight((acc, fn) => fn(acc), initialValue);
  11. };
  12. }

应用场景:处理中间件管道、数据流转换等场景,需注意参数传递顺序与错误处理。

五、算法类经典题

10. 二分查找实现(边界条件处理)

  1. function binarySearch(arr, target) {
  2. let left = 0;
  3. let right = arr.length - 1;
  4. while (left <= right) {
  5. const mid = Math.floor((left + right) / 2);
  6. if (arr[mid] === target) return mid;
  7. if (arr[mid] < target) left = mid + 1;
  8. else right = mid - 1;
  9. }
  10. return -1;
  11. }

关键细节:循环条件使用<=而非<,中间索引计算使用Math.floor,以及边界更新的正确性。

11. 快速排序实现(分区策略优化)

  1. function quickSort(arr) {
  2. if (arr.length <= 1) return arr;
  3. const pivot = arr[0];
  4. const left = [];
  5. const right = [];
  6. for (let i = 1; i < arr.length; i++) {
  7. if (arr[i] < pivot) left.push(arr[i]);
  8. else right.push(arr[i]);
  9. }
  10. return [...quickSort(left), pivot, ...quickSort(right)];
  11. }

性能优化:三向切分的快速排序可处理大量重复元素,随机选择pivot避免最坏情况。

六、手写题备考策略

  1. 分类练习法:将题目按数据结构、算法、设计模式等维度分类,建立知识图谱
  2. 代码审查习惯:手写后立即检查边界条件、变量命名、注释完整性
  3. 变体题训练:如将”实现Promise.all”扩展为”实现Promise.any/race/allSettled”
  4. 性能优化意识:在实现中主动考虑时间复杂度与空间复杂度
  5. ES新特性应用:合理使用Proxy、Reflect、Generator等现代语法简化实现

七、常见面试官追问点

  1. 实现缺陷:如深拷贝未处理Symbol属性、LRU缓存未考虑Map的迭代顺序
  2. 性能瓶颈:数组去重的O(n²)方案与O(n)方案的对比
  3. 扩展性:如何将LRU缓存改为TTL缓存
  4. 错误处理:异步队列中的错误传播机制
  5. 兼容性:Set/Map在IE11中的polyfill方案

通过系统梳理这些高频手写题,开发者不仅能提升面试通过率,更能深化对JavaScript语言本质的理解。建议结合LeetCode中等难度题目进行同步训练,形成”理论-实践-优化”的完整学习闭环。

相关文章推荐

发表评论