当前位置: 首页 > news >正文

前端核心进阶:从原理到手写Promise、防抖节流与深拷贝

“在面试和实际开发中,我多次被Promise的实现原理、防抖节流的性能优化和深拷贝的边界条件所困扰。本文通过手写实现这三个核心功能,帮助大家从根源上理解JavaScript的异步控制、性能优化和数据处理的底层逻辑。”

一、手写Promise实现

1. Promise基本概念

Promise是异步编程的一种解决方案,比传统的回调函数更合理和强大。它有三种状态:

  • pending(进行中)
  • fulfilled(已成功)
  • rejected(已失败)

2. Promise基础实现

class MyPromise {constructor(executor) {// 初始状态为pendingthis.state = 'pending';// 存储成功的结果值this.value = undefined;// 存储失败的原因this.reason = undefined;// 成功回调队列(用于处理异步情况)this.onFulfilledCallbacks = [];// 失败回调队列(用于处理异步情况)this.onRejectedCallbacks = [];// resolve函数:将状态从pending变为fulfilledconst resolve = (value) => {// 只有pending状态可以改变if (this.state === 'pending') {this.state = 'fulfilled';this.value = value;// 执行所有成功回调this.onFulfilledCallbacks.forEach(fn => fn());}};// reject函数:将状态从pending变为rejectedconst reject = (reason) => {// 只有pending状态可以改变if (this.state === 'pending') {this.state = 'rejected';this.reason = reason;// 执行所有失败回调this.onRejectedCallbacks.forEach(fn => fn());}};// 立即执行executor函数try {executor(resolve, reject);} catch (err) {// 如果executor执行出错,直接rejectreject(err);}}// then方法:注册回调函数then(onFulfilled, onRejected) {// 参数可选处理:如果不是函数,则创建一个默认函数onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };// 返回一个新的Promise,实现链式调用const promise2 = new MyPromise((resolve, reject) => {// 如果当前状态已经是fulfilledif (this.state === 'fulfilled') {// 使用setTimeout确保异步执行setTimeout(() => {try {// 执行成功回调const x = onFulfilled(this.value);// 处理返回值(可能是普通值或Promise)resolvePromise(promise2, x, resolve, reject);} catch (e) {// 如果回调执行出错,直接rejectreject(e);}}, 0);} // 如果当前状态已经是rejectedelse if (this.state === 'rejected') {setTimeout(() => {try {// 执行失败回调const x = onRejected(this.reason);// 处理返回值resolvePromise(promise2, x, resolve, reject);} catch (e) {reject(e);}}, 0);} // 如果当前状态还是pending(异步情况)else if (this.state === 'pending') {// 将成功回调加入队列this.onFulfilledCallbacks.push(() => {setTimeout(() => {try {const x = onFulfilled(this.value);resolvePromise(promise2, x, resolve, reject);} catch (e) {reject(e);}}, 0);});// 将失败回调加入队列this.onRejectedCallbacks.push(() => {setTimeout(() => {try {const x = onRejected(this.reason);resolvePromise(promise2, x, resolve, reject);} catch (e) {reject(e);}}, 0);});}});return promise2;}
}// 处理then方法返回值的函数
function resolvePromise(promise2, x, resolve, reject) {// 如果返回的是同一个Promise,报错if (promise2 === x) {return reject(new TypeError('Chaining cycle detected for promise'));}// 防止重复调用let called = false;// 如果x是对象或函数(可能是Promise)if (x !== null && (typeof x === 'object' || typeof x === 'function')) {try {// 获取then方法const then = x.then;// 如果then是函数,则认为x是Promiseif (typeof then === 'function') {then.call(x,// resolve回调y => {if (called) return;called = true;// 递归解析,直到返回值不是PromiseresolvePromise(promise2, y, resolve, reject);},// reject回调r => {if (called) return;called = true;reject(r);});} else {// 普通对象,直接resolveresolve(x);}} catch (e) {if (called) return;called = true;reject(e);}} else {// 普通值,直接resolveresolve(x);}
}

二、防抖(debounce)与节流(throttle)

1. 防抖(debounce)实现

/*** 防抖函数* @param {Function} fn 要执行的函数* @param {number} delay 延迟时间(ms)* @param {boolean} immediate 是否立即执行* @return {Function} 返回防抖后的函数*/
function debounce(fn, delay, immediate = false) {let timer = null;let isInvoke = false; // 是否已经立即执行过return function(...args) {const context = this; // 保存this指向// 如果设置立即执行且还未执行过if (immediate && !isInvoke) {fn.apply(context, args); // 立即执行isInvoke = true;} else {clearTimeout(timer); // 清除之前的定时器}// 设置新的定时器timer = setTimeout(() => {// 如果不是立即执行模式,则执行函数if (!immediate) {fn.apply(context, args);}// 重置状态,允许下次立即执行isInvoke = false;}, delay);};
}// 使用示例
const input = document.getElementById('search-input');
input.addEventListener('input', debounce(function(e) {console.log('搜索:', e.target.value);// 这里可以执行实际的搜索逻辑
}, 500, true)); // 500ms防抖,立即执行第一次

防抖原理说明

