2026-01-19 10:21:18 +08:00

8.2 KiB
Raw Permalink Blame History

JS 基础与手写实现

1. 手写 Promise

场景:理解异步编程核心机制,处理 Ajax 请求、文件读取、定时器等异步操作的链式调用和错误处理。 解决:回调地狱问题,提供优雅的异步流程控制。

class MyPromise {
  constructor(executor) {
    this.state = 'pending';       // 初始状态为 pending
    this.value = undefined;       // 存储成功值或失败原因
    this.callbacks = [];          // 存储 then 注册的回调函数
    
    // 成功时调用,将状态改为 fulfilled
    const resolve = (value) => {
      if (this.state !== 'pending') return;  // 状态只能改变一次
      this.state = 'fulfilled';
      this.value = value;
      this.callbacks.forEach(cb => cb.onFulfilled(value));  // 执行所有成功回调
    };
    
    // 失败时调用,将状态改为 rejected
    const reject = (reason) => {
      if (this.state !== 'pending') return;
      this.state = 'rejected';
      this.value = reason;
      this.callbacks.forEach(cb => cb.onRejected(reason));  // 执行所有失败回调
    };
    
    // 立即执行 executor捕获异常则 reject
    try { executor(resolve, reject); } catch (e) { reject(e); }
  }
  
  then(onFulfilled, onRejected) {
    // 返回新 Promise 实现链式调用
    return new MyPromise((resolve, reject) => {
      const handle = (callback, fallback) => {
        try {
          const result = (callback || fallback)(this.value);  // 执行回调
          // 如果返回值是 Promise等待其完成
          result instanceof MyPromise ? result.then(resolve, reject) : resolve(result);
        } catch (e) { reject(e); }  // 回调报错则 reject
      };
      
      // 根据当前状态决定立即执行还是存储回调
      if (this.state === 'fulfilled') handle(onFulfilled, v => v);
      else if (this.state === 'rejected') handle(onRejected, e => { throw e; });
      else this.callbacks.push({  // pending 状态先存储回调
        onFulfilled: () => handle(onFulfilled, v => v),
        onRejected: () => handle(onRejected, e => { throw e; })
      });
    });
  }
}

2. Promise.all / Promise.race

场景Promise.all 用于并行请求多个接口后统一处理(如同时加载用户信息和订单列表);Promise.race 用于请求超时控制、竞速取最快响应。 解决:多异步任务协调与超时兜底问题。

// Promise.all - 所有成功才成功,一个失败则立即失败
Promise.myAll = (promises) => {
  return new Promise((resolve, reject) => {
    const results = [];    // 存储所有结果
    let count = 0;         // 记录完成数量
    promises.forEach((p, i) => {
      Promise.resolve(p).then(val => {
        results[i] = val;  // 按索引存储,保证顺序
        if (++count === promises.length) resolve(results);  // 全部完成则 resolve
      }, reject);  // 任一失败立即 reject
    });
  });
};

// Promise.race - 第一个完成就返回(无论成功失败)
Promise.myRace = (promises) => {
  return new Promise((resolve, reject) => {
    // 第一个 resolve/reject 的结果会被采纳,后续的会被忽略
    promises.forEach(p => Promise.resolve(p).then(resolve, reject));
  });
};

3. 防抖与节流

场景:防抖用于搜索框输入(停止输入后才请求)、窗口 resize 结束后计算布局;节流用于滚动事件监听、按钮防重复点击。 解决:高频事件触发导致的性能问题和重复请求。

// 防抖:停止触发后才执行(适合搜索框输入)
function debounce(fn, delay) {
  let timer = null;  // 保存定时器 ID
  return function(...args) {
    clearTimeout(timer);  // 每次触发都清除上一个定时器
    timer = setTimeout(() => fn.apply(this, args), delay);  // 重新设定定时器
  };
}

// 节流:固定间隔执行(适合滚动事件)
function throttle(fn, delay) {
  let last = 0;  // 记录上次执行时间
  return function(...args) {
    const now = Date.now();
    if (now - last >= delay) {  // 距离上次执行超过 delay 才执行
      last = now;
      fn.apply(this, args);
    }
  };
}

4. 深拷贝(处理循环引用)

