电脑知识|欧美黑人一区二区三区|软件|欧美黑人一级爽快片淫片高清|系统|欧美黑人狂野猛交老妇|数据库|服务器|编程开发|网络运营|知识问答|技术教程文章 - 好吧啦网

您的位置:首頁技術文章
文章詳情頁

JavaScript 防抖和節(jié)流遇見的奇怪問題及解決

瀏覽:113日期:2023-10-07 15:17:56

場景

網(wǎng)絡上已經(jīng)存在了大量的有關 防抖 和 節(jié)流 的文章,為何吾輩還要再寫一篇呢?事實上,防抖和節(jié)流,吾輩在使用中發(fā)現(xiàn)了一些奇怪的問題,并經(jīng)過了數(shù)次的修改,這里主要分享一下吾輩遇到的問題以及是如何解決的。

為什么要用防抖和節(jié)流?

因為某些函數(shù)觸發(fā)/調用的頻率過快,吾輩需要手動去限制其執(zhí)行的頻率。例如常見的監(jiān)聽滾動條的事件,如果沒有防抖處理的話,并且,每次函數(shù)執(zhí)行花費的時間超過了觸發(fā)的間隔時間的話 ? 頁面就會卡頓。

演進

初始實現(xiàn)

我們先實現(xiàn)一個簡單的去抖函數(shù)

function debounce(delay, action) { let tId return function(...args) { if (tId) clearTimeout(tId) tId = setTimeout(() => { action(...args) }, delay) }}

測試一下

// 使用 Promise 簡單封裝 setTimeout,下同const wait = ms => new Promise(resolve => setTimeout(resolve, ms));(async () => { let num = 0 const add = () => ++num add() add() console.log(num) // 2 const fn = debounce(10, add) fn() fn() console.log(num) // 2 await wait(20) console.log(num) // 3})()

好了,看來基本的效果是實現(xiàn)了的。包裝過的函數(shù) fn 調用了兩次,卻并沒有立刻執(zhí)行,而是等待時間間隔過去之后才最終執(zhí)行了一次。

this 怎么辦?

然而,上面的實現(xiàn)有一個致命的問題,沒有處理 this!當你用在原生的事件處理時或許還不覺得,然而,當你使用了 ES6 class 這類對 this 敏感的代碼時,就一定會遇到 this 帶來的問題。

例如下面使用 class 來聲明一個計數(shù)器

class Counter { constructor() { this.i = 0 } add() { this.i++ }}

我們可能想在 constructor 中添加新的屬性 fn

class Counter { constructor() { this.i = 0 this.fn = debounce(10, this.add) } add() { this.i++ }}

但很遺憾,這里的 this 綁定是有問題的,執(zhí)行以下代碼試試看

const counter = new Counter()counter.fn() // Cannot read property ’i’ of undefined

會拋出異常 Cannot read property ’i’ of undefined,究其原因就是 this 沒有綁定,我們可以手動綁定 this .bind(this)

class Counter { constructor() { this.i = 0 this.fn = debounce(10, this.add.bind(this)) } add() { this.i++ }}

但更好的方式是修改 debounce,使其能夠自動綁定 this

function debounce(delay, action) { let tId return function(...args) { if (tId) clearTimeout(tId) tId = setTimeout(() => { action.apply(this, args) }, delay) }}

然后,代碼將如同預期的運行

