logo

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

作者:十万个为什么2025.09.19 12:47浏览量:0

简介:本文汇总了前端面试中高频出现的手写JavaScript题目,涵盖基础类型判断、深拷贝、事件总线、防抖节流等核心场景,提供解题思路与代码实现,助力开发者系统掌握面试必备技能。

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

在前端技术面试中,手写JavaScript代码是考察开发者基础能力的重要环节。这类题目不仅检验对语言特性的理解,更能体现问题拆解与工程化思维。本文系统梳理了8类高频手写题,结合实际场景与边界条件分析,帮助开发者构建完整的解题框架。

一、基础类型判断

1.1 类型检测的终极方案

传统typeof存在局限性(如typeof null === 'object'),而instanceof无法检测原始类型。更可靠的方案是结合Object.prototype.toString

  1. function getType(target) {
  2. return Object.prototype.toString.call(target).slice(8, -1).toLowerCase();
  3. }
  4. // 测试用例
  5. console.log(getType([])); // 'array'
  6. console.log(getType(null)); // 'null'

该方法通过调用对象的toString方法获取内部[[Class]]属性,覆盖所有JS类型。

1.2 数组判断的优化实现

判断变量是否为数组时,需考虑跨框架环境:

  1. function isArray(target) {
  2. // 优先使用ES5标准方法
  3. if (Array.isArray) return Array.isArray(target);
  4. // 兼容性回退
  5. return Object.prototype.toString.call(target) === '[object Array]';
  6. }

现代工程中建议直接使用Array.isArray(),此实现展示了渐进增强思想。

二、深拷贝实现

2.1 基础版递归实现

  1. function deepClone(obj, hash = new WeakMap()) {
  2. // 处理基本类型和函数
  3. if (obj === null || typeof obj !== 'object') return obj;
  4. // 处理循环引用
  5. if (hash.has(obj)) return hash.get(obj);
  6. // 处理Date和RegExp
  7. if (obj instanceof Date) return new Date(obj);
  8. if (obj instanceof RegExp) return new RegExp(obj);
  9. // 创建对应类型的实例
  10. const cloneObj = Array.isArray(obj) ? [] : {};
  11. hash.set(obj, cloneObj);
  12. // 递归拷贝属性
  13. for (let key in obj) {
  14. if (obj.hasOwnProperty(key)) {
  15. cloneObj[key] = deepClone(obj[key], hash);
  16. }
  17. }
  18. // 处理Symbol属性
  19. const symbolKeys = Object.getOwnPropertySymbols(obj);
  20. for (let key of symbolKeys) {
  21. cloneObj[key] = deepClone(obj[key], hash);
  22. }
  23. return cloneObj;
  24. }

该实现完整处理了:

  • 原始类型直接返回
  • 循环引用检测(使用WeakMap避免内存泄漏)
  • 特殊对象(Date/RegExp)
  • 继承属性过滤(hasOwnProperty)
  • Symbol属性拷贝

2.2 性能优化方案

对于大型对象,可采用迭代+栈的方式避免递归爆栈:

  1. function deepCloneIterative(obj) {
  2. const stack = [{ source: obj, target: {} }];
  3. const map = new WeakMap();
  4. while (stack.length) {
  5. const { source, target } = stack.pop();
  6. if (map.has(source)) {
  7. continue;
  8. }
  9. map.set(source, target);
  10. for (let key in source) {
  11. if (source.hasOwnProperty(key)) {
  12. if (typeof source[key] === 'object' && source[key] !== null) {
  13. stack.push({
  14. source: source[key],
  15. target: Array.isArray(source[key]) ? [] : {}
  16. });
  17. } else {
  18. target[key] = source[key];
  19. }
  20. }
  21. }
  22. }
  23. return target;
  24. }

三、事件总线实现

3.1 发布-订阅模式

  1. class EventBus {
  2. constructor() {
  3. this.events = {};
  4. }
  5. on(event, callback) {
  6. if (!this.events[event]) {
  7. this.events[event] = [];
  8. }
  9. this.events[event].push(callback);
  10. }
  11. emit(event, ...args) {
  12. if (this.events[event]) {
  13. this.events[event].forEach(callback => {
  14. callback(...args);
  15. });
  16. }
  17. }
  18. off(event, callback) {
  19. if (!this.events[event]) return;
  20. if (callback) {
  21. this.events[event] = this.events[event].filter(
  22. cb => cb !== callback
  23. );
  24. } else {
  25. delete this.events[event];
  26. }
  27. }
  28. once(event, callback) {
  29. const onceCallback = (...args) => {
  30. callback(...args);
  31. this.off(event, onceCallback);
  32. };
  33. this.on(event, onceCallback);
  34. }
  35. }

