logo

JS初级进阶:手写实现常用方法详解与实战

作者:很菜不狗2025.09.19 12:48浏览量:0

简介:本文聚焦JavaScript初级基础,通过手写实现常用方法(如数组扁平化、深拷贝、防抖节流等),帮助读者理解底层原理,提升编码能力。内容涵盖方法原理、代码实现及使用场景,适合初学者巩固基础。

JS初级基础之:手写一些常用的方法

在JavaScript的学习过程中,理解并掌握常用方法的手动实现是提升编程能力的关键。许多开发者习惯直接调用内置方法(如Array.prototype.mapObject.assign等),却忽略了其底层逻辑。本文将通过手写实现数组扁平化、深拷贝、防抖节流等经典方法,帮助读者深入理解JavaScript的核心机制。

一、数组扁平化:从嵌套到一维

1.1 递归实现

数组扁平化是指将多层嵌套的数组转换为一维数组。递归是最直观的实现方式:

  1. function flatten(arr) {
  2. let result = [];
  3. for (let item of arr) {
  4. if (Array.isArray(item)) {
  5. result = result.concat(flatten(item)); // 递归展开
  6. } else {
  7. result.push(item);
  8. }
  9. }
  10. return result;
  11. }
  12. // 示例:flatten([1, [2, [3, 4]]]) → [1, 2, 3, 4]

关键点:通过Array.isArray判断元素是否为数组,递归调用flatten并合并结果。

1.2 迭代实现(使用栈)

递归可能导致栈溢出,迭代实现更安全

  1. function flattenIterative(arr) {
  2. const stack = [...arr];
  3. const result = [];
  4. while (stack.length) {
  5. const next = stack.pop();
  6. if (Array.isArray(next)) {
  7. stack.push(...next); // 展开数组
  8. } else {
  9. result.push(next);
  10. }
  11. }
  12. return result.reverse(); // 保持原始顺序
  13. }

优势:避免递归深度限制,适合处理超长嵌套数组。

二、深拷贝:突破引用传递

2.1 基础版深拷贝

直接使用JSON.parse(JSON.stringify(obj))存在局限性(无法处理函数、循环引用等),手动实现更灵活:

  1. function deepClone(obj, hash = new WeakMap()) {
  2. if (obj === null || typeof obj !== 'object') return obj;
  3. // 处理循环引用
  4. if (hash.has(obj)) return hash.get(obj);
  5. const cloneObj = Array.isArray(obj) ? [] : {};
  6. hash.set(obj, cloneObj); // 记录已拷贝对象
  7. for (let key in obj) {
  8. if (obj.hasOwnProperty(key)) {
  9. cloneObj[key] = deepClone(obj[key], hash); // 递归拷贝
  10. }
  11. }
  12. return cloneObj;
  13. }

核心逻辑

  • 使用WeakMap记录已拷贝对象,解决循环引用问题。
  • 区分数组和普通对象,分别初始化空数组或空对象。
  • 递归拷贝每个属性。

2.2 处理特殊对象

扩展基础版以支持DateRegExp等特殊对象:

  1. function deepCloneAdvanced(obj, hash = new WeakMap()) {
  2. if (obj === null || typeof obj !== 'object') return obj;
  3. // 处理Date和RegExp
  4. if (obj instanceof Date) return new Date(obj);
  5. if (obj instanceof RegExp) return new RegExp(obj);
  6. // 其余逻辑同基础版
  7. // ...
  8. }

三、防抖与节流:性能优化利器

3.1 防抖(Debounce)

防抖的核心是“延迟执行,直到停止触发后一段时间再执行”:

  1. function debounce(fn, delay) {
  2. let timer = null;
  3. return function(...args) {
  4. if (timer) clearTimeout(timer); // 清除上一次定时器
  5. timer = setTimeout(() => {
  6. fn.apply(this, args); // 保持this和参数传递
  7. }, delay);
  8. };
  9. }
  10. // 示例:输入框实时搜索,避免频繁请求

应用场景:搜索框输入、窗口resize事件。

3.2 节流(Throttle)

节流的核心是“固定时间间隔执行一次”:

  1. function throttle(fn, delay) {
  2. let lastTime = 0;
  3. return function(...args) {
  4. const now = Date.now();
  5. if (now - lastTime >= delay) {
  6. fn.apply(this, args);
  7. lastTime = now; // 更新上次执行时间
  8. }
  9. };
  10. }
  11. // 示例:滚动事件监听,避免频繁触发

时间戳版 vs 定时器版

  • 时间戳版(如上)在事件触发时立即执行第一次。
  • 定时器版在延迟结束后执行第一次,适合需要“延迟后执行”的场景。

四、其他实用方法

4.1 柯里化(Currying)

将多参数函数转换为单参数函数序列:

  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 = (a, b, c) => a + b + c;
  14. const curriedSum = curry(sum);
  15. console.log(curriedSum(1)(2)(3)); // 6

4.2 发布-订阅模式

实现事件监听与触发:

  1. class EventEmitter {
  2. constructor() {
  3. this.events = {};
  4. }
  5. on(event, callback) {
  6. if (!this.events[event]) this.events[event] = [];
  7. this.events[event].push(callback);
  8. }
  9. emit(event, ...args) {
  10. if (this.events[event]) {
  11. this.events[event].forEach(cb => cb(...args));
  12. }
  13. }
  14. }
  15. // 示例:
  16. const emitter = new EventEmitter();
  17. emitter.on('click', (x, y) => console.log(`Clicked at (${x}, ${y})`));
  18. emitter.emit('click', 10, 20); // 输出:Clicked at (10, 20)

五、学习建议

  1. 动手实践:将每个方法实现后,编写测试用例验证边界条件(如空数组、循环引用等)。
  2. 对比源码:参考MDN或开源库(如Lodash)的实现,理解优化点。
  3. 场景驱动:结合实际项目需求(如表单验证、性能优化),应用手写方法。

通过手写实现常用方法,不仅能加深对JavaScript语言特性的理解,还能提升解决复杂问题的能力。建议从简单方法(如数组扁平化)入手,逐步挑战更复杂的逻辑(如深拷贝)。

相关文章推荐

发表评论