面试和算法:常见面试题实现与深度解析

本文将深入探讨前端面试中常见的算法和编程题,提供多种实现方案和性能优化策略,帮助大家全面掌握核心面试技能。

1. 函数柯里化(Currying)

1.1 基础柯里化实现
javascript 复制代码
/**
 * 基础柯里化函数
 * 将多参数函数转换为一系列单参数函数
 */
function curry(fn) {
  return function curried(...args) {
    // 如果参数数量足够, 直接执行原函数
    if (args.length >= fn.length) {
      return fn.apply(this, args);
    }
    // 否则返回一个新函数, 继续收集参数
    return function (...nextArgs) {
      return curried.apply(this, args.concat(nextArgs));
    };
  };
}
// 示例:加法函数柯里化
function add(a, b, c) {
  return a + b + c;
}

const curriedAdd = curry(add);

// 测试
console.log(curriedAdd(1)(2)(3)); // 6
console.log(curriedAdd(1, 2)(3)); // 6
console.log(curriedAdd(1)(2, 3)); // 6
1.2 高级柯里化实现(支持占位符)
javascript 复制代码
/**
 * 高级柯里化函数, 支持占位符
 */
function advancedCurry(fn) {
  return function curried(...args) {
    // 检查参数是否足够且没有占位符
    const complete =
      args.length >= fn.length &&
      !args.slice(0, fn.length).includes(advancedCurry.placeholder);
    if (complete) {
      return fn.apply(this, args);
    }

    return function (...nextArgs) {
      // 替换占位符
      const combinedArgs = args
        .map((arg) =>
          arg === advancedCurry.placeholder && nextArgs.length
            ? nextArgs.shift()
            : arg
        )
        .concat(nextArgs);

      return curried.apply(this, combinedArgs);
    };
  };
}

// 定义占位符
advancedCurry.placeholder = Symbol("_");
// 示例使用
const curriedMultiply = advancedCurry((a, b, c) => a * b * c);

const _ = advancedCurry.placeholder;

// 测试
console.log(curriedMultiply(2)(3)(4)); // 24
console.log(curriedMultiply(_, 3)(2)(4)); // 24
console.log(curriedMultiply(2, _, 4)(3)); // 24

2. 函数组合(Compose)

2.1 基础函数组合
javascript 复制代码
function compose(...fns) {
  return function(x) {
    return fns.reduceRight((acc, fn) => fn(acc), x);
  };
}

function pipe(...fns) {
  return function(x) {
    return fns.reduce((acc, fn) => fn(acc), x);
  };
}

// 测试
const add1 = x => x + 1;
const multiply2 = x => x * 2;
const square = x => x * x;

const composed = compose(square, multiply2, add1);
console.log(composed(2)); // 36

3. 斐波那契数列优化

3.1 多种实现对比
javascript 复制代码
// 1. 递归(性能差)
function fibonacciRecursive(n) {
  if (n <= 1) return n;
  return fibonacciRecursive(n - 1) + fibonacciRecursive(n - 2);
}

// 2. 记忆化递归
function fibonacciMemo(n, memo = {}) {
  if (n <= 1) return n;
  if (memo[n]) return memo[n];
  memo[n] = fibonacciMemo(n - 1, memo) + fibonacciMemo(n - 2, memo);
  return memo[n];
}

// 3. 动态规划
function fibonacciDP(n) {
  if (n <= 1) return n;
  const dp = [0, 1];
  for (let i = 2; i <= n; i++) {
    dp[i] = dp[i - 1] + dp[i - 2];
  }
  return dp[n];
}

// 4. 空间优化
function fibonacciOptimized(n) {
  if (n <= 1) return n;
  let prev = 0, curr = 1;
  for (let i = 2; i <= n; i++) {
    const next = prev + curr;
    prev = curr;
    curr = next;
  }
  return curr;
}

4. 数组去重多种方法

4.1 基础方法
javascript 复制代码
// 1. Set
function uniqueSet(arr) {
  return [...new Set(arr)];
}

// 2. filter + indexOf
function uniqueFilter(arr) {
  return arr.filter((item, index) => arr.indexOf(item) === index);
}

// 3. reduce
function uniqueReduce(arr) {
  return arr.reduce((acc, curr) => {
    if (!acc.includes(curr)) acc.push(curr);
    return acc;
  }, []);
}
4.2 复杂对象去重
javascript 复制代码
function uniqueComplex(arr, keyFn) {
  const seen = new Map();
  const result = [];
  
  for (let item of arr) {
    const key = keyFn ? keyFn(item) : JSON.stringify(item);
    if (!seen.has(key)) {
      seen.set(key, true);
      result.push(item);
    }
  }
  
  return result;
}