场景Redux/Vuex 状态管理中复制 state、表单数据备份与重置、避免修改原始数据造成副作用。 解决:引用类型浅拷贝导致的数据污染问题,特别是循环引用场景。

function deepClone(obj, map = new WeakMap()) {
  // 基本类型和 null 直接返回
  if (obj === null || typeof obj !== 'object') return obj;
  // 特殊对象处理
  if (obj instanceof Date) return new Date(obj);
  if (obj instanceof RegExp) return new RegExp(obj);
  
  // 处理循环引用:如果已拷贝过,直接返回缓存
  if (map.has(obj)) return map.get(obj);
  
  // 创建新对象/数组
  const clone = Array.isArray(obj) ? [] : {};
  map.set(obj, clone);  // 先存入 map防止循环引用
  
  // 递归拷贝每个属性
  for (const key in obj) {
    if (obj.hasOwnProperty(key)) {  // 只拷贝自身属性
      clone[key] = deepClone(obj[key], map);
    }
  }
  return clone;
}

5. 函数柯里化

场景:参数复用(如日志函数固定模块名)、延迟执行、函数式编程中的组合与管道操作。 解决:减少重复传参,提高函数复用性和可读性。

function curry(fn) {
  return function curried(...args) {
    // 如果参数数量足够,直接执行原函数
    if (args.length >= fn.length) {
      return fn.apply(this, args);
    }
    // 参数不够,返回新函数继续收集参数
    return (...nextArgs) => curried(...args, ...nextArgs);
  };
}

// 使用示例
const add = (a, b, c) => a + b + c;
const curriedAdd = curry(add);
curriedAdd(1)(2)(3);  // 6 - 每次传一个参数
curriedAdd(1, 2)(3);  // 6 - 也可以一次传多个

6. call / apply / bind 实现

场景改变函数执行上下文如借用数组方法处理类数组、React 类组件中绑定事件处理函数的 this。 解决this 指向问题,实现函数借用和预设参数。

// call - 立即执行,参数逐个传入
Function.prototype.myCall = function(ctx, ...args) {
  ctx = ctx || window;         // ctx 为 null/undefined 时指向 window
  const key = Symbol();        // 用 Symbol 避免属性名冲突
  ctx[key] = this;             // 将函数作为 ctx 的方法
  const result = ctx[key](...args);  // 调用,此时 this 指向 ctx
  delete ctx[key];             // 清理临时属性
  return result;
};

// apply - 立即执行,参数以数组传入
Function.prototype.myApply = function(ctx, args = []) {
  ctx = ctx || window;
  const key = Symbol();
  ctx[key] = this;
  const result = ctx[key](...args);  // 展开数组参数
  delete ctx[key];
  return result;
};

// bind - 返回新函数,不立即执行
Function.prototype.myBind = function(ctx, ...args) {
  const fn = this;  // 保存原函数
  return function(...newArgs) {
    // 合并预设参数和新参数
    return fn.apply(ctx, [...args, ...newArgs]);
  };
};

7. 事件总线 EventEmitter

场景:组件间通信(如 Vue 的 EventBus、插件系统、微前端应用间消息传递、解耦模块依赖。 解决:发布-订阅模式实现松耦合的事件驱动架构。

class EventEmitter {
  constructor() {
    this.events = {};  // 存储事件名 -> 监听器数组的映射
  }
  
  // 订阅事件
  on(event, listener) {
    (this.events[event] ||= []).push(listener);  // 添加监听器
    return this;  // 返回 this 支持链式调用
  }
  
  // 取消订阅
  off(event, listener) {
    if (this.events[event]) {
      // 过滤掉指定的监听器
      this.events[event] = this.events[event].filter(l => l !== listener);
    }
    return this;
  }
  
  // 触发事件
  emit(event, ...args) {
    // 执行该事件的所有监听器
    (this.events[event] || []).forEach(listener => listener(...args));
    return this;
  }
  
  // 一次性订阅,触发后自动取消
  once(event, listener) {
    const wrapper = (...args) => {
      listener(...args);       // 执行原始监听器
      this.off(event, wrapper);  // 立即取消订阅
    };
    return this.on(event, wrapper);
  }
}