JS初级进阶:手写实现常用方法详解与实战
2025.09.19 12:48浏览量:0简介:本文聚焦JavaScript初级基础,通过手写实现常用方法(如数组扁平化、深拷贝、防抖节流等),帮助读者理解底层原理,提升编码能力。内容涵盖方法原理、代码实现及使用场景,适合初学者巩固基础。
JS初级基础之:手写一些常用的方法
在JavaScript的学习过程中,理解并掌握常用方法的手动实现是提升编程能力的关键。许多开发者习惯直接调用内置方法(如Array.prototype.map
、Object.assign
等),却忽略了其底层逻辑。本文将通过手写实现数组扁平化、深拷贝、防抖节流等经典方法,帮助读者深入理解JavaScript的核心机制。
一、数组扁平化:从嵌套到一维
1.1 递归实现
数组扁平化是指将多层嵌套的数组转换为一维数组。递归是最直观的实现方式:
function flatten(arr) {
let result = [];
for (let item of arr) {
if (Array.isArray(item)) {
result = result.concat(flatten(item)); // 递归展开
} else {
result.push(item);
}
}
return result;
}
// 示例:flatten([1, [2, [3, 4]]]) → [1, 2, 3, 4]
关键点:通过Array.isArray
判断元素是否为数组,递归调用flatten
并合并结果。
1.2 迭代实现(使用栈)
递归可能导致栈溢出,迭代实现更安全:
function flattenIterative(arr) {
const stack = [...arr];
const result = [];
while (stack.length) {
const next = stack.pop();
if (Array.isArray(next)) {
stack.push(...next); // 展开数组
} else {
result.push(next);
}
}
return result.reverse(); // 保持原始顺序
}
优势:避免递归深度限制,适合处理超长嵌套数组。
二、深拷贝:突破引用传递
2.1 基础版深拷贝
直接使用JSON.parse(JSON.stringify(obj))
存在局限性(无法处理函数、循环引用等),手动实现更灵活:
function deepClone(obj, hash = new WeakMap()) {
if (obj === null || typeof obj !== 'object') return obj;
// 处理循环引用
if (hash.has(obj)) return hash.get(obj);
const cloneObj = Array.isArray(obj) ? [] : {};
hash.set(obj, cloneObj); // 记录已拷贝对象
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
cloneObj[key] = deepClone(obj[key], hash); // 递归拷贝
}
}
return cloneObj;
}
核心逻辑:
- 使用
WeakMap
记录已拷贝对象,解决循环引用问题。 - 区分数组和普通对象,分别初始化空数组或空对象。
- 递归拷贝每个属性。
2.2 处理特殊对象
扩展基础版以支持Date
、RegExp
等特殊对象:
function deepCloneAdvanced(obj, hash = new WeakMap()) {
if (obj === null || typeof obj !== 'object') return obj;
// 处理Date和RegExp
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
// 其余逻辑同基础版
// ...
}
三、防抖与节流:性能优化利器
3.1 防抖(Debounce)
防抖的核心是“延迟执行,直到停止触发后一段时间再执行”:
function debounce(fn, delay) {
let timer = null;
return function(...args) {
if (timer) clearTimeout(timer); // 清除上一次定时器
timer = setTimeout(() => {
fn.apply(this, args); // 保持this和参数传递
}, delay);
};
}
// 示例:输入框实时搜索,避免频繁请求
应用场景:搜索框输入、窗口resize事件。
3.2 节流(Throttle)
节流的核心是“固定时间间隔执行一次”:
function throttle(fn, delay) {
let lastTime = 0;
return function(...args) {
const now = Date.now();
if (now - lastTime >= delay) {
fn.apply(this, args);
lastTime = now; // 更新上次执行时间
}
};
}
// 示例:滚动事件监听,避免频繁触发
时间戳版 vs 定时器版:
- 时间戳版(如上)在事件触发时立即执行第一次。
- 定时器版在延迟结束后执行第一次,适合需要“延迟后执行”的场景。
四、其他实用方法
4.1 柯里化(Currying)
将多参数函数转换为单参数函数序列:
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args); // 参数足够时执行
} else {
return function(...args2) {
return curried.apply(this, args.concat(args2)); // 继续柯里化
};
}
};
}
// 示例:
const sum = (a, b, c) => a + b + c;
const curriedSum = curry(sum);
console.log(curriedSum(1)(2)(3)); // 6
4.2 发布-订阅模式
实现事件监听与触发:
class EventEmitter {
constructor() {
this.events = {};
}
on(event, callback) {
if (!this.events[event]) this.events[event] = [];
this.events[event].push(callback);
}
emit(event, ...args) {
if (this.events[event]) {
this.events[event].forEach(cb => cb(...args));
}
}
}
// 示例:
const emitter = new EventEmitter();
emitter.on('click', (x, y) => console.log(`Clicked at (${x}, ${y})`));
emitter.emit('click', 10, 20); // 输出:Clicked at (10, 20)
五、学习建议
- 动手实践:将每个方法实现后,编写测试用例验证边界条件(如空数组、循环引用等)。
- 对比源码:参考MDN或开源库(如Lodash)的实现,理解优化点。
- 场景驱动:结合实际项目需求(如表单验证、性能优化),应用手写方法。
通过手写实现常用方法,不仅能加深对JavaScript语言特性的理解,还能提升解决复杂问题的能力。建议从简单方法(如数组扁平化)入手,逐步挑战更复杂的逻辑(如深拷贝)。
发表评论
登录后可评论,请前往 登录 或 注册