// 测试
const users = [
  { id: 1, name: 'Alice' },
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' }
];
console.log(uniqueComplex(users, user => user.id));

5. 深比较(DeepEqual)

javascript 复制代码
function deepEqual(a, b) {
  if (a === b) return true;
  
  if (a == null || b == null || typeof a !== 'object' || typeof b !== 'object') {
    return false;
  }
  
  if (Array.isArray(a) && Array.isArray(b)) {
    if (a.length !== b.length) return false;
    for (let i = 0; i < a.length; i++) {
      if (!deepEqual(a[i], b[i])) return false;
    }
    return true;
  }
  
  const keysA = Object.keys(a);
  const keysB = Object.keys(b);
  
  if (keysA.length !== keysB.length) return false;
  
  for (let key of keysA) {
    if (!keysB.includes(key) || !deepEqual(a[key], b[key])) {
      return false;
    }
  }
  
  return true;
}

6. 防抖与节流

防抖(Debounce) 节流(Throttle)

javascript 复制代码
function debounce(fn, delay) {
  let timer = null;
  return function(...args) {
    if (timer) clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, args), delay);
  };
}

function throttle(fn, interval) {
  let lastTime = 0;
  return function(...args) {
    const now = Date.now();
    if (now - lastTime >= interval) {
      fn.apply(this, args);
      lastTime = now;
    }
  };
}

7. Promise实现

手写 Promise:深入理解 JavaScript 异步编程的核心

javascript 复制代码
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());
      }
    };
    
    const reject = (reason) => {
      if (this.state === 'pending') {
        this.state = 'rejected';
        this.reason = reason;
        this.onRejectedCallbacks.forEach(cb => cb());
      }
    };
    
    try {
      executor(resolve, reject);
    } catch (error) {
      reject(error);
    }
  }
  
  then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v;
    onRejected = typeof onRejected === 'function' ? onRejected : e => { throw e };
    
    const promise2 = new MyPromise((resolve, reject) => {
      const handleFulfilled = () => {
        setTimeout(() => {
          try {
            const x = onFulfilled(this.value);
            this.resolvePromise(promise2, x, resolve, reject);
          } catch (error) {
            reject(error);
          }
        });
      };
      
      const handleRejected = () => {
        setTimeout(() => {
          try {
            const x = onRejected(this.reason);
            this.resolvePromise(promise2, x, resolve, reject);
          } catch (error) {
            reject(error);
          }
        });
      };
      
      if (this.state === 'fulfilled') {
        handleFulfilled();
      } else if (this.state === 'rejected') {
        handleRejected();
      } else {
        this.onFulfilledCallbacks.push(handleFulfilled);
        this.onRejectedCallbacks.push(handleRejected);
      }
    });
    
    return promise2;
  }
  
  resolvePromise(promise2, x, resolve, reject) {
    if (promise2 === x) {
      return reject(new TypeError('循环引用'));
    }
    
    let called = false;
    
    if (x && (typeof x === 'object' || typeof x === 'function')) {
      try {
        const then = x.then;
        if (typeof then === 'function') {
          then.call(
            x,
            y => {
              if (called) return;
              called = true;
              this.resolvePromise(promise2, y, resolve, reject);
            },
            r => {
              if (called) return;
              called = true;
              reject(r);
            }
          );
        } else {
          resolve(x);
        }
      } catch (error) {
        if (called) return;
        called = true;
        reject(error);
      }
    } else {
      resolve(x);
    }
  }
  
  catch(onRejected) {
    return this.then(null, onRejected);
  }
}

8. call、apply、bind实现

JavaScript 核心方法深度解析:手写 call、apply、bind 和 Object.create

javascript 复制代码
Function.prototype.myCall = function(context = window, ...args) {
  const fnKey = Symbol('fn');
  context[fnKey] = this;
  const result = context[fnKey](...args);
  delete context[fnKey];
  return result;
};

Function.prototype.myApply = function(context = window, args = []) {
  const fnKey = Symbol('fn');
  context[fnKey] = this;
  const result = context[fnKey](...args);
  delete context[fnKey];
  return result;
};

Function.prototype.myBind = function(context = window, ...bindArgs) {
  const self = this;
  return function(...callArgs) {
    return self.apply(context, [...bindArgs, ...callArgs]);
  };
};

