防抖 操作時不執(zhí)行 確定不操作了才執(zhí)行,在前端應(yīng)用場景還是比較多的
好比你在一直玩手機(jī)的時候,手機(jī)不會息屏 玩了一分鐘不玩了 就息屏了
原理: 操作(你快動我?。?>關(guān)閉定時器(好了,我要重新數(shù)數(shù)了)=>重新開始計(jì)算器(你再不動我就要息屏了)=>執(zhí)行(手機(jī)息屏了)
如何去實(shí)現(xiàn)這么一個函數(shù)
1.創(chuàng)建一個函數(shù),他接受倆個參數(shù),一個是你想去防抖的函數(shù),一個是你想延時操作的時間
function debounce(fun,delay){
}
2.他返回一個自執(zhí)行函數(shù),并且這個函數(shù)在 delay后執(zhí)行
function debounce(fun,delay){
return function(){
setTimeout(()=>{
fun()
},delay)
}
}
此時其實(shí)已經(jīng)實(shí)現(xiàn)了一個延時函數(shù)了,但是需要自執(zhí)行一下,因?yàn)槲覀兎祷氐氖呛瘮?shù)體,可以使用debounce(fun,delay)(),或者return function(){}()加個括弧
function debounce(fun,delay){
return function(){
setTimeout(()=>{
fun()
},delay)
}
}
function sleep(){
console.log(1)
}
debounce(sleep,3000)()
那當(dāng)然這種代碼肯定沒有如下來的簡潔
const sleep = time =>{
return new Promise(resolve=>setTimeout(resolve,time))
}
3.再來看一遍原理 操作=>關(guān)閉定時器=>重新開始計(jì)算器=>執(zhí)行,那么接下來就是對定時器的操作,我們聲明一個定時器然后并覆蓋
function debounce(fun,delay){
let timer
return function(){
if(timer) clearTimeout(timer)
timer = setTimeout(()=>{
fun()
},delay)
}
}
那么到這步,其實(shí)已經(jīng)實(shí)現(xiàn)了大多數(shù)的防抖,我們嘗試一下
function watchResize() {
console.log(1)
}
window.addEventListener('resize', debounces(watchResize, 1000))
在頁面中進(jìn)行窗口改變的時候,當(dāng)你停止改變1s后就會打印1,當(dāng)然這個函數(shù)還有一些優(yōu)化,比如對fn的類型判斷,delay的處理等等,了解基礎(chǔ)原理之后 稍微進(jìn)階一下
<input type="text" id="ipt">
比如我們寫個input,給它綁定一個input事件
ipt.oninput = function(){
console.log(this.value)
}
OK,此時沒有問題,但是我們想要用戶最后輸入的值,做個防抖
ipt.oninput = debounce(function () {
console.log(this.value)
}, 2000)
此時控制臺你會發(fā)現(xiàn),打印的是undefined,問題就出在this指向問題
ES5: 一個函數(shù)在被調(diào)用時,會自動取得兩個特殊變量:this和arguments。在全局環(huán)境調(diào)用函數(shù)時,this代表window對象;而當(dāng)函數(shù)被作為其他某個對象的方法而調(diào)用時,this就代表那個對象。
簡而言之: ES5的函數(shù)中,this是執(zhí)行時函數(shù)所在的那個對象。
function debounce(fun, delay) {
console.log(this) //此處打印的是windows
let timer
return function () {
//此處是執(zhí)行環(huán)境 因?yàn)榉祷氐暮瘮?shù) 在xx環(huán)境下觸發(fā)的 xx就是調(diào)用的對象
console.log(this)
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
fun()
}, delay)
}
}
所以我們在定時器的函數(shù)執(zhí)行顯示綁定當(dāng)前的this即可
function debounce(fun, delay) {
let timer
return function () {
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
fun.apply(this)
}, delay)
}
}
同理,調(diào)用的時候就不能使用箭頭函數(shù),箭頭函數(shù)不存在this,當(dāng)然你非要用 ,可以這樣用
const co = (obj)=>obj.value
ipt.oninput = debounce(function(){
console.log(co(this))
},1000)
感覺就挺憨的?。?/p>
節(jié)流,原理差不多,都是操控時間
好比你打游戲的技能冷卻,亦或是你等的公交車,總是多少時間來一趟,如何控制時間有倆種說法,可以通過當(dāng)前時間減去上一次時間,也可以通過設(shè)置的時間去操作,兩種思路都可以
我們先按照防抖的思路寫一個setTimeout版本,老樣子 先返回一個函數(shù),有了前面的基礎(chǔ),現(xiàn)在節(jié)奏快點(diǎn)
function throttle(fn,delay){
return function(){
setTimeout(()=>{
fn.apply(this,arguments)
},delay)
}
}
然后我們?nèi)プ鲆幌驴刂?/p>
function throttle(fn, delay) {
//開啟一個值 默認(rèn)關(guān)閉
let timer = false
return function () {
//如果這個timer是開啟的 那么說明我們在時間范圍之外 那我們不做啥 直接返回
if (timer) {
return
}
else {
//開啟這個值
timer = true
setTimeout(() => {
fn.apply(this, arguments)
//說明下一次可以執(zhí)行了 那你就繼續(xù)false吧
timer = false
}, delay)
}
}
}
window.addEventListener('resize', throttle(watchresize, 2000))
然后你發(fā)現(xiàn)在一直操作窗口的時候,2s觸發(fā)一次咯
function throttle(fun, delay) {
let previous = 0;
return function () {
let now = Date.now();
let args = arguments;
if (now - previous > delay) {
func.apply(this, args);
previous = now;
}
}
}
也可以使用時間戳去控制??! 看完嘗試自己再手寫幾遍就應(yīng)該可以理解了