logo

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

作者:KAKAKA2025.09.19 12:47浏览量:0

简介:掌握高频手写JS题是面试成功的关键,本文汇总了类型检查、数组操作、异步处理等核心题型,并提供解题思路与代码示例。

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

在前端开发岗位的面试中,手写JavaScript代码题是考察候选人基础能力的重要环节。这类题目不仅检验对语言特性的理解,更能体现问题拆解与逻辑实现能力。本文将系统梳理面试中高频出现的手写JS题型,覆盖类型检查、数组操作、异步处理等核心场景,并提供解题思路与代码示例。

一、类型检查与转换

1. 深度类型判断

基础typeofinstanceof无法满足复杂场景需求,需实现能区分nullArrayDate等特殊类型的函数:

  1. function deepType(obj) {
  2. return Object.prototype.toString.call(obj).slice(8, -1).toLowerCase();
  3. }
  4. // 测试用例
  5. deepType([]); // "array"
  6. deepType(new Date()); // "date"
  7. deepType(null); // "null"

该实现利用Object.prototype.toString的内部行为,通过截取字符串获取精确类型标识,比手动判断constructor更可靠。

2. 安全类型转换

实现将任意值转为指定类型的函数,需处理边界情况:

  1. function safeConvert(value, targetType) {
  2. switch (targetType.toLowerCase()) {
  3. case 'number':
  4. return isNaN(Number(value)) ? 0 : Number(value);
  5. case 'string':
  6. return String(value);
  7. case 'boolean':
  8. return value === '' ? false : !!value;
  9. default:
  10. throw new Error('Unsupported type');
  11. }
  12. }
  13. // 测试用例
  14. safeConvert('123', 'number'); // 123
  15. safeConvert(null, 'string'); // "null"

关键点在于处理null、空字符串等特殊值,避免隐式转换的意外行为。

二、数组操作算法

1. 扁平化数组

实现多级嵌套数组的扁平化,支持指定深度:

  1. function flatten(arr, depth = Infinity) {
  2. return depth > 0
  3. ? arr.reduce((acc, val) =>
  4. Array.isArray(val)
  5. ? acc.concat(flatten(val, depth - 1))
  6. : acc.concat(val),
  7. [])
  8. : arr.slice();
  9. }
  10. // 测试用例
  11. flatten([1, [2, [3]]], 1); // [1, 2, [3]]

递归实现需注意深度控制与性能优化,对于超大数组可改用迭代方式。

2. 数组去重

实现高性能去重函数,考虑不同数据类型:

  1. function unique(arr) {
  2. const seen = new WeakSet(); // 仅对对象有效
  3. const result = [];
  4. const stringSet = new Set();
  5. for (const item of arr) {
  6. const isObject = deepType(item) === 'object';
  7. const key = isObject ? JSON.stringify(item) : item;
  8. if (isObject
  9. ? !seen.has(item) && seen.add(item)
  10. : !stringSet.has(key) && stringSet.add(key)) {
  11. result.push(item);
  12. }
  13. }
  14. return result;
  15. }
  16. // 测试用例
  17. unique([1, 1, '1', {a:1}, {a:1}]); // [1, '1', {a:1}]

混合使用SetWeakSet处理原始值与引用类型,JSON.stringify需注意对象属性顺序问题。

三、异步编程实现

1. Promise链式调用

实现简化版Promise,支持then方法:

  1. class MyPromise {
  2. constructor(executor) {
  3. this.value = undefined;
  4. this.reason = undefined;
  5. this.onFulfilled = [];
  6. this.onRejected = [];
  7. const resolve = (value) => {
  8. setTimeout(() => {
  9. this.value = value;
  10. this.onFulfilled.forEach(fn => fn(value));
  11. }, 0);
  12. };
  13. executor(resolve, reject);
  14. }
  15. then(onFulfilled) {
  16. return new MyPromise(resolve => {
  17. this.onFulfilled.push(val => {
  18. const result = onFulfilled(val);
  19. if (result instanceof MyPromise) {
  20. result.then(resolve);
  21. } else {
  22. resolve(result);
  23. }
  24. });
  25. });
  26. }
  27. }

