寫在最前面:這是我寫的一個一文搞懂JS系列專題散休。文章清晰易懂,會將會將關(guān)聯(lián)的只是串聯(lián)在一起乐尊,形成自己獨(dú)立的知識脈絡(luò)戚丸,整個合集讀完相信你也一定會有所收獲。寫作不易扔嵌,希望您能給我點(diǎn)個贊限府!
合集地址:一文搞懂JS系列專題
概覽
-
食用時間: 10-15分鐘
-
難度: 簡單,別跑痢缎,看完再走
-
食用價值: JS性能優(yōu)化
-
食材
先來看一段代碼胁勺,這會是一個貫穿全文的案例,代碼如下:
<div id="content" style="height:150px;line-height:150px;
text-align:center; color: #fff;background-color:black;
font-size:80px;"></div>
<script>
let num = 1;
const content = document.getElementById('content');
function count() {
content.innerHTML = num++;
};
content.onmousemove = count;
</script>
可以看到独旷,在黑色色塊中移動的同時姻几, addCount
函數(shù)被瘋狂執(zhí)行,但是很多時候势告,我們不希望這個函數(shù)執(zhí)行地如此頻繁,畢竟會影響程序或者網(wǎng)頁的性能抚恒,作為 性能優(yōu)化
方案的一種咱台,接下來,我們來引入今天的主角俭驮,防抖 和 節(jié)流
防抖
定義
將多次執(zhí)行變?yōu)樽詈笠淮螆?zhí)行或立即執(zhí)行回溺,你可以理解為防止手抖
使用場景
- 搜索框搜索輸入。只需用戶最后一次輸入完混萝,再發(fā)送請求
- 手機(jī)號遗遵、郵箱驗(yàn)證輸入檢測
- 窗口大小Resize。只需窗口調(diào)整完成后逸嘀,計(jì)算窗口大小车要。防止重復(fù)渲染
實(shí)現(xiàn)方式
-
非立即執(zhí)行版
這應(yīng)該是最基礎(chǔ)也是最常用的一個版本,先來看下代碼
function debounce(func,wait,...args) {
let timeout; //延時器變量
return function () {
const context = this; //改變this指向
if (timeout) clearTimeout(timeout); //先判斷有沒有延時器崭倘,有則清空翼岁,畢竟要最后一次執(zhí)行
timeout = setTimeout(() => {
func.apply(context, args) //apply調(diào)用傳入方法
}, wait);
}
}
可以看到类垫,方法
debounce()
有兩個入?yún)ⅲ粋€方法名func
琅坡, 以及一個延時時間wait
悉患,單位ms
,還有一個使用了擴(kuò)展運(yùn)算符...
的函數(shù)執(zhí)行時候的入?yún)?args
(選傳)
接下來榆俺,使用 content.onmousemove = debounce(count,1000);
調(diào)用我們新寫的非立即執(zhí)行版的防抖售躁,先來看下實(shí)際的運(yùn)行效果,可以看到事件觸發(fā)了以后,只有在觸發(fā)以后的1s內(nèi)不再觸發(fā)茴晋,才會執(zhí)行相應(yīng)的方法陪捷,也就是 count++
。如果停止時間間隔小于 wait
的值并且再次觸發(fā)晃跺,那么將重新計(jì)算執(zhí)行時間揩局,計(jì)時器結(jié)束以后,再執(zhí)行方法掀虎×瓒ⅲ總結(jié)而言就是觸發(fā)事件后函數(shù)不會立即執(zhí)行,而是在 n 秒后執(zhí)行烹玉,如果在 n 秒內(nèi)又觸發(fā)了事件驰怎,則會重新計(jì)算函數(shù)執(zhí)行時間,也就是方法的執(zhí)行是非立即執(zhí)行的
整個方法的核心思想就是依靠變量 timeout
二打,用來控制當(dāng)前是否存在定時器县忌,如果有缠黍,則清空妈倔,清空完以后再繼續(xù)創(chuàng)建一個华望。所以挪鹏,在多次執(zhí)行的同時据过,不斷清空再新建卢鹦,直到停止執(zhí)行以后美旧,在停止執(zhí)行以后的 wait
毫秒以后怀大,延時器就會成功生效凡简,方法就會被觸發(fā)逼友,也就是所謂的非立即執(zhí)行,畢竟秤涩,還要等待延時器的延時 wait
帜乞。
-
立即執(zhí)行版
立即執(zhí)行版就是在觸發(fā)事件后函數(shù)會立即執(zhí)行,然后 n 秒內(nèi)不觸發(fā)事件才能繼續(xù)執(zhí)行函數(shù)的效果筐眷,代碼如下:
function debounce(func,wait,...args){
let timeout; //延時器變量
return function(){
const context = this;
if (timeout) clearTimeout(timeout);
let callNow = !timeout; //是否立即執(zhí)行
timeout = setTimeout(() => {
timeout = null;
},wait)
if(callNow) func.apply(context,args)
}
}
可以看到的是黎烈, timeout
依然是延時器,主要核心控制是靠 callNow
① 在剛初始化的時候,沒有定時器怨喘,所以剛開始
callNow=!timeout
執(zhí)行完以后津畸,callNow
為true
,再設(shè)置一個延時器必怜,然后直接執(zhí)行方法肉拓,這就是所謂的立即執(zhí)行
② 第二次的時候在進(jìn)入的時候,
if (timeout)
為真梳庆,將定時器進(jìn)行清空暖途,callNow=!timeout
為假,條件不成立
③
if(callNow)
不成立膏执,函數(shù)不執(zhí)行驻售,因?yàn)?timeout = null
,往后將不再執(zhí)行函數(shù)更米,直到延時器完成調(diào)用timeout = null
之后再觸發(fā)事件
④ 觸發(fā)之后欺栗,
timeout = null
,callNow
賦值為真征峦,if(callNow)
條件再次符合迟几,完成執(zhí)行函數(shù)
關(guān)于上面有一點(diǎn), clearTimeout(timeout)
以后栏笆,console.log(timeout)
輸出為 1
不相信的可以看一下下面的代碼輸出
let timer=setTimeout(()=>{
},1000);
clearTimeout(timer);
console.log(!timer); //false
最后类腮,讓我們再來看一下實(shí)際使用效果,可以看到的是蛉加,觸發(fā)事件后函數(shù)會立即執(zhí)行蚜枢,然后 n 秒內(nèi)不觸發(fā)事件才能繼續(xù)執(zhí)行函數(shù)的效果
節(jié)流
定義
將多次執(zhí)行變?yōu)槊扛粢欢螘r間執(zhí)行一次
使用場景
- 滾動加載,加載更多或滾到底部監(jiān)聽
實(shí)現(xiàn)方式
-
時間戳版(立即執(zhí)行版)
在持續(xù)觸發(fā)事件的過程中针饥,函數(shù)會立即執(zhí)行厂抽,并且每隔一段時間執(zhí)行一次,代碼如下:
function throttle(func, wait, ...args){
let pre = 0;
return function(){
const context = this;
let now = Date.now();
if (now - pre >= wait){
func.apply(context, args);
pre = Date.now();
}
}
}
① 首先定義了一個只有完成函數(shù)調(diào)用才更新當(dāng)前時間的變量
pre
丁眼,然后定義了一個實(shí)時更新的當(dāng)前時間now
② 進(jìn)入第一次計(jì)算時間間隔筷凤,
now - pre >= wait
是必定成立的,所以函數(shù)會立即觸發(fā)
③ 觸發(fā)完了以后户盯,將
pre
的值進(jìn)行更新,之后饲化,now
的值會進(jìn)行實(shí)時更新
④ 直到
now - pre >= wait
的條件成立莽鸭,也就是現(xiàn)在的時間距離上次觸發(fā)的時間大于等于wait
的等待時間,函數(shù)會再次觸發(fā)吃靠,(畢竟只要函數(shù)不觸發(fā)硫眨,pre
的值不更新,而now一直在實(shí)時更新巢块,時間長了礁阁,條件肯定會成立的)
⑤ 以此類推巧号,完成了事件一直在觸發(fā),首次立即執(zhí)行函數(shù)姥闭,之后函數(shù)只會隔一段時間執(zhí)行
分析完了代碼丹鸿,讓我們來看看實(shí)際運(yùn)行效果,果然和我們的分析如出一轍:
-
延時器版(非立即執(zhí)行版)
在持續(xù)觸發(fā)事件的過程中棚品,函數(shù)不會立即執(zhí)行靠欢,并且每隔一段時間執(zhí)行一次,在停止觸發(fā)事件后铜跑,函數(shù)還會再執(zhí)行一次门怪,代碼如下:
function throttle(func, wait, ...args){
let timeout;
return function(){
const context = this;
if(!timeout){
timeout = setTimeout(() => {
timeout = null;
func.apply(context,args);
},wait)
}
}
}
① 首先定義了一個延時器變量
timeout
,先判斷是否有延時器锅纺,沒有則創(chuàng)建掷空,所以第一次進(jìn)入函數(shù)的時候,會先創(chuàng)建一個延時器
② 再次進(jìn)入函數(shù)的時候囤锉,因?yàn)楫?dāng)前已經(jīng)存在延時器了坦弟,所以什么都不做
③ 什么都不做直到延時器的時間結(jié)束,函數(shù)開始執(zhí)行嚼锄,將
timeout
進(jìn)行清空并且執(zhí)行函數(shù)
④ 清空以后减拭,再一次判斷,
if(!timeout)
條件成立区丑,繼續(xù)創(chuàng)建延時器
⑤ 以此類推拧粪,有延時器就什么都不做,沒有了延時器則創(chuàng)建
⑥ 即使不觸發(fā)事件沧侥,延時器仍然存在可霎,所以,停止觸發(fā)事件以后宴杀,函數(shù)仍然會再執(zhí)行一次
分析完了代碼癣朗,讓我們來看看實(shí)際運(yùn)行效果,果然和我們的分析如出一轍:
系列目錄
-
一文搞懂JS系列(一)之編譯原理旺罢,作用域旷余,作用域鏈,變量提升扁达,暫時性死區(qū)
-
一文搞懂JS系列(二)之JS內(nèi)存生命周期正卧,棧內(nèi)存與堆內(nèi)存,深淺拷貝
-
一文搞懂JS系列(三)之垃圾回收機(jī)制跪解,內(nèi)存泄漏炉旷,閉包
-
一文搞懂JS系列(四)之閉包應(yīng)用-柯里化,偏函數(shù)
-
一文搞懂JS系列(四)之閉包應(yīng)用-防抖,節(jié)流
-
一文搞懂JS系列(六)之微任務(wù)與宏任務(wù)窘行,Event Loop
-
一文搞懂JS系列(七)之構(gòu)造函數(shù)饥追,new,實(shí)例對象罐盔,原型但绕,原型鏈,ES6中的類