关键设计点:

  • 使用对象存储事件队列
  • 支持链式调用(可扩展)
  • once方法的实现技巧
  • 参数解构传递

四、防抖与节流

4.1 防抖(debounce)

  1. function debounce(fn, delay, immediate = false) {
  2. let timer = null;
  3. return function(...args) {
  4. const context = this;
  5. if (immediate && !timer) {
  6. fn.apply(context, args);
  7. }
  8. clearTimeout(timer);
  9. timer = setTimeout(() => {
  10. if (!immediate) {
  11. fn.apply(context, args);
  12. }
  13. timer = null;
  14. }, delay);
  15. };
  16. }

参数说明:

  • immediate控制是否立即执行首次调用
  • 使用闭包保存timer引用
  • 箭头函数保持this指向

4.2 节流(throttle)

  1. function throttle(fn, delay) {
  2. let lastTime = 0;
  3. let timer = null;
  4. return function(...args) {
  5. const context = this;
  6. const now = Date.now();
  7. const remaining = delay - (now - lastTime);
  8. if (remaining <= 0) {
  9. if (timer) {
  10. clearTimeout(timer);
  11. timer = null;
  12. }
  13. lastTime = now;
  14. fn.apply(context, args);
  15. } else if (!timer) {
  16. timer = setTimeout(() => {
  17. lastTime = Date.now();
  18. timer = null;
  19. fn.apply(context, args);
  20. }, remaining);
  21. }
  22. };
  23. }

时间戳+定时器组合实现:

  • 首次立即执行
  • 末次延迟执行
  • 动态计算剩余时间

五、Promise实现

5.1 基础版Promise

  1. class MyPromise {
  2. constructor(executor) {
  3. this.state = 'pending';
  4. this.value = undefined;
  5. this.reason = undefined;
  6. this.onFulfilledCallbacks = [];
  7. this.onRejectedCallbacks = [];
  8. const resolve = (value) => {
  9. if (this.state === 'pending') {
  10. this.state = 'fulfilled';
  11. this.value = value;
  12. this.onFulfilledCallbacks.forEach(fn => fn());
  13. }
  14. };
  15. const reject = (reason) => {
  16. if (this.state === 'pending') {
  17. this.state = 'rejected';
  18. this.reason = reason;
  19. this.onRejectedCallbacks.forEach(fn => fn());
  20. }
  21. };
  22. try {
  23. executor(resolve, reject);
  24. } catch (err) {
  25. reject(err);
  26. }
  27. }
  28. then(onFulfilled, onRejected) {
  29. onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
  30. onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err; };
  31. const promise2 = new MyPromise((resolve, reject) => {
  32. if (this.state === 'fulfilled') {
  33. setTimeout(() => {
  34. try {
  35. const x = onFulfilled(this.value);
  36. resolvePromise(promise2, x, resolve, reject);
  37. } catch (e) {
  38. reject(e);
  39. }
  40. }, 0);
  41. } else if (this.state === 'rejected') {
  42. setTimeout(() => {
  43. try {
  44. const x = onRejected(this.reason);
  45. resolvePromise(promise2, x, resolve, reject);
  46. } catch (e) {
  47. reject(e);
  48. }
  49. }, 0);
  50. } else if (this.state === 'pending') {
  51. this.onFulfilledCallbacks.push(() => {
  52. setTimeout(() => {
  53. try {
  54. const x = onFulfilled(this.value);
  55. resolvePromise(promise2, x, resolve, reject);
  56. } catch (e) {
  57. reject(e);
  58. }
  59. }, 0);
  60. });
  61. this.onRejectedCallbacks.push(() => {
  62. setTimeout(() => {
  63. try {
  64. const x = onRejected(this.reason);
  65. resolvePromise(promise2, x, resolve, reject);
  66. } catch (e) {
  67. reject(e);
  68. }
  69. }, 0);
  70. });
  71. }
  72. });
  73. return promise2;
  74. }
  75. }
  76. function resolvePromise(promise2, x, resolve, reject) {
  77. if (promise2 === x) {
  78. return reject(new TypeError('Chaining cycle detected for promise'));
  79. }
  80. let called = false;
  81. if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
  82. try {
  83. const then = x.then;
  84. if (typeof then === 'function') {
  85. then.call(
  86. x,
  87. y => {
  88. if (called) return;
  89. called = true;
  90. resolvePromise(promise2, y, resolve, reject);
  91. },
  92. r => {
  93. if (called) return;
  94. called = true;
  95. reject(r);
  96. }
  97. );
  98. } else {
  99. resolve(x);
  100. }
  101. } catch (e) {
  102. if (called) return;
  103. called = true;
  104. reject(e);
  105. }
  106. } else {
  107. resolve(x);
  108. }
  109. }

