理解Javascript的閉包

tony0087發表於2021-09-09

大綱

  • 閉包真言
  • 理解閉包情景一:函式作為返回值
  • 理解閉包情景二:函式作為引數傳遞到其他函式中
  • 理解閉包情景三:迴圈和閉包
  • 實際開發中閉包的應用
  • 閉包優缺點

閉包真言

  • JavaScript中閉包無處不在,你只需要能夠識別並擁抱它。
  • 當函式可以記住並訪問所在的詞法作用域,即使函式是在當前詞法作用域之外執行,這時就產生了閉包。

理解閉包需要搞懂JS的作用域,。

理解閉包

閉包情景一:函式作為返回值

function foo() {
    var a = 2;

    function bar() {
        console.log(a);
    }

    return bar;
}

var baz = foo();

baz(); // 2

我們知道 bar 函式里面的 a 變數在外面是訪問不到的,但呼叫 baz 方法為什麼仍然能輸出 2 呢?

原因:

  • 首先,呼叫 foo 函式把 bar 方法函式作為返回值,且賦值了給 baz 變數。
  • 然後,baz 變數就存在了對 bar 函式的引用。
  • 最後,呼叫 baz 方法時,a去它的父級作用域(foo函式)查詢得到該值,所以就輸出了 a 值。

這就是閉包的作用之一:把函式作為返回值,外部的作用域可以能訪問到函式作用域內部的變數。

閉包情景二:函式作為引數傳遞到其他函式中

function foo() {
    var a = 2;

    function baz() {
        console.log(a); // 2
    }

    bar(baz);
}

function bar(fn) {
    fn();
}

foo()

函式作為引數傳遞到其他函式中的原理和函式作為返回值差不多:

  • foo 函式的 bar 方法把內部的 baz 方法傳入外部的 bar 方法中。
  • 外部的 bar 方法呼叫了傳入的baz方法,a去它的父級作用域(foo函式)查詢得到值,所以就輸出了 a 值。

這就是閉包的作用之二:函式作為引數傳遞到其他函式中,外部函式作用域也可以訪問到其他函式內部作用域的變數。

閉包情景三:迴圈和閉包

需求:

在迴圈裡面新增一個定時器,想預期每秒列印一次對應的值,如按順序輸出1,2,3,4,5

程式碼:

以下程式碼是不能完成預期結果,都輸出 6,6,6,6,6,原因:i 都是同一個引用for迴圈最後的結果: 6

for (var i = 1; i <= 5; i++) {
    setTimeout(function timer() {
        console.log(i);
    }, i * 1000);
}

改進方案1

  • i 當做引數傳入一個立即執行函式中,立即執行函式每次迭代時都建立一個新的作用域。
  • 這時候 i 擁有了自己的函式內部作用域,儲存到每個對應的 i 值 ,就能到達預期的輸出 1, 2, 3, 4, 5結果。
for (var i=1; i<=5; i++) {
    (function(j) {
        setTimeout( function timer() {
            console.log( j );
        }, j*1000 );
    })( i );
}

改進方案2

方案1可知,使用ES6的 let 宣告也有塊級作用域。

for迴圈頭部的let宣告,每次迭代都會宣告一次 i,那麼每個 i 就有了自己的作用域,也能達到預期輸出結果。

for (let i = 1; i <= 5; i++) {
    setTimeout(function timer() {
        console.log(i);
    }, i * 1000);
}

這就是閉包的作用之三:能讓一個變數長期儲存在記憶體中。

實際開發中閉包的應用

收斂許可權

// 檢測一個數字是否被使用過
function isFirstLoad() {
    var _list = []
    return function (id) {
        if(_list.indexOf(id) >= 0) {
            return false
            
        }else {
            _list.push(id)
            
            return true
        }
    }
}

// 使用
// 如果第一次使用該數字則返回true,反之返回false
var firstLoad = isFirstLoad()
firstLoad(10) // true
firstLoad(10) // false
firstLoad(20) // true

建立10個標籤,點選的時候彈出來對應的序號

// 建立10個<a>標籤,點選的時候彈出來對應的序號
for (var i = 0; i < 10; i++) {
    (function (i) {
        var a = document.createElement('a')
        a.innerHTML = i + '<br>'
        
        a.addEventListener('click', function (e) {
            e.preventDefault()
            console.log(i)
        })
        document.body.appendChild(a)
    })(i)
}

閉包優缺點

優點

  • 能讓一個變數長期儲存在記憶體中。
  • 避免全域性變數的汙染。
  • 私有成員的存在。

缺點

  • 常駐記憶體,增加記憶體使用量。
  • 使用不當會很容易造成記憶體洩露。

參考連結

  • by, 雙越老師
  • , by 雲霏霏

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/1747/viewspace-2822851/,如需轉載,請註明出處,否則將追究法律責任。

相關文章