当前位置: 首页 > 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.dtcms.com/a/298302.html

相关文章:

  • iOS 抓包工具有哪些?模块化功能解析与选型思路
  • 容器化环境下的服务器性能瓶颈与优化策略
  • ubuntu22.04.4锁定内核应对海光服务器升级内核无法启动问题
  • Qt Mysql linux驱动编译
  • Google AI Mode 解析:生成式搜索功能的核心机制与应用场景
  • PowerDesigner安装教程(附加安装包)PowerDesigner详细安装教程PowerDesigner 16.6 最新版安装教程
  • Nacos-服务注册,服务发现(一)
  • 【模型剪枝1】结构化剪枝论文学习笔记
  • 如何理解SpringBoot starters的自动装配
  • 地下隧道管廊结构健康监测系统 测点的布设及设备选型
  • 1 51单片机-C51语法
  • 4麦 360度定位
  • docker搭建ray集群
  • SAP-PP-MRPLIST
  • MybatisPlus-17.扩展功能-JSON处理器
  • 【57】MFC入门到精通——MFC 多线程编程总结
  • 【lucene】自定义tokenfilter 自带payload
  • String类常用方法练习
  • synchronized锁普通方法和锁静态方法有什么区别?
  • RPG66.制作死亡画面(二):实现按钮逻辑
  • 毕业论文参考文档(免费)—DHT11 温湿度传感器的硬件与软件系统设计
  • Pydantic 配置管理
  • vehicle_template | vehicle_seat_addon
  • 功能安全实战系列14-英飞凌TC3xx MBIST检测理论篇
  • 【大模型关键技术】Transformer 前沿发展
  • 模糊匹配fuzzywuzzy
  • c++文件操作详解
  • ubuntu安装cuda版本问题
  • 平时开发中使用 Redis 分布式锁,有哪些需要注意的问题?
  • Mysql 日志 binlog redolog