徹底搞懂JavaScript的閉包、防抖跟節流

最近出去面試了一下,收穫頗多!!!

以前的我,追求實際,比較追求實用價值,然而最近面試,傳說中的面試造火箭,工作擰螺絲,竟然被我遇到了。雖然很多知識點在實際工作中並不經常用到,但人家就是靠這個來篩選人才的!!!

在大學裡,你可能會感覺到微積分沒啥用處,去菜市場買菜,你不可能用到微積分吧,但是呢,這個知識卻決定了你能在哪個菜市場買菜。請細品!

關於前端方面,我用的是jQuery,前端採用Ajax請求數據,後端返回JSON數據,得到數據後,再通過jQuery去操作DOM。

這裡可能有個誤解,很多人會以為jQuery是一種框架,然而現實並非如此,它只是個工具庫而已,簡化了JavaScript的一些語法。可能很多人用習慣了jQuery後,甚至都分不清jQuery語法跟JavaScript語法。jQuery能做的事情,JavaScript也能做,只是簡單地簡化了一些寫法。

閑話少說,開始正文。

一、談談對閉包的理解

從概念上來講,所謂的閉包指的是一個函數,一個什麼樣的函數呢?有權訪問另一個函數作用域中的變數的函數。
直觀點來講,JavaScript的閉包就是函數中嵌套函數。
本質上來講,是因為作用域而產生一種特殊的情況,從而導致函數內部的變數無法進行銷毀。如果再去深究的話,會出現個作用域鏈,這裡面比較難理解。也是閉包產生的根本原因。
舉個例子:

function myMethod(){
      var num=6;
      return function(){
            var n=0;
            console.log(++n);
            console.log(++num);
      }
}
myMethod();//執行後,返回的是個方法(就是所謂的閉包),因為這個方法中還用到了myMethod裡面的變數num,會導致num無法釋放。

//可以證明
var myFn=myMethod();//將返回的方法用變數保存起來
myFn();//此時的n=1,num=7    num沒有被銷毀,仍然保存著值。這便是閉包的精髓所在,有點像C#/JAVA裡面的靜態變數
myFn();//此時的n=1,num=8     

myMethod()();//如果這樣運行的話,輸出永遠是n=1,num=7    跟上面是有區別的,上面是用了個變數將其保存了起來。

閉包的優缺點如下:
優點:1、保護函數內部的變數安全,實現封裝
2、可以把這個變數當作快取來使用
弊端:無法銷毀變數,使用不當,容易造成記憶體泄漏

你可能會問,閉包的使用場景到底是什麼呢?
這個問題可能令你很是疑惑,我也想了很久。直到最近的一次面試,才豁然開朗。接下來看看js的防抖跟節流。

二、談談對防抖的理解

防抖,從字面上來理解,便是防止抖動。
這個,我們應該會經常接觸到,可能自己不知道而已。我們經常使用百度吧,沒事百度搜索一下。在這裡,當我們輸入關鍵詞的時候,會出現一些聯想詞,供我們去選擇。這裡面便用到了防抖。

試想,如果是我們,怎麼去實現這個功能,正常情況下,我們肯定會這樣寫

<input type="text" id="txt_test" />
//相應的js程式碼
var txt_test=document.getElementById("txt_test");
txt_test.oninput=function(){
    console.log(txt_test.value);
    //其他程式碼
    請求後端數據
}

對應的結果

這樣寫,的確不錯。每輸入一個字元,觸發一次輸入事件。

聯想到實際
現實中使用百度的人成千上萬,那每次輸入一個字元,請求後端一次,這麼多人的話同時使用百度的話,那請求也太多了,肯定會導致伺服器壓力賊大,我們知道,這些請求中很多是沒有什麼實際意義的,基於這種場景,怎麼優化呢?

試想一下,現實中,我們是怎麼搜索的?
當我們搜索ghost的時候,應該是快速地輸入,不可能一個字元一個字元的輸入的吧,那打字速度也忒慢了吧。當輸入完的時候,肯定會停頓一下,而這時候再去觸發一次輸入事件豈不正好?此時,防抖,應運而生。

function debounce(fn,wait) {
    var timeout = null; // 創建一個標記用來存放定時器的返回值
    return function () {      
        clearTimeout(timeout); //清除定時器
        //創建新的 setTimeout
        timeout = setTimeout(function(){
            fn();
        }, wait);
    };
}
// 處理函數
function handle() {
    console.log(document.getElementById("kw").value);
}
        
//輸入框事件
document.getElementById("kw").addEventListener('input', debounce(handle,5000));

以上的程式碼便是百度搜索中,不管怎麼輸入,只有在輸入完成後,停頓5s(這個時間故意設置這麼長的),才會觸發一次handle方法。

三、談談對節流的理解

我們應該也會經常接觸到,比如打開一個網頁,往下滾動的時候,會出現滾動條,當滾動到一定的程度時,會出現一個返回頂部的按鈕,點擊一下,便會返回到頂部。在這裡,便會用到節流。

試想,如果是我們,需要實現以上的功能,我們是如何寫程式碼的。
首先,先寫一個div通過position:fixed;display:none;固定到頁面右下角。然後寫一個滾動事件,

window.onscroll=function(e){
      //相應的處理程式碼
      handler();距離多少的位置,顯示按鈕或者隱藏按鈕。
}
function handler(){
      console.log("滾動事件觸發");
}

在這裡,當我們向下滾動頁面的時候,你會發現,這個onscroll事件觸發的頻率太高太高,稍微向下滾動一丟丟,就已經觸發了很多次,而且其中很多的觸發並沒有實在的意義,如何減少觸發的頻率,減少那麼多的計算操作呢?

現實中,螢幕刷新率一般在60HZ,我們看到的靜態畫面並不是完全不動的,只是動的太快,人眼無法覺察出,誤以為是不動的。如果過慢的話,我們自然會看得出來,過快的話,沒啥實際意義,刷新的過快了,通過人眼,我們還是認為螢幕是不動的。
既然這樣,那可不可以這樣?
在人眼識別的範圍內,間接地去觸發這個事件。豈不正好?這時候,節流便誕生了。

所謂的節流呢,其思想指的就是某些程式碼不可以在沒有間斷的情況下連續重複執行。類似的還有onresize等事件。

function throttle(fn,delay){
      var canRun=true;//通過閉包保存該變數
      return function(){
            if(!canRun) return;//立刻返回
            canRun=false;
            setTimeout(function(){
                  fn();
                  canRun=true;
            },delay);
      };
}

function handle(){
      console.log(123);
}

window.addEventListener("scroll",throttle(handle,2000));

以上的程式碼便是觸發窗口滾動事件後,每2s執行一次handle方法。

總結

以上的防抖跟節流的方法,都是比較基礎的方法。至於怎麼升級,就靠個人了
防抖跟節流都用到了閉包,
使用的都是計時器setTimeout,
防抖是某個事件觸發後幾秒後執行相應的方法,
而節流是某個事件觸發後周期性執行相應的方法。