  1. 当事件触发时,不立即执行函数,而是设置一个定时器
  2. 如果在延迟时间内事件再次触发,则清除之前的定时器并重新设置
  3. 只有当事件停止触发且延迟时间到达后,才真正执行函数
  4. immediate参数控制是否在第一次触发时立即执行

2. 节流(throttle)实现(带注释)

/*** 节流函数* @param {Function} fn 要执行的函数* @param {number} interval 时间间隔(ms)* @param {Object} options 配置选项*        leading: 是否立即执行第一次*        trailing: 是否在间隔结束后执行最后一次* @return {Function} 返回节流后的函数*/
function throttle(fn, interval, options = { leading: true, trailing: true }) {let lastTime = 0; // 上次执行时间let timer = null; // 定时器return function(...args) {const context = this; // 保存thisconst nowTime = Date.now(); // 当前时间// 如果不需要立即执行且是第一次触发if (!lastTime && !options.leading) {lastTime = nowTime; // 将lastTime设为当前时间}// 计算剩余时间const remainTime = interval - (nowTime - lastTime);if (remainTime <= 0) {// 如果剩余时间<=0,应该执行函数if (timer) {clearTimeout(timer);timer = null;}fn.apply(context, args); // 执行函数lastTime = nowTime; // 更新上次执行时间} else if (options.trailing && !timer) {// 如果需要执行最后一次且没有定时器timer = setTimeout(() => {fn.apply(context, args);// 如果不需要立即执行,lastTime设为0,否则设为当前时间lastTime = !options.leading ? 0 : Date.now();timer = null;}, remainTime);}};
}// 使用示例
window.addEventListener('scroll', throttle(function() {console.log('滚动事件处理');// 这里可以执行实际的滚动处理逻辑
}, 1000, { leading: true, trailing: true }));

节流原理说明

  1. 节流函数会按照固定的时间间隔执行函数
  2. 有两种实现方式:
    • 时间戳方式:通过比较当前时间和上次执行时间
    • 定时器方式:通过设置定时器
  3. leadingtrailing选项可以控制:
    • leading: 是否在节流开始时立即执行
    • trailing: 是否在节流结束后再执行一次

3. 防抖与节流对比表格

特性防抖(debounce)节流(throttle)
原理事件触发后延迟执行,期间重复触发则重新计时固定时间内只执行一次
适用场景输入框搜索联想、窗口resize滚动加载、鼠标移动、频繁点击
执行频率停止触发后才执行一次固定频率执行
实现方式setTimeout时间戳或setTimeout
效果将多次密集触发合并为一次执行稀释执行频率,保持一定节奏执行

三、深拷贝(deep clone)实现

1. 基础深拷贝实现(带注释)

/*** 深拷贝函数* @param {*} target 要拷贝的目标* @param {WeakMap} map 用于解决循环引用的WeakMap* @return {*} 返回深拷贝后的对象*/
function deepClone(target, map = new WeakMap()) {// 1. 处理基本数据类型和nullif (typeof target !== 'object' || target === null) {return target;}// 2. 解决循环引用问题if (map.get(target)) {return map.get(target);}// 3. 处理特殊对象类型// 3.1 处理Date对象if (target instanceof Date) {return new Date(target);}// 3.2 处理RegExp对象if (target instanceof RegExp) {return new RegExp(target);}// 4. 创建新对象/数组const cloneTarget = Array.isArray(target) ? [] : {};// 将target和cloneTarget存入map,解决循环引用map.set(target, cloneTarget);// 5. 处理Symbol属性const symbolKeys = Object.getOwnPropertySymbols(target);if (symbolKeys.length) {symbolKeys.forEach(symKey => {cloneTarget[symKey] = deepClone(target[symKey], map);});}// 6. 递归拷贝普通属性for (const key in target) {if (target.hasOwnProperty(key)) {cloneTarget[key] = deepClone(target[key], map);}}return cloneTarget;
}// 使用示例
const obj = {a: 1,b: { c: 2 },d: new Date(),e: /regexp/,[Symbol('key')]: 'symbol value'
};
obj.self = obj; // 循环引用const clonedObj = deepClone(obj);
console.log(clonedObj);

2. 处理更多数据类型的深拷贝(带注释)

function deepClone(target, map = new WeakMap()) {// 1. 处理基本数据类型和nullif (typeof target !== 'object' || target === null) {return target;}// 2. 解决循环引用问题if (map.get(target)) {return map.get(target);}// 3. 获取构造函数const constructor = target.constructor;// 4. 处理特殊对象类型// 4.1 处理Functionif (constructor === Function) {// 通过函数字符串创建新函数return new Function('return ' + target.toString())();}// 4.2 处理RegExpif (constructor === RegExp) {return new RegExp(target);}// 4.3 处理Dateif (constructor === Date) {return new Date(target);}// 4.4 处理Mapif (constructor === Map) {const newMap = new Map();map.set(target, newMap);// 遍历原Map,递归拷贝每一项target.forEach((value, key) => {newMap.set(deepClone(key, map), deepClone(value, map));});return newMap;}// 4.5 处理Setif (constructor === Set) {const newSet = new Set();map.set(target, newSet);// 遍历原Set,递归拷贝每一项target.forEach(value => {newSet.add(deepClone(value, map));});return newSet;}// 5. 处理数组和普通对象const cloneTarget = new constructor();map.set(target, cloneTarget);// 6. 处理Symbol属性const symbolKeys = Object.getOwnPropertySymbols(target);if (symbolKeys.length) {symbolKeys.forEach(symKey => {cloneTarget[symKey] = deepClone(target[symKey], map);});}// 7. 递归拷贝普通属性for (const key in target) {if (target.hasOwnProperty(key)) {cloneTarget[key] = deepClone(target[key], map);}}return cloneTarget;
}// 使用示例
const complexObj = {arr: [1, 2, { a: 3 }],date: new Date(),reg: /abc/gi,map: new Map([['key1', 'value1'], ['key2', { b: 2 }]]),set: new Set([1, 2, 3]),func: function(a, b) { return a + b; },[Symbol('sym')]: 'symbol value'
};
complexObj.self = complexObj; // 循环引用const clonedComplexObj = deepClone(complexObj);
console.log(clonedComplexObj);

3. 深拷贝关键点解释

