共计 2268 个字符,预计需要花费 6 分钟才能阅读完成。
JavaScript 高频算法实战:从两数之和到深拷贝的避坑指南
1. 两数之和:哈希表的妙用
问题背景
两数之和是算法面试中的经典问题,也是实际开发中常见的需求。比如在电商系统中,我们需要找到两个商品的价格之和等于某个优惠券面额的情况。

技术对比
- 暴力解法:双重循环,时间复杂度 O(n²),空间复杂度 O(1)
- 哈希表解法:单次遍历,时间复杂度 O(n),空间复杂度 O(n)
核心实现
- 创建一个空对象作为哈希表
- 遍历数组,计算目标值与当前元素的差值
- 检查差值是否存在于哈希表中
- 如果存在,返回两个索引;否则将当前元素存入哈希表
function twoSum(nums, target) {const map = {};
for (let i = 0; i < nums.length; i++) {const complement = target - nums[i];
if (map[complement] !== undefined) {return [map[complement], i];
}
map[nums[i]] = i;
}
return [];}
避坑指南
- 注意处理重复元素的情况
- 确保返回的索引顺序正确
2. 防抖函数:控制执行频率的艺术
问题背景
在搜索框输入、窗口 resize 等高频触发事件的场景中,防抖可以有效减少不必要的函数调用,提升性能。
技术对比
- 简单实现:直接使用 setTimeout,但可能丢失 this 和 arguments
- 完整实现:保留上下文,支持立即执行选项
核心实现
- 设置定时器
- 每次调用时清除之前的定时器
- 重新设置新的定时器
- 可选立即执行功能
function debounce(func, wait, immediate = false) {
let timeout;
return function() {
const context = this;
const args = arguments;
const later = function() {
timeout = null;
if (!immediate) func.apply(context, args);
};
const callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
}
性能考量
- 内存消耗:每个防抖函数实例会保持一个定时器引用
- 执行频率:确保 wait 时间合理设置
3. 深拷贝:破解循环引用难题
问题背景
在状态管理、数据持久化等场景中,我们需要创建对象的完全独立副本,避免修改原始数据。
技术对比
- JSON 方法:简单但无法处理函数、Symbol 和循环引用
- 递归实现:完整支持各种类型但性能较低
- WeakMap 优化:解决循环引用问题
核心实现
- 处理基本数据类型
- 处理数组和对象
- 使用 WeakMap 记录已拷贝对象
- 特殊处理 Date、RegExp 等内置对象
function deepClone(obj, hash = new WeakMap()) {if (obj === null || typeof obj !== 'object') {return obj;}
if (hash.has(obj)) {return hash.get(obj);
}
let clone;
if (obj instanceof Date) {clone = new Date(obj);
} else if (obj instanceof RegExp) {clone = new RegExp(obj);
} else {clone = Array.isArray(obj) ? [] : {};
hash.set(obj, clone);
for (let key in obj) {if (obj.hasOwnProperty(key)) {clone[key] = deepClone(obj[key], hash);
}
}
// 处理 Symbol 类型
const symbolKeys = Object.getOwnPropertySymbols(obj);
for (let symKey of symbolKeys) {clone[symKey] = deepClone(obj[symKey], hash);
}
}
return clone;
}
避坑指南
- 注意保持原型链的正确性
- 处理特殊对象类型时要小心
4. 数组扁平化与去重
问题背景
在数据处理和分析中,经常需要将多维数组展开并去除重复项。
技术对比
- concat+ 递归:简单但性能一般
- reduce 实现:代码简洁
- 扩展运算符:ES6 新特性,性能较好
核心实现
- 扁平化实现
- 去重实现
- 组合使用
// 扁平化
function flatten(arr) {return arr.reduce((acc, val) =>
Array.isArray(val) ? acc.concat(flatten(val)) : acc.concat(val), []);
}
// 去重
function unique(arr) {return [...new Set(arr)];
}
// 组合使用
const processArray = arr => unique(flatten(arr));
性能考量
- 大数据量时考虑使用迭代代替递归
- Set 的去重性能优于手动实现
实践建议
- 从简单实现开始,逐步添加优化
- 为关键算法编写单元测试
- 使用性能分析工具验证优化效果
延伸学习
- 节流 (throttle) 与防抖 (debounce) 的区别与应用场景
- WeakMap 和 WeakSet 的其他应用
- 更复杂的数据结构拷贝策略
- 算法时间复杂度的深入理解
希望这篇文章能帮助你在 JavaScript 开发中避开这些常见问题的坑,写出更高效、健壮的代码。记住,理解原理比记住实现更重要,多动手实践才能融会贯通。
正文完
发表至: 未分类
近两天内
