JavaScript高频算法实战:从哈希表优化两数之和到深拷贝的循环引用处理

1次阅读
没有评论

共计 2338 个字符,预计需要花费 6 分钟才能阅读完成。

image.webp

为什么这些算法问题值得关注

在日常 JavaScript 开发中,算法优化和特殊场景处理是提升代码质量的关键。无论是面试高频题两数之和,还是实际业务中频繁出现的防抖需求,亦或是数据操作时的深拷贝痛点,都需要我们掌握系统化的解决方案。本文将从四个典型场景出发,提供可直接落地的实现方案。

JavaScript 高频算法实战:从哈希表优化两数之和到深拷贝的循环引用处理

1. 两数之和:哈希表优化

问题背景

给定一个整数数组 nums 和一个目标值 target,需要在数组中找出和为目标值的两个整数。

暴力解法(O(n²))

双循环遍历所有组合,最直观但效率最低:

function twoSumNaive(nums, target) {for (let i = 0; i < nums.length; i++) {for (let j = i + 1; j < nums.length; j++) {if (nums[i] + nums[j] === target) {return [i, j];
      }
    }
  }
  return [];}

哈希表优化(O(n))

通过空间换时间,存储遍历过的数值及其索引:

function twoSum(nums, target) {const map = new Map();
  for (let i = 0; i < nums.length; i++) {const complement = target - nums[i];
    if (map.has(complement)) {return [map.get(complement), i];
    }
    map.set(nums[i], i); // 存储当前值和索引
  }
  return [];}

性能对比

在 10,000 个元素的数组测试中:
– 暴力解法:~250ms
– 哈希表解法:~5ms

2. 防抖函数:控制执行频率

核心原理

在事件频繁触发时,只执行最后一次或第一次调用。

基础实现

function debounce(fn, delay) {
  let timer = null;
  return function(...args) {clearTimeout(timer); // 清除之前的调用
    timer = setTimeout(() => {fn.apply(this, args);
    }, delay);
  };
}

立即执行版

function debounceImmediate(fn, delay, immediate = true) {
  let timer = null;
  return function(...args) {if (timer) clearTimeout(timer);
    if (immediate && !timer) {fn.apply(this, args);
    }
    timer = setTimeout(() => {
      timer = null;
      if (!immediate) fn.apply(this, args);
    }, delay);
  };
}

典型应用场景

  • 搜索框输入联想
  • 窗口 resize 事件
  • 按钮频繁点击防护

3. 深拷贝:处理复杂对象

JSON 方法的局限性

const obj = {a: 1};
obj.self = obj;
JSON.parse(JSON.stringify(obj)); // 报错:循环引用 

完整实现方案

function deepClone(target, map = new WeakMap()) {
  // 处理循环引用
  if (map.get(target)) return map.get(target);

  // 处理 Symbol 类型
  if (typeof target === 'symbol') return Symbol.for(target.description);

  // 基础类型直接返回
  if (typeof target !== 'object' || target === null) {return target;}

  const cloneTarget = Array.isArray(target) ? [] : {};
  map.set(target, cloneTarget);

  // 处理 Map/Set 等特殊对象(篇幅限制省略)// 遍历属性
  Reflect.ownKeys(target).forEach(key => {cloneTarget[key] = deepClone(target[key], map);
  });

  return cloneTarget;
}

4. 数组扁平化与去重

递归扁平化

function flattenDeep(arr) {return arr.reduce((acc, val) => 
    Array.isArray(val) 
      ? acc.concat(flattenDeep(val)) 
      : acc.concat(val),
    []);
}

迭代方案(ES6)

function flatten(arr) {while (arr.some(Array.isArray)) {arr = [].concat(...arr);
  }
  return arr;
}

最优去重方案

const unique = arr => [...new Set(arr)];
// 或处理对象数组
const uniqueBy = (arr, key) => {const seen = new Map();
  return arr.filter(item => {const k = key ? item[key] : JSON.stringify(item);
    return seen.has(k) ? false : seen.set(k, true);
  });
};

生产环境注意事项

  1. 内存泄漏风险
  2. 防抖函数中注意清除定时器
  3. 深拷贝时 WeakMap 自动释放内存

  4. 极端 case 测试

  5. 空数组 / 超大数组处理
  6. 特殊类型如 NaN、Infinity
  7. 循环引用深度超过调用栈

  8. 性能监控

  9. 使用 console.time/performance API
  10. 避免在渲染关键路径执行复杂操作

延伸思考

  1. 如何实现支持取消的防抖函数?
  2. WeakMap 在深拷贝中相比 Map 有何优势?
  3. 对于超大规模数组,如何优化扁平化性能?

希望这些方案能帮助你在实际开发中写出更健壮的代码。记住,理解原理比记住实现更重要!

正文完
 0
评论(没有评论)