1. 程式人生 > 程式設計 >詳細聊聊閉包在js中充當著什麼角色

詳細聊聊閉包在js中充當著什麼角色

目錄
  • 什麼是閉包
    • 閉包就是函式有權訪問另一個函式作用域中的變數,此函式和被引用的變數一起構成了閉包
  • 如何觀察閉包
    • 閉包的錯誤認識
      • 1.閉包的產生需要使用 return 暴露出去
      • 2.閉包會導致記憶體洩漏
    • 閉包導致的問題
      • 閉包的使用場景
        • 1. 單例模式
        • 2. 函式柯里化
        • 3. 與立即執行函式配合使用完成類庫的封裝
        • 4. 儲存私有變數
      • 總結

        什麼是閉包

        開篇明義,概念先行。閉包是什麼,為什麼說在中處處充滿了閉包。

        閉包就是函式有權訪問另一個函式作用域中的變數,此函式和被引用的變數一起構成了閉包

        文字描述文縐縐的難以理解,看一下程式碼就能夠一目瞭然了

        function test() {
            var a = 1
            var b = function() {
         
        console.log(a) } return b }

        在上面的程式碼例子中,變數 a 處於函式 test 的作用域中,但是函式 b 中可以對變數 a 進行訪問。
        套用閉包的概念,也就是函式 b 有權訪問函式 test 作用域中的變數 a,此時函式 b 與變數 a 就形成了一個閉包。

        看了上面的例子,大家是否恍然大悟,這不就是我們在程式碼中經常寫的嗎。所以說js中處處充滿了閉包。

        如何觀察閉包

        如果一開始我們對於閉包的認識還不是很深刻,我們怎麼知道在程式碼中寫出了一個閉包呢?一招教你找出閉包

        function test() {
            let a = 1
            return function test1() {
                debugger
                console.log(a)
            }
        }
        
        test()()

        如上的一段程式碼,在執行到 debugger 關鍵字的時候,我們可以開啟瀏覽器的開發者調製工具,此時我們可以從呼叫棧中看到 Closure 的字樣,這就是代表我們寫出了一個閉包啦

        詳細聊聊閉包在js中充當著什麼角色

        閉包的錯誤認識

        說完了閉包的概念,再來說說可能大家會對閉包產生的一些錯誤認識。

        1.閉包的產生需要使用 return 暴露出去

        首先從閉包的概念上來看就沒有說到閉包需要暴露到函式外才叫閉包,而是隻要引用了不屬於當前函式作用域中的變數就已經產生閉包了。

        為什麼會有這樣的錯誤認識,是因為我們使用閉包引用了外部作用域中的變數,一般是為了將這個變數或者是這個函式暴露出去,讓我們在外部也可以訪問到這個變數或者函式,也就是說將閉包暴露到函式外部只是我們的業務需求,而不是閉包的必要條件。

        詳細聊聊閉包在js中充當著什麼角色

        2.閉包會導致記憶體洩漏

        首先我們要知道為什麼閉包會導致記憶體洩漏,是因為我們將閉包暴露到函式外部的時候,閉包內部仍然引用著其外部作用域中的變數,導致外部作用域中的變數無法被垃圾回收機制回收,如果迴圈引用閉包的話就容易造成記憶體洩漏的現象。但這是由於我們在使用閉包過程中所引起的,而不是閉包本身的性質所決定的,因此說閉包一定會導致記憶體洩漏是不嚴謹的。

        (另外在 IE9 之後也對瀏覽器的垃圾回收機制做了優化,現在已經不容易導致記憶體洩露了)

        詳細聊聊閉包在js中充當著什麼角色

        閉包導致的問題

        作為 js 中八大陷阱之一的迴圈陷阱,就是由於閉包引起的

        for (var i = 0; i < 4; i++) {
            setTimeout(() => {
                console.log(i)
            },1000)
        }  // 4,4,4

        執行以上程式碼,會發現 1s 之後列印了 4個 4,為什麼不是列印 0,1,2,3,就是因為 setTimeout 中的回撥函式是一個閉包,引用了外部作用域中的 i 變數,但是 i 只有一個,並不會在每個回撥中生成新的 i,因此在 1s 後列印的時候訪問的是同一個作用域中的 i 變數,因此列印的結果就是 4個 4

        如何解決以上問題,有兩個方法:

        • 一種是使用 es6 的 let 語法生成塊級作用域,這樣每個塊級作用域中的 i 變數就不是指向同一個 i 變數,不會對彼此產生影響
        for (let i = 0; i < 4; i++) {
            setTimeout(() => {
                console.log(i)
            },1000)
        }  // 0,3
        • 一種是使用立即執行函式,每個立即執行函式中的變數 i 都是當前外部變數 i 的一個快照
        for (let i = 0; i < 4; i++) {
            (function(i) {
                setTimeout(() => {
                    console.log(i)
                },1000)
            })(i)
        }  // 0,3

        閉包的使用場景

        說了這麼多閉包的性質,甚至閉包還會引發迴圈陷阱這麼重大的問題,那麼閉包到底有什麼用?面試官問到的時候總不能說 js 處處都是閉包,所以 js 到處都是閉包的使用場景吧。那麼我們就來說說閉包的幾個經典使用場景

        1. 單例模式

        var CreateSingleton = (function() {
            var instance = null
            var CreateSingleton = function() {
                if (instance) return instance
                return instance = this
            }
            return CreateSingleton
        })()

        單例模式是設計模式的一種,目的是為了保證全域性中只有一個例項物件,上述程式碼利用 inswww.cppcns.comtance 建立一個閉包。單例模式在元件庫保證全域性中只有一個彈窗元件尤其好用。

        2. 函式柯里化

        柯里化是將一個多引數的函式轉化成幾個單引數的函式巢狀的形式,例如: function test(a,b,c) => function test(a)(b)(c)

        function currying(fn, args) {
          var _this = this
          var len = fn.length
          var args = args || []
        
          return FvAYWBWsfunction() {
            var _args = Array.prototype.slice.call(arguments)
            Array.prototype.push.apply(args,_args)
            
            if(_args.length < len) {
              return currying.call(this,fn,_args)
            }
        
            return fn.apply(this,_args)
          }
        }

        3. 與立即執行函式配合使用完成類庫的封裝

        閉包往往配合著立即執行函式來一起使用,能夠發揮出強大的效果。因此,往往很多人容易被誤導,認為閉包與立即執行函式之間有什麼關係,甚至認為立即執行函式就是閉包。但這種認識其實是錯誤的,立即執行函式與閉包之間沒有任何關係。

        在 盛行的年代,各類規範百花爭豔,其中 umd 規範能夠相容多種環境,主要在於其 umd 頭部結構的實現

        (function( global,factory ) {
            "use strict";
            if ( typeof module === "object" && typeof module.exports === "object" ) {
                module.exports = global.document ?
                factory( global,true ) :
        	function( w ) {
                    if ( !w.document ) {
        		throw new Error( "jQuery requires a window with a document" );
                    }
                    return factory( w );
        	};
            } else {
        	factory( global );
            客棧}
        })( typeof window !== "undefined" ? window : this,function( window,noGlobal ) {})

        以上這種程式碼的寫法使用過 jQuery 開發的人應該非常熟悉吧。

        4. 儲存私程式設計客棧有變數

        在實際開發過程中,我們有時候需要對於計算結果進行快取,或者是儲存私有變數而不被外部訪問到,就可以使用閉包來實現。

        另外,在目前流行的兩大前端框架 和 React 中其實也大量用到了閉包進行相關功能的實現,具體大家可以自己翻翻原始碼啦~

        總結

        到此這篇關於閉包在js中充當著什麼角色的文章就介紹到這了,更多相關js中的閉包內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!