js中字符串json数据的处理【匹配url、邮箱、电话版本号千位分割判断回问】-CSDN博客手写实现js类/方法【改变this、map、Object、发布订阅、promise】-CSDN博客目录防抖、节流防抖debounce以操作为准触发重置计时无操作time后才可能执行节流throttle以固定时间间隔为准到点执行应用示例*开发import _ from lodash;参数maxwait结束时间应用选择深拷贝A.JSON.parse() 和 JSON.stringify()B.遍历递归arr instanceof Array ? [] : {}C.改进版Symbol属性Object.getOwnPropertySymbols不可枚举属性Reflect.ownKeys循环引用WeakMapsetTimeout()倒计时js递归调用reactuseEffect((){},[cnt])requestAnimationFrame代替settimeout更精准setTimeout、setInterval最短时长为4mssetTimeout固定时长后执行setInterval间隔固定时间重复执行setTimeout模拟实现setIntervalsetInterval模拟实现setTimeout数组去重[...new Set(arr)]arr.filter((value, index, self) self.indexOf(value) index)浮点数不精确性原因部分十进制小数在二进制中无限循环解决面试toFixed精度业务第三方库bignumber.js处理大数字和高精度运算Objectnew Fn[...arguments].shift()*寄生组合式继承callcreateconstructorObject.defineProperty(obj, prop, descriptor)手撕时间有限核心目的是考察对核心概念的理解和基本的编程能力因此防抖节流手写考察基础版leading/trailing思路讨论防抖、节流防抖回城受到攻击/重新点击就重新打断计满 10 秒只看最后结果。节流技能 CD技能冷却 5 秒冷却期内怎么按都没用按固定频率响应。防抖debounce以操作为准触发重置计时无操作time后才可能执行箭头函数没有自己的arguments但能访问外层的可能导致意想不到的引用。在支持 ES6 的环境中箭头函数内可以使用Rest参数...args来代替arguments真正的数组args是数组可以直接用数组方法无需转换在闭包中传递参数时最安全可靠的方法是使用...args接收再用apply传递。*time改成loadsh中的waitfunction debounce(fn, delay) { let timer null; return function(...args) { clearTimeout(timer); // 只要触发就清除重置 timer setTimeout(() { timer null; // 养成清理习惯 fn.apply(this, args); }, delay); }; } // 支持 leading/trailing function debounce(fn, delay, option {}) { const { leading false, trailing true } option; let timer null; return function(...args) { const callNow leading !timer; // 是否满足“首次立即执行” if (timer) clearTimeout(timer); timer setTimeout(() { timer null;// 养成清理习惯 // 只有开启了 trailing且这次不是首部刚执行过的单次触发才补一次尾部执行 if (trailing !callNow) { fn.apply(this, args); } }, delay); if (callNow) { fn.apply(this, args); } }; }// 旧版 node可能会// 内存泄漏未正确清理的定时器引用// 不可预测的定时器行为可能干扰其他正在运行的定时器clearTimeout(undefined);setTimeout 回调内部如果不设置为 null- timer 变量会一直持有对已完成的定时器的引用- 虽然定时器已经执行完成但引用仍然存在- 在长时间运行的应用程序中可能积累不必要的引用调用 clearTimeout(timer) - ❌ 不必要的 定时器已经完成- ❌ 可能有害的 如果 timer 指向新的定时器- ❌ 性能浪费 额外的函数调用开发if (this.scrollToBottomTimer) { window.clearTimeout(this.scrollToBottomTimer); this.scrollToBottomTimer null; } this.scrollToBottomTimer setTimeout(() { // console.log(onKeyboardStatus 滚动); this.scrollToBottom({ smooth: true }); this.scrollToBottomTimer null; }, 200);节流throttle以固定时间间隔为准到点执行*time改成loadsh中的waitfunction throttle(fn, delay) { let last 0; // 保存上次执行的时间戳 return function(...args) { const now Date.now(); if (now - last delay) { last now; // 更新上次执行时间 fn.apply(this, args); } }; } // 支持 leading/trailing function throttle(fun,time,option){ const { leading true, trailing true } option || {}; let lastInovokeTime 0, timer; return function(...args){ const curTime Date.now(); if(curTime - lastInovokeTime time){ if(leading){ lastInovokeTime curTime; fun.apply(this,args); } }else if( trailing !timer){ timer setTimeout((){ timer null; // 养成清理习惯 lastInovokeTime curTime; fun.apply(this,args); },time - (curTime - lastInovokeTime)) } } }ES6 出现...args特性...args (Rest 参数)arguments数据类型真正的 Array可以直接使用.map(),.forEach()等伪数组 / 类数组没有数组的内置方法箭头函数支持不支持箭头函数中没有arguments灵活性可以只收集部分剩余参数例如function(a, b, ...rest)始终包含所有参数应用示例// 模拟搜索请求 function search(keyword) { console.log(发送 API 请求搜索:, keyword); } // 1. 生成防抖包裹后的新函数延迟 500ms const debouncedSearch debounce(search, 500); // 2. 监听输入框事件 const inputEl document.querySelector(#search-input); inputEl.addEventListener(input, function(e) { // 连续打字时不会触发请求只有停顿 500ms 后才会打印一次 debouncedSearch(e.target.value); }); function handleScroll() { console.log(当前滚动距离:, window.scrollY); } // 每 200ms 最多执行一次 const throttledScroll throttle(handleScroll, 200); // 快速滚动页面时控制台会按每 200ms 匀速输出而不是一瞬间输出上百条 window.addEventListener(scroll, throttledScroll);*开发import _ from lodash;$ npm i -g npm$ npm i --save lodashimport _ from lodash; downLoad: _.debounce( function() { OrgServer.getModelUrl().then((res: any) { window.open(res); }); }, 500, { leading: true, trailing: false }, )参数func:触发函数wait?:延迟的毫秒数。可选参数。默认0。options?:更改方法行为的选项对象。可选参数。options以下参数均可选leading?:首次调用是否立即执行。maxWait?:它是函数被调用之前允许延迟的最大时间。trailing?:等待时间结束后的最后一次调用是否执行。配置为leading和trailing若都为false则永不执行防抖在操作time无操作则结束节流在间隔无操作则结束配置类型0:00说汉堡0:30说薯条0:45说可乐1:00时1:45时最终订单❌❌防抖计时1分钟重置再计1分钟重置再计1分钟❌ 不来❌ 不来不下单❌✅计时1分钟重置再计1分钟重置再计1分钟❌ 不来⚡ 下单可乐可乐✅❌⚡ 下单汉堡忽略忽略❌ 不来❌ 不来汉堡✅✅⚡ 下单汉堡重置计时重置计时❌ 不来⚡ 下单可乐汉堡可乐❌❌节流等1:00边界更新为薯条更新为可乐❌ 不下单❌ 不下单不下单❌✅等1:00边界更新为薯条更新为可乐⚡ 下单可乐⚡ 等2:00边界可乐✅❌⚡ 下单汉堡忽略忽略❌ 不下单❌ 不下单汉堡✅✅⚡ 下单汉堡更新为薯条更新为可乐⚡ 下单可乐⚡ 等2:00边界汉堡可乐防抖/节流执行时机不一致有最短执行时间/允许一直被打断首/尾/首尾执行需要即时/准确吗防抖关注最终状态一个操作重复多次/短时【点击/短时打字】节流关注均匀响应瞬时完成多个不同的操作【滚动/拖拽/游戏等】加粗为默认值loadashleadingtrailing执行行为应用防抖 (Debounce)falsetrue以wait为准判断停止停止后执行最后一次【保存maxwait】准可能长时不执行truefalse立即执行第一次忽略后续【按钮连点】快不变truetrue立即执行第一次 停止后执行最后一次【搜索】重复没关系快准可能变节流 (Throttle)truetrue固定间隔执行首尾都执行【滚动事件】立即响应最终处理truefalse固定间隔只执行第一次【游戏控制maxwait】立即响应固定频率falsetrue固定间隔只执行最后一次【窗口resize】最终状态处理搜索axios取消请求cancelToken和AbortController_axios.canceltokenimport axios from axios //去除首尾空格 let searchKey queryString.trim() searchKeysearchKey.substring(0, 50) if (searchKey) { if(this.searchTimer){ clearTimeout(this.searchTimer); if(this.source){ this.source.cancel(search-cancel); } } this.searchTimer setTimeout(() { this.CancelToken axios.CancelToken this.source this.CancelToken.source() ...请求 },200) }maxwait解决了“防抖可能导致长时间无反馈”的缺陷特性防抖 maxWait节流 maxWait要解决的问题用户长时间不停止操作导致始终没有反馈。(等待太久)两次触发间隔意外变长导致下一次执行延迟太久。(响应太慢)maxWait的作用为“等待停止”设置最长时限超时就强制执行一次。为“执行间隔”设置上限超过此间隔的触发可以立即执行。行为纠正从“无限等待停止” → “最多等X毫秒”从“固定时间点执行” → “最多延迟X毫秒执行”形象比喻无限耐心 → 有限耐心老师等学生提问“我给你3分钟思考时间到还没人问我就直接讲下一题。”死板的闹钟 → 聪明的闹钟原闹钟只在7:00响。现如果6:50醒了可以立即起床不必硬等到7:00。防抖maxWait是“耐心等待”的极限。它让防抖从“无限期等待用户停止”变为“我只等你这么久时间一到必须处理”。节流maxWait是“固定频率”的打破。它让节流从“必须等到下一个固定时间点”变为“如果长时间没有触发再次触发则立即执行”。特性防抖 maxWait节流 maxWait默认行为无maxWait用户停止操作后等待固定时间再执行。无maxWait严格按照固定间隔执行无论期间触发多密集。核心需求既要响应最终状态又要在长时间操作中提供必要反馈。既要控制最低执行频率又要防止在极端情况下响应过于迟缓。添加maxWait的效果设置“最后期限”。如果用户持续操作超过maxWait时间则强制执行一次然后重新开始防抖计时。设置“最大间隔”。如果两次触发的间隔超过了maxWait则立即执行而非等待下一个节流周期。形象比喻打字自动保存智能版1. 你停止打字3秒后自动保存。基础防抖2. 但你写论文连续打了30秒不停系统怕你忘记保存会强制在第30秒保存一次。maxWait: 30000效果**最终保存 防止长时间未保存。游戏技能冷却灵活版1. 技能固定每2秒可放一次。基础节流2. 但如果你有2.1秒没按技能键这时突然需要释放无需再等满2秒可以立即释放。maxWait: 2000效果**控制频率上限 避免操作延迟感。代码配置示例_.debounce(saveDraft, 3000, {maxWait: 10000, trailing: true})// 停止输入3秒后保存但最多10秒必存一次。_.throttle(renderFrame, 16, {maxWait: 100})// 试图每16ms(60fps)渲染但如果卡顿导致100ms都没渲染必须立即强制渲染一帧。经典应用场景1.实时协作编辑器光标/内容同步2.带有“正在输入…”提示的聊天框3.需要定期心跳的长时间数据流处理1.游戏引擎渲染/逻辑更新2.监控仪表盘的数据流渲染3.对响应延迟敏感的高频UI更新结束时间节流是事件驱动(Event-driven)的只有触发时才会检查状态存储函数内部保存一个关键状态通常是lastExecTime上次执行时间戳。触发时检查当函数被调用触发时它被动地做一件事计算当前时间 - lastExecTime。条件判断如果差值 wait冷却期已过则立即执行并更新lastExecTime 当前时间。如果差值 wait仍在冷却期则忽略本次调用。概念防抖 (Debounce)节流 (Throttle)核心机制一次性倒计时冷却循环计时终点倒计时结束函数执行无终点只有“冷却期结束”生命周期触发 → (重置)倒计时 → 执行 →结束执行 → 进入冷却 → 冷却结束 → 等待触发 → 执行 …(循环)maxWait角色倒计时的最长期限从首次触发算两次执行的最大间隔从上次执行算maxWait与wait关系maxWait wait通常maxWait远大于wait在经典实现中throttle(fn, wait)等于debounce(fn, wait, {maxWait: wait})应用选择Vueinput.debounce500msReact可配合 useCallback useRef 实现或使用第三方如 use-debounce深拷贝A.JSON.parse() 和 JSON.stringify()数据类型限制不能复制、undefined、Date、Symbol、函数等等其他的js数据类型其实也很好理解因为JSON只支持以上列举的数据类型其他要么被忽略要么报错。丢失原型的信息最终拷贝的对象原型只会是Object.prototype。不能处理循环引用对象包含循环引用JSON.stringify() 将会导致错误。性能问题对于大型复杂对象或深度嵌套的对象容易产生性能问题const originalObject { a: 1, b: { c: 2 } }; // 通过将对象转换为字符串再将其解析回对象来实现深拷贝 const deepCopy JSON.parse(JSON.stringify(originalObject)); console.log(deepCopy); // 输出深拷贝后的对象B.遍历递归arr instanceof Array ? [] : {}function deepClone(obj) { if (typeof obj ! object || obj null) { return obj; } const res Array.isArray(obj) ? [] : {}; for (const key in obj) { if (obj.hasOwnProperty(key)) { res[key] deepClone(obj[key]); } } return res; }要拷贝的属性只能是对象自身的不能是原型链上的拷贝的属性不仅仅是string类型的键还有symbol类型的键对象身上可能会有循环引用需要处理而不是陷入死循环C.改进版Symbol属性Object.getOwnPropertySymbolsSymbols 在 for...in 迭代中不可枚举。Object.getOwnPropertyNames() 不会返回 symbol 对象的属性// 复制 Symbol 类型属性 const symbols Object.getOwnPropertySymbols(obj); for (const symbolKey of symbols) { res[symbolKey] deepClone(obj[symbolKey]); }不可枚举属性Reflect.ownKeysReflect.ownKeys简单来说会返回一个数组属性包括对象自身的所有属性包括所有可枚举的和不可枚举的的string和symbol类型// 拿到对象身上所有的属性返回一个数组 const keys Reflect.ownKeys(obj); for (const key of keys) { res[key] deepClone(obj[key]); }循环引用WeakMap在每次对复杂数据类型进行深拷贝前保存其值如果下次又出现了该值就不再进行拷贝直接截止。WeakMap中的键是弱引用当键对象在其他地方没有被引用时它可以被垃圾回收这有助于防止内存泄漏。function deepClone(obj, clones new WeakMap()) { // 如果是原始类型或 null直接返回 if (typeof obj ! object || obj null) { return obj; } // 检查是否已经克隆过该对象防止循环引用 if (clones.has(obj)) { return clones.get(obj); } // 判断是否数组还是普通对象 const res Array.isArray(obj) ? [] : {}; // 将当前对象添加到克隆Map中 clones.set(obj, res); // 拿到所有 key const keys Reflect.ownKeys(obj); for (const key of keys) { res[key] deepClone(obj[key], clones); } return res; } // test const obj { foo: bar, num: 42, arr: [1, 2, 3], obj: { dd: true }, [Symbol(symbol属性)]: hello, }; // 循环引用 obj.newObj obj; const copy deepClone(obj); console.log(copy);setTimeout()倒计时//返回值timeoutID是一个正整数表示定时器的编号。 let timeoutID scope.setTimeout(function[, delay])//delay表示最小等待时间真正等待时间取决于前面排队的消息 clearTimeout(timeoutID) //取消该定时器。js递归调用var c 10; // 设置初始倒计时时间为10秒 var t; // 声明一个变量用来存储 setTimeout 的返回值 function timedCount() { c - 1; // 每次调用函数倒计时时间减一秒 if (c 0) { clearTimeout(t); // 当倒计时为零时清除定时器 console.log(倒计时结束); // 可以在这里添加任何你想要执行的操作比如提示用户倒计时结束 return; } console.log(剩余时间 c 秒); // 在控制台打印剩余时间你也可以将其显示在页面上的某个元素中 t setTimeout(function () { timedCount(); }, 1000); // 每隔一秒调用一次自身实现倒计时 } timedCount(); // 调用函数开始倒计时reactuseEffect((){},[cnt])import React, { useState, useEffect } from react; const Countdown ({ initialCount }) { const [count, setCount] useState(initialCount); useEffect(() { // 如果倒计时已经结束则不再减少计时 if (count 0) return; // 设置定时器 const timerId setTimeout(() { setCount(count - 1); }, 1000); // 清理函数 return () clearTimeout(timerId); }, [count]); return ( div h1倒计时: {count} 秒/h1 /div ); }; export default Countdown;requestAnimationFrame代替settimeout更精准startTime Date.now(); requestAnimationFrame(function update() { var currentTime Date.now(); var deltaTime currentTime - startTime; if (deltaTime 1000) { // 每秒更新一次 startTime currentTime; timedCount(); // 递归调用自身实现倒计时 } else { requestAnimationFrame(update); // 继续请求下一帧 } });setTimeout、setInterval最短时长为4mssetTimeout固定时长后执行setInterval间隔固定时间重复执行setTimeout模拟实现setInterval// 使用闭包实现 function mySetInterval(fn, t) { let timer null; function interval() { fn(); timer setTimeout(interval, t); } interval(); return { // cancel用来清除定时器 cancel() { clearTimeout(timer); } }; }setInterval模拟实现setTimeoutfunction mySetTimeout(fn, time) { let timer setInterval(() { clearInterval(timer); fn(); }, time); } // 使用 mySetTimeout(() { console.log(1); }, 2000);数组去重[...new Set(arr)]arr.filter((value, index, self) self.indexOf(value) index)浮点数不精确性原因部分十进制小数在二进制中无限循环Java、Python、C、JS都是基于 IEEE 754 标准的双精度浮点数来表示数字0.2的二进制表示是无限循环的近似等于0.0011001100110011001100IEEE-754 标准下双精度浮点数由三部分组成分别如下sign(符号): 占 1 bit, 表示正负;exponent(指数): 占 11 bit表示范围;mantissa(尾数): 占 52 bit表示精度多出的末尾如果是 1 需要进位;阅读 JavaScript 浮点数陷阱及解法可以了解到公式的由来。解决面试toFixed精度/*** method ** * add / subtract / multiply /divide * floatObj.add(0.1, 0.2) 0.3 * floatObj.multiply(19.9, 100) 1990 * */ var floatObj function() { /* * 判断obj是否为一个整数 */ function isInteger(obj) { return Math.floor(obj) obj } /* * 将一个浮点数转成整数返回整数和倍数。如 3.14 314倍数是 100 * param floatNum {number} 小数 * return {object} * {times:100, num: 314} */ function toInteger(floatNum) { var ret {times: 1, num: 0} if (isInteger(floatNum)) { ret.num floatNum return ret } var strfi floatNum var dotPos strfi.indexOf(.) var len strfi.substr(dotPos1).length var times Math.pow(10, len) var intNum Number(floatNum.toString().replace(.,)) ret.times times ret.num intNum return ret } /* * 核心方法实现加减乘除运算确保不丢失精度 * 思路把小数放大为整数乘进行算术运算再缩小为小数除 * * param a {number} 运算数1 * param b {number} 运算数2 * param digits {number} 精度保留的小数点数比如 2, 即保留为两位小数 * param op {string} 运算类型有加减乘除add/subtract/multiply/divide * */ function operation(a, b, digits, op) { var o1 toInteger(a) var o2 toInteger(b) var n1 o1.num var n2 o2.num var t1 o1.times var t2 o2.times var max t1 t2 ? t1 : t2 var result null switch (op) { case add: if (t1 t2) { // 两个小数位数相同 result n1 n2 } else if (t1 t2) { // o1 小数位 大于 o2 result n1 n2 * (t1 / t2) } else { // o1 小数位 小于 o2 result n1 * (t2 / t1) n2 } return result / max case subtract: if (t1 t2) { result n1 - n2 } else if (t1 t2) { result n1 - n2 * (t1 / t2) } else { result n1 * (t2 / t1) - n2 } return result / max case multiply: result (n1 * n2) / (t1 * t2) return result case divide: result (n1 / n2) * (t2 / t1) return result } } // 加减乘除的四个接口 function add(a, b, digits) { return operation(a, b, digits, add) } function subtract(a, b, digits) { return operation(a, b, digits, subtract) } function multiply(a, b, digits) { return operation(a, b, digits, multiply) } function divide(a, b, digits) { return operation(a, b, digits, divide) } // exports return { add: add, subtract: subtract, multiply: multiply, divide: divide } }();业务第三方库bignumber.js处理大数字和高精度运算Objectnew Fn[...arguments].shift()_new函数该函数会返回一个对象该对象的构造函数为函数参数、原型对象为函数参数的原型核心步骤有创建一个新对象获取函数参数将新对象的原型对象和函数参数的原型连接起来将新对象和参数传给构造器执行如果构造器返回的不是对象那么就返回第一个新对象const _new function() { const object1 {} const Fn [...arguments].shift() object1.__proto__ Fn.prototype const object2 Fn.apply(object1, arguments) return object2 instanceof Object ? object2 : object1 }*寄生组合式继承callcreateconstructor通过寄生组合式继承使Chinese构造函数继承于Human构造函数。要求如下1. 给Human构造函数的原型上添加getName函数该函数返回调用该函数对象的name属性2. 给Chinese构造函数的原型上添加getAge函数该函数返回调用该函数对象的age属性在Human构造函数的原型上添加getName函数在”Chinese“构造函数中通过call函数借助”Human“的构造器来获得通用属性Object.create函数返回一个对象该对象的__proto__属性为对象参数的原型。此时将”Chinese“构造函数的原型和通过Object.create返回的实例对象联系起来最后修复Chinese构造函数的原型链即自身的constructor属性需要指向自身在”Chinese“构造函数的原型上添加”getAge“函数function Human(name) { this.name name this.kingdom animal this.color [yellow, white, brown, black] } Human.prototype.getName function() { return this.name } function Chinese(name,age) { Human.call(this,name)//call函数借助”Human“的构造器来获得通用属性 this.age age this.color yellow } //返回的对象__proto__属性为对象参数的原型 Chinese.prototype Object.create(Human.prototype)//使用现有的对象来作为新创建对象的原型 //修复Chinese构造函数的原型链即自身的constructor属性需要指向自身 Chinese.prototype.constructor Chinese Chinese.prototype.getAge function() { return this.age }Object.defineProperty(obj, prop, descriptor)