在JS文件和Vue組件中使用防抖節流函數

如題,最近筆者發現如果將防抖節流函數寫成工具類函數放在公共文件中進行調用的話,在一般的JS文件裏調用和在Vue組件中調用是有不同的區別的。首先我們參考網上其他資料寫一個防抖函數和一個節流函數:

防抖函數(debounce):

function debounce(func, wait, immediate) {
    var timeout

    return function () {
        var context = this;
        var args = arguments;

        if (timeout) {
            clearTimeout(timeout);
        }
        if (immediate) {
            // 如果已經執行過,不再執行
            var callNow = !timeout;
            timeout = setTimeout(function(){
                timeout = null;
                func.apply(context, args) // 這行代碼可不加,詳細說明見下文
            }, wait)
            if (callNow) func.apply(context, args)
        }
        else {
            timeout = setTimeout(function(){
                func.apply(context, args)
            }, wait);
        }
    }
}

這裏簡單的說明一下這個防抖函數的主要思路:首先我們如果要對某個函數進行防抖處理(比如說根據用戶在搜索框中輸入的關鍵字來向服務器發起請求實時匹配相關的內容) 如下圖:

一般我們要對這種http請求函數做防抖處理,如果這類處理不常見的話,可以直接做一個簡單的防抖處理:

// vue組件中
data () {
    return {
        timer: null
    }
},
methods: {
    getXX () {
        if (this.timer) {
            clearTimeout(this.timer)
        }
        this.timer = setTimeout(() => {
            // 具體的業務代碼
        }, 500) // 假設防抖間隔是500ms
    }
}

但是如果我們在很多地方都需要進行防抖處理的話,此時爲了減少函數功能間的耦合,增強代碼的可讀性和可維護性,我們有必要寫一個公共的防抖/節流函數放在utils.js等工具類文件中,那麼具體的寫法就如同上文的debounce函數一樣,這種防抖函數是比較終極的一個版本了,因爲其不但含有immediate參數(控制是否先立即執行一次功能函數func),還考慮了this在不同調用環境下的指向問題(使用apply函數綁定this),具體含義可以參考這篇文章:https://www.cnblogs.com/cc-freiheit/p/10827372.html

1. debounce函數在一般JS文件中的用法:

<!DOCTYPE html>
<html lang="zh-cmn-Hans">

<head>
    <meta charset="utf-8">
    <meta http-equiv="x-ua-compatible" content="IE=edge, chrome=1">
    <title>debounce</title>
    <style>
        #container{
            width: 100%; height: 200px; line-height: 200px; text-align: center; color: #fff; background-color: #444; font-size: 30px;
        }
    </style>
</head>

<body>
<div id="container"></div>
<button id="button">cancel</button>
<script src="debounce.js"></script>
</body>

</html>

這裏給出了一個 id爲 container的div,爲該div綁定onmousemove 方法,當鼠標在container上移動時就會不斷觸發onmousemove事件從而增加count的值,在沒有加入防抖處理前效果是這樣的:

加入防抖處理後: 

// TODO: 立即執行版的意思是觸發事件後函數會立即執行,然後 n 秒內不觸發事件才能繼續執行函數的效果。
// 假設debounce函數寫在utils.js文件內
var count = 1;
var container = document.getElementById('container');

function handleUserAction() {
    container.innerHTML = count++;
}

container.onmousemove = utils.debounce(handleUserAction, 3000, true);
 

結果如下:

如圖:這裏由於將immediate置爲true,當鼠標觸發mousemove事件時會馬上觸發一次,此時顯示爲1,之後不斷在div中移動鼠標觸發mousemove事件,經防抖函數處理後count一直爲1,直至停止觸發該事件3s以上,count變爲2(最後又因爲鼠標再次移動了count變爲3)

 這是在debounce函數中含有第16行代碼 func.apply(context, args) 情況下的防抖效果,在註釋中我們提到這行代碼可以不加,那麼如果在debounce函數中不加第16行代碼效果會有何不同呢?如下圖:

 

在鼠標觸發了mousemove事件後(immediate爲true),count立即變爲了1,之後停止觸發mousemove 3秒以上,但是count值最終也沒有再加1

也就是說再immediate爲true的情況下,第16行代碼加與不加的區別在於最後停止頻繁觸發某一被防抖事件後還會不會再執行一次該事件。筆者認爲這裏我們可以針對不同的需求加上/刪去第16行代碼。不過根據筆者之前遇到的情況來看,一般都是需要第16行代碼的,比如說上文中的搜索框根據關鍵詞實時聯想相關搜索結果的防抖處理就要加。

 

2. debounce函數在vue組件中的使用

 防抖函數在vue組件中的寫法與在一般js文件中的寫法略有不同,有時候沒有注意到這一點在調用該函數的時候會遇到防抖無效的問題。首先我們同樣可以將debounce函數作爲一個工具函數放入一個工具類文件中,然後在vue組件中調用該函數:

<!--vue組件的HTML部分-->

    <div id="box" class="welcome">
        <div id="content1" 
            style="height:150px;line-height:150px;text-align:center; color: #fff;background-color:#ccc;font-size:80px;" 
            @mousemove="changeContent">
         </div>
    </div>
// methods函數部分,正確的調用寫法
methods: {
        addContent () {
            let content = document.getElementById('content1')
            console.log('content', content)
            content.innerHTML = this.num++
        },
        changeContent: utils._debounce(function() {
            this.addContent()
        }, 2000, true),
}

 如上,根據測試,在vue組件的中調用debounce函數必須以這樣的形式調用纔有效,否則調用無效

// 這種調用寫法無效
changeContent () {
     utils._debounce(function() {
          this.addContent()
     }, 2000, true)
}

具體原因筆者現在還沒有完全搞清除,但是推測與vue中的方法存在於其組件實例(每個組件具體的this)上有關 。

最後是節流函數的使用,其實與防抖函數的調用是一樣的,只是效果不同,我們應該針對具體需求對相關函數進行防抖/節流處理,這裏給出一個節流的參考版本:

// trailing同樣控制的是 是否先立即執行一次函數
function throttle (func, wait, trailing) {
    console.log('func', func)
    console.log('執行函數') // 這是閉包,函數只要引入頁面就執行了
    let timer = null
    let start = 0
    return function () {
        console.log('func2', func)
        let now = new Date()
        let remaining = wait - (now - start)
        console.log(remaining)
        // 攔截:延遲時間>多次點擊間隔時間,執行:多次點擊間隔時間>延遲時間
        if ((now - start) >= wait) {
            // 由於setTimeout存在最小時間精度問題,因此會存在到達wait的時間間隔
            // 但之前設置的setTimeout操作還沒被執行,因此爲保險起見,這裏先清理setTimeout操作
            timer && clearTimeout(timer)
            timer = null
            start = now
            console.log('非頻繁操作,點擊間隔爲:',remaining)
            return func()
        } else if (!timer && trailing) {
            // trailing有值時,延時執行func函數
            // 頻繁操作,第一次設置定時器之後,之後的不會再走到這裏創建定時器
            // 清除問題,只能在第二有效點擊的時候纔會清除
            timer = setTimeout(() => {
                timer = null
                console.log('頻繁操作,定時器延時執行')
                return func
            }, wait)
        }
    }
}

 demo代碼演示地址:

防抖: https://codepen.io/zitonzong/pen/jONpBMm

節流:https://codepen.io/zitonzong/pen/QWLBpKp

參考資料:

https://www.cnblogs.com/cc-freiheit/p/10827372.html

https://www.cnblogs.com/fozero/p/11107606.html

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章