关键点在于异步调度与值穿透处理,实际实现需完善状态管理与错误处理。

2. 异步队列控制

实现并发数限制的异步任务队列:

  1. class AsyncQueue {
  2. constructor(concurrency = 1) {
  3. this.concurrency = concurrency;
  4. this.running = 0;
  5. this.queue = [];
  6. }
  7. add(task) {
  8. return new Promise((resolve, reject) => {
  9. this.queue.push({ task, resolve, reject });
  10. this.next();
  11. });
  12. }
  13. next() {
  14. while (this.running < this.concurrency && this.queue.length) {
  15. const { task, resolve, reject } = this.queue.shift();
  16. this.running++;
  17. task().then(resolve, reject).finally(() => {
  18. this.running--;
  19. this.next();
  20. });
  21. }
  22. }
  23. }
  24. // 使用示例
  25. const queue = new AsyncQueue(2);
  26. Array(5).fill().map((_,i) =>
  27. queue.add(() => new Promise(r => setTimeout(() => {
  28. console.log(i);
  29. r(i);
  30. }, 1000)))
  31. );

通过维护运行计数器与任务队列实现并发控制,适用于批量API调用场景。

四、函数式编程实践

1. 柯里化实现

实现参数自动收集的柯里化函数:

  1. function curry(fn) {
  2. return function curried(...args) {
  3. if (args.length >= fn.length) {
  4. return fn.apply(this, args);
  5. } else {
  6. return function(...args2) {
  7. return curried.apply(this, args.concat(args2));
  8. }
  9. }
  10. };
  11. }
  12. // 测试用例
  13. const sum = curry((a,b,c) => a+b+c);
  14. sum(1)(2)(3); // 6

关键在于通过fn.length判断参数收集完成时机,支持占位符的高级实现可进一步扩展。

2. 组合函数实现

实现函数组合工具,支持从右到左的执行顺序:

  1. function compose(...fns) {
  2. return function(initialValue) {
  3. return fns.reduceRight((acc, fn) => fn(acc), initialValue);
  4. };
  5. }
  6. // 测试用例
  7. const add1 = x => x + 1;
  8. const mul2 = x => x * 2;
  9. compose(mul2, add1)(5); // 12

pipe函数的区别在于执行顺序,适用于数据处理流水线构建。

五、性能优化技巧

1. 防抖与节流

实现高频事件处理的防抖函数:

  1. function debounce(fn, delay) {
  2. let timer = null;
  3. return function(...args) {
  4. clearTimeout(timer);
  5. timer = setTimeout(() => fn.apply(this, args), delay);
  6. };
  7. }
  8. // 测试用例
  9. window.addEventListener('resize', debounce(() => {
  10. console.log('Resized');
  11. }, 200));

节流函数的实现需使用时间戳或标记位控制执行频率,适用于滚动、输入等场景。

2. 记忆化缓存

实现函数结果缓存的装饰器:

  1. function memoize(fn) {
  2. const cache = new Map();
  3. return function(...args) {
  4. const key = JSON.stringify(args);
  5. if (cache.has(key)) {
  6. return cache.get(key);
  7. }
  8. const result = fn.apply(this, args);
  9. cache.set(key, result);
  10. return result;
  11. };
  12. }
  13. // 测试用例
  14. const expensiveCalc = memoize((a,b) => {
  15. console.log('Calculating...');
  16. return a * b;
  17. });
  18. expensiveCalc(2,3); // 执行计算
  19. expensiveCalc(2,3); // 从缓存读取

适用于计算密集型函数,需注意缓存大小控制与参数序列化问题。

六、实战建议

  1. 分步拆解:将复杂问题拆解为输入处理、核心逻辑、输出返回三个阶段
  2. 边界测试:编写测试用例时覆盖null、undefined、空数组等边界情况
  3. 性能考量:对于大数据量处理,优先考虑时间复杂度与空间复杂度
  4. ES6+特性:合理使用...展开符、ProxyGenerator等现代语法简化实现

掌握这些高频手写题不仅能帮助通过面试,更能深化对JavaScript语言特性的理解。建议结合实际项目场景,理解每个实现背后的设计思想,而非机械记忆代码模板。

相关文章推荐

发表评论