聊一聊閉包

前言

閉包是何時被建立的,何時被銷燬的?具體的實現又是怎麼樣的?javascript

一開始學閉包的時候,囫圇吞棗😔,但願此次能夠靜下來好好琢磨琢磨,對閉包有更深的理解。html

🤭爲了更好的理解閉包,有必要先了解做用域跟執行上下文相關的內容:java

JavaScript執行上下文-執行棧bash

你不知道的Javascript動態做用域閉包

話很少說,上來就是一個思考題🤔異步

var name = "The Window";
        var object = {
            name: "My Object",
            getNameFunc: function () {
                return function () {
                    return this.name;
                };
            }
        };
        alert(object.getNameFunc()());
複製代碼

👆固然了js基礎紮實的,必定明白裏面的端詳,看完一下內容,對於這道題必定就能遊刃有餘的解決了✊函數

什麼是閉包

來看看紅寶石怎麼說的👇post

閉包是指有權訪問另一個函數做用域中的變量的函數
複製代碼

先來看看MDN定義👇ui

函數和對其周圍狀態(**lexical environment,詞法環境**)的引用捆綁在一塊兒構成**閉包**(**closure**)。也就是說,閉包可讓你從內部函數訪問外部函數做用域。在 JavaScript 中,每當函數被建立,就會在函數生成時生成閉包。
複製代碼

👆上面定義大概就是:閉包是指那些可以訪問自由變量的函數。其中自由變量,指在函數中使用的,但既不是函數參數arguments也不是函數的局部變量的變量,其實就是另一個函數做用域中的變量。this

網上對於閉包的定義各類各樣,每一個人對閉包的理解不一樣,天然定義就不一樣,那麼我也不去定義閉包,本身意會吧👊
複製代碼

非要理解的話,有兩種理解:

1️⃣閉包是嵌套的內部函數(絕大數人)

2️⃣包含被引用變量(或函數)的對象(極少數人)

我以爲閉包存在與嵌套的內部函數中😊

咱們針對第二個理解,寫一個demo👇

function count () {
            var x = 0
            return {
                add() {
                    x++;
                },
                print() {
                    console.log(x)
                }
            }
        }
        let demo = count();
        demo.add()
        demo.print()
        demo.add()
        demo.print()
複製代碼

嗯🙃 只可意會不可言傳,往下再看看吧!

閉包的生命週期

閉包產生緣由

首先要明白做用域鏈的概念,其實也很簡單,在ES5中只存在兩種做用域

1️⃣全局做用域 2️⃣局部做用域

當訪問一個變量時,解釋器會首先在當前做用域查找標示符,若是沒有找到,就去父做用域找,直到找到該變量的標示符或者不在父做用域中,這就是做用域鏈。 值得注意的是,每個子函數都會拷貝上級的做用域,造成一個做用域的鏈條。 好比:

var x = 1;
function demo() {
  var x = 2
  function demo2() {
    var x = 3;
    console.log(x);//3
  }
}
複製代碼

上面這段代碼中,我我的的理解demo函數做用域指向全局做用域(window)和它自己,而demo2函數的做用域指向全局做用域(window)丶demo 和它自己。而且的話,做用域的查找是從最底層開始向上找的,直到找到全局做用域window爲止,若是全局做用域尚未找到的話,就會報錯❌

閉包產生的本質💯 當前環境中存在指向父級做用域的引用。仍是舉上面的例子:👇

var x = 1;
        function demo() {
            var x = 2

            function demo2() {
                console.log(x); 
            }
            return demo2;
        }
        var h = demo();
        h()   // 2
複製代碼

這裏h變量會拿到父級做用域的變量,輸出2。在當前的環境中,含有對demo2的引用,demo2偏偏引用了window demo 和 自己的做用域。 所以demo2能夠訪問到demo做用域中的變量。

問題來了? **是否是隻有返回函數纔會產生閉包呢?**❓

回到閉包實質:只須要讓父級做用域的引用存在便可 那麼咱們能夠這麼作:point_down:

var demo2;
        function demo() {
            var x = 2

            demo2 = function () {
                console.log(x); 
            }
            
        }
        demo();
        demo2()    // 2
複製代碼

首先讓外部函數執行,給demo2賦值,等於demo2如今擁有了window demo 和 自身的做用域的訪問權限,那麼查找變量x的時候,會逐級的向上去找,在最近的demo做用域找到標示符就返回結果,輸出2。

🤭 在這裏外部的變量demo2存在父級做用域的引用,所以產生了閉包,形式變了,本質仍是沒有改變。

閉包的表現形式🤤

明白了本質,咱們從真實的場景中出發,究竟在哪些地方能體現閉包的存在❓

1️⃣返回一個函數,上面已經舉例子了

:two:做爲函數參數傳遞

var a = 1;
function foo(){
  var a = 2;
  function baz(){
    console.log(a);
  }
  bar(baz);
}
function bar(fn){
  // 這就是閉包
  fn();
}
// 輸出2,而不是1
foo();
複製代碼

:three:在定時器、事件監聽、Ajax請求、跨窗口通訊、Web Workers或者任何異步中,只要使用了回調函數,實際上就是在使用閉包

如下的閉包保存的僅僅是window和當前做用域。

// 定時器
setTimeout(function timeHandler(){
  console.log('2222');
},100)

// 事件監聽
$('#div').click(function(){
  console.log('DIV Click');
})
複製代碼

:four:IIFE(當即執行函數表達式)建立閉包, 保存了全局做用域window當前函數的做用域,所以能夠全局的變量。

var x = 22;
(function IIFE(){
  // 輸出22
  console.log(x);
})();
複製代碼

產生的條件

1️⃣函數嵌套

:two:內部函數引用了外部函數的數據(變量/函數)

銷燬閉包

說到閉包的銷燬,得先聊一聊的就是V8的垃圾回收機制

V8垃圾回收機制,我想到時候我會開一個新的章節去接受它🤭

閉包優缺點

總結

  • 我的理解:閉包產生的本質💯 當前環境中存在指向父級做用域的引用
  • 被引用的變量直到閉包被銷燬時纔會被銷燬

參考

發現JavaScript中閉包的強大威力

阮一峯閉包

JavaScript閉包的底層運行機制

發現 JavaScript 中閉包的強大威力

相關文章
相關標籤/搜索