9. 事件总线(EventEmitter)

手写 EventEmitter:深入理解发布订阅模式

javascript 复制代码
class EventEmitter {
  constructor() {
    this.events = new Map();
  }
  
  on(event, listener) {
    if (!this.events.has(event)) {
      this.events.set(event, []);
    }
    this.events.get(event).push(listener);
  }
  
  off(event, listener) {
    if (!this.events.has(event)) return;
    const listeners = this.events.get(event);
    const index = listeners.indexOf(listener);
    if (index > -1) listeners.splice(index, 1);
  }
  
  emit(event, ...args) {
    if (!this.events.has(event)) return false;
    this.events.get(event).forEach(listener => listener.apply(this, args));
    return true;
  }
  
  once(event, listener) {
    const onceWrapper = (...args) => {
      listener.apply(this, args);
      this.off(event, onceWrapper);
    };
    this.on(event, onceWrapper);
  }
}

10. LRU缓存

JavaScript性能与优化:手写实现关键优化技术 JavaScript 性能与优化:数据结构和算法

javascript 复制代码
class LRUCache {
  constructor(capacity) {
    this.capacity = capacity;
    this.cache = new Map();
  }
  
  get(key) {
    if (!this.cache.has(key)) return -1;
    const value = this.cache.get(key);
    this.cache.delete(key);
    this.cache.set(key, value);
    return value;
  }
  
  put(key, value) {
    if (this.cache.has(key)) {
      this.cache.delete(key);
    } else if (this.cache.size >= this.capacity) {
      const firstKey = this.cache.keys().next().value;
      this.cache.delete(firstKey);
    }
    this.cache.set(key, value);
  }
}

11. 快速排序

JavaScript 数组原生方法手写实现 JavaScript 性能与优化:数据结构和算法

javascript 复制代码
function quickSort(arr) {
  if (arr.length <= 1) return arr;
  
  const pivotIndex = Math.floor(arr.length / 2);
  const pivot = arr[pivotIndex];
  const left = [];
  const right = [];
  
  for (let i = 0; i < arr.length; i++) {
    if (i === pivotIndex) continue;
    if (arr[i] < pivot) {
      left.push(arr[i]);
    } else {
      right.push(arr[i]);
    }
  }
  
  return [...quickSort(left), pivot, ...quickSort(right)];
}

12. 二分查找

JavaScript 性能与优化:数据结构和算法

javascript 复制代码
function binarySearch(arr, target) {
  let left = 0, 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;
}

总结

本文涵盖了前端面试中常见的算法和编程题,包括函数柯里化、函数组合、斐波那契数列、数组去重、深比较、防抖节流、Promise实现等核心知识点。掌握这些内容有助于提升编程能力和面试表现。

关键要点:

  1. 函数柯里化: 理解函数式编程思想,掌握基础实现和高级功能
  2. 函数组合: 学会构建可复用的函数管道,支持同步和异步操作
  3. 算法优化: 掌握递归优化、动态规划、空间优化等技巧
  4. 数据处理: 了解不同去重方法的适用场景和性能差异
  5. 深度比较: 处理复杂对象的比较,包括循环引用和特殊类型
  6. 异步控制: 实现防抖和节流,优化高频事件处理
  7. Promise实现: 深入理解异步编程模型
  8. 原生方法实现: 掌握call、apply、bind的内部原理
  9. 设计模式: 实现事件总线,理解发布-订阅模式

这些知识点不仅是面试的常见考点,也是实际开发中的重要技能。建议大家不仅要理解代码实现,更要掌握背后的设计思想和适用场景。

相关推荐
Ashley_Amanda2 小时前
JavaScript 中数组的常用处理方法
开发语言·javascript·网络
BD_Marathon2 小时前
Router_路由的基本使用
javascript
float_六七2 小时前
行级与块级元素:核心区别与应用场景
开发语言·前端·javascript
Gary Studio2 小时前
面试经验积累
面试·职场和发展
毕设十刻2 小时前
基于Vue的家教预约系统7fisz(程序 + 源码 + 数据库 + 调试部署 + 开发环境配置),配套论文文档字数达万字以上,文末可获取,系统界面展示置于文末
前端·数据库·vue.js
前端无涯2 小时前
深度解析:fetch 与 Promise 结合实战及面试重点
前端·javascript
风舞红枫3 小时前
node代理vue打包后的文件,实现本地测试
前端·javascript·vue.js·node.js
helloCat3 小时前
记录CI/CD自动化上传AppGallery遇到的坑
android·前端·api