;(async () => { class Counter { constructor() { this.i = 0 this.fn = debounce(10, this.add) } add() { this.i++ } } const counter = new Counter() counter.add() counter.add() console.log(counter.i) // 2 counter.fn() counter.fn() console.log(counter.i) // 2 await wait(20) console.log(counter.i) // 3})()

返回值呢?

不知道你有沒有發(fā)現(xiàn),現(xiàn)在使用 debounce 包裝的函數(shù)都沒有返回值,是完全只有副作用的函數(shù)。然而,吾輩還是遇到了需要返回值的場景。例如:輸入停止后,使用 Ajax 請求后臺數(shù)據(jù)判斷是否已存在相同的數(shù)據(jù)。

修改 debounce 成會緩存上一次執(zhí)行結果并且有初始結果參數(shù)的實現(xiàn)

function debounce(delay, action, init = undefined) { let flag let result = init return function(...args) { if (flag) clearTimeout(flag) flag = setTimeout(() => { result = action.apply(this, args) }, delay) return result }}

調用代碼變成了

;(async () => { class Counter { constructor() { this.i = 0 this.fn = debounce(10, this.add, 0) } add() { return ++this.i } } const counter = new Counter() console.log(counter.add()) // 1 console.log(counter.add()) // 2 console.log(counter.fn()) // 0 console.log(counter.fn()) // 0 await wait(20) console.log(counter.fn()) // 3})()

看起來很完美?然而,沒有考慮到異步函數(shù)是個大失??!

嘗試以下測試代碼

;(async () => { const get = async i => i console.log(await get(1)) console.log(await get(2)) const fn = debounce(10, get, 0) fn(3).then(i => console.log(i)) // fn(...).then is not a function fn(4).then(i => console.log(i)) await wait(20) fn(5).then(i => console.log(i))})()

會拋出異常 fn(...).then is not a function,因為我們包裝過后的函數(shù)是同步的,第一次返回的值并不是 Promise 類型。

除非我們修改默認值

;(async () => { const get = async i => i console.log(await get(1)) console.log(await get(2)) // 注意,修改默認值為 Promise const fn = debounce(10, get, new Promise(resolve => resolve(0))) fn(3).then(i => console.log(i)) // 0 fn(4).then(i => console.log(i)) // 0 await wait(20) fn(5).then(i => console.log(i)) // 4})()

支持有返回值的異步函數(shù)

支持異步有兩種思路

將異步函數(shù)包裝為同步函數(shù) 將包裝后的函數(shù)異步化

第一種思路實現(xiàn)

function debounce(delay, action, init = undefined) { let flag let result = init return function(...args) { if (flag) clearTimeout(flag) flag = setTimeout(() => { const temp = action.apply(this, args) if (temp instanceof Promise) { temp.then(res => (result = res)) } else { result = temp } }, delay) return result }}

調用方式和同步函數(shù)完全一樣,當然,是支持異步函數(shù)的

;(async () => { const get = async i => i console.log(await get(1)) console.log(await get(2)) // 注意,修改默認值為 Promise const fn = debounce(10, get, 0) console.log(fn(3)) // 0 console.log(fn(4)) // 0 await wait(20) console.log(fn(5)) // 4})()

第二種思路實現(xiàn)

const debounce = (delay, action, init = undefined) => { let flag let result = init return function(...args) { return new Promise(resolve => { if (flag) clearTimeout(flag) flag = setTimeout(() => { result = action.apply(this, args) resolve(result) }, delay) setTimeout(() => { resolve(result) }, delay) }) }}

調用方式支持異步的方式

;(async () => { const get = async i => i console.log(await get(1)) console.log(await get(2)) // 注意,修改默認值為 Promise const fn = debounce(10, get, 0) fn(3).then(i => console.log(i)) // 0 fn(4).then(i => console.log(i)) // 4 await wait(20) fn(5).then(i => console.log(i)) // 5})()

可以看到,第一種思路帶來的問題是返回值永遠會是 舊的 返回值,第二種思路主要問題是將同步函數(shù)也給包裝成了異步。利弊權衡之下,吾輩覺得第二種思路更加正確一些,畢竟使用場景本身不太可能必須是同步的操作。而且,原本 setTimeout 也是異步的,只是不需要返回值的時候并未意識到這點。

避免原函數(shù)信息丟失

后來,有人提出了一個問題,如果函數(shù)上面攜帶其他信息,例如類似于 jQuery 的 $,既是一個函數(shù),但也同時含有其他屬性,如果使用 debounce 就找不到了呀

一開始吾輩立刻想到了復制函數(shù)上面的所有可遍歷屬性,然后想起了 ES6 的 Proxy 特性 ? 這實在是太魔法了。使用 Proxy 解決這個問題將異常的簡單 ? 因為除了調用函數(shù),其他的一切操作仍然指向原函數(shù)!

const debounce = (delay, action, init = undefined) => { let flag let result = init return new Proxy(action, { apply(target, thisArg, args) { return new Promise(resolve => { if (flag) clearTimeout(flag) flag = setTimeout(() => { resolve((result = Reflect.apply(target, thisArg, args))) }, delay) setTimeout(() => { resolve(result) }, delay) }) }, })}

測試一下

;(async () => { const get = async i => i get.rx = ’rx’ console.log(get.rx) // rx const fn = debounce(10, get, 0) console.log(fn.rx) // rx})()

實現(xiàn)節(jié)流

以這種思路實現(xiàn)一個節(jié)流函數(shù) throttle

/** * 函數(shù)節(jié)流 * 節(jié)流 (throttle) 讓一個函數(shù)不要執(zhí)行的太頻繁,減少執(zhí)行過快的調用,叫節(jié)流 * 類似于上面而又不同于上面的函數(shù)去抖, 包裝后函數(shù)在上一次操作執(zhí)行過去了最小間隔時間后會直接執(zhí)行, 否則會忽略該次操作 * 與上面函數(shù)去抖的明顯區(qū)別在連續(xù)操作時會按照最小間隔時間循環(huán)執(zhí)行操作, 而非僅執(zhí)行最后一次操作 * 注: 該函數(shù)第一次調用一定會執(zhí)行,不需要擔心第一次拿不到緩存值,后面的連續(xù)調用都會拿到上一次的緩存值 * 注: 返回函數(shù)結果的高階函數(shù)需要使用 {@link Proxy} 實現(xiàn),以避免原函數(shù)原型鏈上的信息丟失 * * @param {Number} delay 最小間隔時間,單位為 ms * @param {Function} action 真正需要執(zhí)行的操作 * @return {Function} 包裝后有節(jié)流功能的函數(shù)。該函數(shù)是異步的,與需要包裝的函數(shù) {@link action} 是否異步?jīng)]有太大關聯(lián) */const throttle = (delay, action) => { let last = 0 let result return new Proxy(action, { apply(target, thisArg, args) { return new Promise(resolve => { const curr = Date.now() if (curr - last > delay) { result = Reflect.apply(target, thisArg, args) last = curr resolve(result) return } resolve(result) }) }, })}

總結

嘛,實際上這里的防抖和節(jié)流仍然是簡單的實現(xiàn),其他的像 取消防抖/強制刷新緩存 等功能尚未實現(xiàn)。當然,對于吾輩而言功能已然足夠了,也被放到了公共的函數(shù)庫 rx-util 中。

以上就是JavaScript 防抖和節(jié)流遇見的奇怪問題及解決的詳細內容,更多關于JavaScript 防抖和節(jié)流的資料請關注好吧啦網(wǎng)其它相關文章!

標簽: JavaScript
相關文章:
主站蜘蛛池模板: 法兰连接型电磁流量计-蒸汽孔板节流装置流量计-北京凯安达仪器仪表有限公司 | 厂房出售_厂房仓库出租_写字楼招租_土地出售-中苣招商网-中苣招商网 | 钢格栅板_钢格板网_格栅板-做专业的热镀锌钢格栅板厂家-安平县迎瑞丝网制造有限公司 | 非甲烷总烃分析仪|环控百科| 上海盐水喷雾试验机_两厢式冷热冲击试验箱-巨怡环试 | 飞飞影视_热门电影在线观看_影视大全| 超声波电磁流量计-液位计-孔板流量计-料位计-江苏信仪自动化仪表有限公司 | 棉服定制/厂家/公司_棉袄订做/价格/费用-北京圣达信棉服 | 艾默生变频器,艾默生ct,变频器,ct驱动器,广州艾默生变频器,供水专用变频器,风机变频器,电梯变频器,艾默生变频器代理-广州市盟雄贸易有限公司官方网站-艾默生变频器应用解决方案服务商 | 富森高压水枪-柴油驱动-养殖场高压清洗机-山东龙腾环保科技有限公司 | 头条搜索极速版下载安装免费新版,头条搜索极速版邀请码怎么填写? - 欧远全 | 包塑丝_高铁绑丝_地暖绑丝_涂塑丝_塑料皮铁丝_河北创筹金属丝网制品有限公司 | 路面机械厂家 | 扬尘在线监测系统_工地噪声扬尘检测仪_扬尘监测系统_贝塔射线扬尘监测设备「风途物联网科技」 | 全自动包装秤_全自动上袋机_全自动套袋机_高位码垛机_全自动包装码垛系统生产线-三维汉界机器(山东)股份有限公司 | 上海公司注册-代理记账-招投标审计-上海昆仑扇财税咨询有限公司 上海冠顶工业设备有限公司-隧道炉,烘箱,UV固化机,涂装设备,高温炉,工业机器人生产厂家 | 防堵吹扫装置-防堵风压测量装置-电动操作显示器-兴洲仪器 | nalgene洗瓶,nalgene量筒,nalgene窄口瓶,nalgene放水口大瓶,浙江省nalgene代理-杭州雷琪实验器材有限公司 | 派克防爆伺服电机品牌|国产防爆伺服电机|高低温伺服电机|杭州摩森机电科技有限公司 | 门禁卡_智能IC卡_滴胶卡制作_硅胶腕带-卡立方rfid定制厂家 | 培训一点通 - 合肥驾校 - 合肥新亚驾校 - 合肥八一驾校 | 防弹玻璃厂家_防爆炸玻璃_电磁屏蔽玻璃-四川大硅特玻科技有限公司 | 爆炸冲击传感器-无线遥测传感器-航天星百科 | 成都租车_成都租车公司_成都租车网_众行宝 | 爱佩恒温恒湿测试箱|高低温实验箱|高低温冲击试验箱|冷热冲击试验箱-您身边的模拟环境试验设备技术专家-合作热线:400-6727-800-广东爱佩试验设备有限公司 | 干培两用箱-细菌恒温培养箱-菲斯福仪器 | 颚式破碎机,圆锥破碎机,制砂机-新乡市德诚机电制造有限公司 | 短信通106短信接口验证码接口群发平台_国际短信接口验证码接口群发平台-速度网络有限公司 | 实验室pH计|电导率仪|溶解氧测定仪|离子浓度计|多参数水质分析仪|pH电极-上海般特仪器有限公司 | 首页|专注深圳注册公司,代理记账报税,注册商标代理,工商变更,企业400电话等企业一站式服务-慧用心 | 网站建设,北京网站建设,北京网站建设公司,网站系统开发,北京网站制作公司,响应式网站,做网站公司,海淀做网站,朝阳做网站,昌平做网站,建站公司 | 刑事律师_深圳著名刑事辩护律师_王平聚【清华博士|刑法教授】 | 网站制作优化_网站SEO推广解决方案-无锡首宸信息科技公司 | 数控走心机-双主轴走心机厂家-南京建克 | 产业规划_产业园区规划-产业投资选址及规划招商托管一体化服务商-中机院产业园区规划网 | 全自动面膜机_面膜折叠机价格_面膜灌装机定制_高速折棉机厂家-深圳市益豪科技有限公司 | 冷油器-冷油器换管改造-连云港灵动列管式冷油器生产厂家 | 重庆小面培训_重庆小面技术培训学习班哪家好【终身免费复学】 | 工业硝酸钠,硝酸钠厂家-淄博「文海工贸」| 底部填充胶_电子封装胶_芯片封装胶_芯片底部填充胶厂家-东莞汉思新材料 | 施工围挡-施工PVC围挡-工程围挡-深圳市旭东钢构技术开发有限公司 |