实现要点:

  • 三种状态管理
  • 异步执行回调(使用setTimeout)
  • 链式调用处理
  • 值穿透解析
  • 循环引用检测

六、柯里化实现

6.1 通用柯里化函数

  1. function curry(fn, args = []) {
  2. return function(...newArgs) {
  3. const allArgs = [...args, ...newArgs];
  4. if (allArgs.length >= fn.length) {
  5. return fn.apply(this, allArgs);
  6. } else {
  7. return curry.call(this, fn, allArgs);
  8. }
  9. };
  10. }
  11. // 使用示例
  12. function sum(a, b, c) {
  13. return a + b + c;
  14. }
  15. const curriedSum = curry(sum);
  16. console.log(curriedSum(1)(2)(3)); // 6
  17. console.log(curriedSum(1, 2)(3)); // 6

关键特性:

  • 参数收集与阈值判断
  • 递归调用保持this指向
  • 支持分步传参

七、函数组合

7.1 compose函数实现

  1. function compose(...funcs) {
  2. if (funcs.length === 0) return arg => arg;
  3. if (funcs.length === 1) return funcs[0];
  4. return funcs.reduce((a, b) => (...args) => a(b(...args)));
  5. }
  6. // 使用示例
  7. const add5 = x => x + 5;
  8. const multiply3 = x => x * 3;
  9. const divide2 = x => x / 2;
  10. const operation = compose(divide2, multiply3, add5);
  11. console.log(operation(10)); // ((10+5)*3)/2 = 22.5

实现要点:

  • 边界条件处理
  • 使用reduce实现从右向左的组合
  • 支持多参数传递(通过…args)

八、手写AJAX

8.1 原生XMLHttpRequest实现

  1. function ajax(options) {
  2. const { url, method = 'GET', data = null, async = true, headers = {} } = options;
  3. return new Promise((resolve, reject) => {
  4. const xhr = new XMLHttpRequest();
  5. xhr.open(method, url, async);
  6. // 设置请求头
  7. Object.keys(headers).forEach(key => {
  8. xhr.setRequestHeader(key, headers[key]);
  9. });
  10. xhr.onload = function() {
  11. if (xhr.status >= 200 && xhr.status < 300) {
  12. resolve({
  13. status: xhr.status,
  14. data: xhr.responseText,
  15. headers: xhr.getAllResponseHeaders()
  16. });
  17. } else {
  18. reject({
  19. status: xhr.status,
  20. error: xhr.statusText
  21. });
  22. }
  23. };
  24. xhr.onerror = function() {
  25. reject({
  26. error: 'Network Error'
  27. });
  28. };
  29. // 处理请求体
  30. if (data) {
  31. if (typeof data === 'object') {
  32. data = JSON.stringify(data);
  33. if (!headers['Content-Type']) {
  34. xhr.setRequestHeader('Content-Type', 'application/json');
  35. }
  36. }
  37. xhr.send(data);
  38. } else {
  39. xhr.send();
  40. }
  41. });
  42. }

关键实现:

  • Promise封装
  • 请求头动态设置
  • 状态码范围判断
  • JSON数据序列化
  • 错误处理机制

面试应对策略

  1. 代码规范:保持一致的缩进和命名风格
  2. 边界处理:主动考虑null/undefined等特殊情况
  3. 性能优化:在实现中体现对性能的考虑(如循环引用检测)
  4. 沟通技巧
    • 先实现基础功能,再逐步优化
    • 对不确定的部分明确说明假设条件
    • 主动讨论可能的扩展场景

总结

高频手写题考察的是开发者对语言特性的深度理解和工程化思维。建议通过以下方式提升:

  1. 建立类型判断、异步处理等核心模块的模板库
  2. 针对每个实现编写完整的测试用例
  3. 理解底层原理(如事件循环、原型链)
  4. 关注ES6+新特性(如Proxy、Reflect)的应用

掌握这些核心题目后,建议进一步研究源码级实现(如lodash的深拷贝方案),这将在高级面试中形成差异化优势。

相关文章推荐

发表评论