  1. 基本数据类型处理

    • 直接返回,因为它们是不可变的
  2. 循环引用处理

    • 使用WeakMap存储已拷贝对象
    • 遇到相同引用直接返回存储的拷贝
  3. 特殊对象处理

    • Date: 创建新的Date对象
    • RegExp: 创建新的RegExp对象
    • Map/Set: 递归拷贝每一项
    • Function: 通过函数字符串重新创建
  4. Symbol属性处理

    • 使用Object.getOwnPropertySymbols()获取Symbol属性
    • 递归拷贝每个Symbol属性
  5. 普通对象和数组处理

    • 创建新的对象或数组
    • 递归拷贝每个属性
  6. 性能考虑

    • 对于大型对象,深拷贝可能消耗较多内存和CPU
    • 实际项目中可以考虑使用immutable.js等库

4. 深拷贝与浅拷贝对比

特性深拷贝(deep clone)浅拷贝(shallow clone)
拷贝层级所有层级仅第一层
引用类型创建新的引用复制引用
修改影响不影响原对象修改拷贝对象会影响原对象
实现方式递归或序列化/反序列化Object.assign()或扩展运算符
性能较低(需要递归处理)较高
适用场景需要完全独立的对象副本只需要浅层拷贝,性能要求高

四、总结

  1. 手写Promise

    • 理解Promise的状态机制
    • 掌握then方法的链式调用原理
    • 学会处理异步和同步的不同情况
  2. 防抖与节流

    • 防抖适合高频触发但只需最后一次结果的场景
    • 节流适合需要均匀执行高频触发的场景
    • 两者都可以有效优化性能
  3. 深拷贝

    • 理解JavaScript中的值类型和引用类型
    • 掌握循环引用的处理方法
    • 学会处理各种特殊对象类型

这些知识点是前端进阶的重要内容,理解它们的实现原理可以帮助你写出更高效、更健壮的代码。建议在学习时:

  1. 先理解原理和概念
  2. 然后手动实现代码
  3. 最后思考各种边界情况和优化方案
http://www.lryc.cn/news/599960.html

相关文章:

  • ERNIE-4.5-0.3B 实战指南:文心一言 4.5 开源模型的轻量化部署与效能跃升
  • Agentic RAG理解和简易实现
  • 计算机体系结构中的中断服务程序ISR是什么?
  • haproxy集群
  • Java测试题(上)
  • Spring之【Bean后置处理器】
  • sam2环境安装
  • JAVA语法糖
  • JAVA同城服务家政服务家政派单系统源码微信小程序+微信公众号+APP+H5
  • 探索 Sui 上 BTCfi 的各类资产
  • 在DolphinScheduler执行Python问题小记
  • DP4871音频放大芯片3W功率单通道AB类立体声/音频放大器
  • 3N90-ASEMI电源管理领域专用3N90
  • 【前端】JavaScript文件压缩指南
  • 文件包含学习总结
  • reflections:Java非常好用的反射工具包
  • 【linux】Haproxy七层代理
  • 如何理解泊松分布
  • 在 IntelliJ IDEA 中打开这个用于设置 Git 用户名(Name)和邮箱(Email)的特定弹窗
  • JAVA知识点(三):Spring与ORM框架
  • 【RDMA】Adapters PRM Mellanox Adapters Programmer’s Reference mellanox网卡编程手册0.52
  • Linux库——库的制作和原理(1)_回顾动静态库、制作使用库
  • 上位机程序开发基础介绍
  • OpenCV结合深度学习进行图像分类
  • 练习实践-基础设施-文件共享-windows和linux之间的文件共享-smb服务搭建
  • 解决angular与jetty websocket 每30s自动断连的问题
  • 从kHz到GHz:晶振频率范围如何决定其应用场景
  • streamyfin(世博会)android 编译
  • 告别虚函数性能焦虑:深入剖析C++多态的现代设计模式
  • 萤石云替代产品摄像头方案萤石云不支持TCP本地连接-东方仙盟