雲天視角-淺談閉包

1、現狀

閉包是jser繞不過的坎,一直在都在說,套用 simpson 的話來講:JavaScript中閉包無處不在,你只須要可以識別並擁抱它。前端

閉包是基於詞法做用域書寫代碼時的天然結果,你甚至不須要爲了利用它們而有意識的去建立閉包。閉包的建立和使用在你的代碼中隨處可見。你缺乏的只是根據你的意願來識別、擁抱和影響閉包的思惟環境數組

2、什麼是閉包(closure)

當函數能夠記住並訪問所在的詞法做用域時,就產生了閉包。即便函數是在當前詞法做用域以外執行 --《你不知道的js》(上卷)
閉包是指有權訪問另外一個函數做用域中的變量的函數 --《JavaScript高級程序設計》

先來看一個例子:
例子1:閉包

function foo(){
    var a = 2;
    
    function bar(){
        console.log(a); // 2
    } 
    bar();
}

foo()

這是閉包嗎?
這個代碼從技術上來講是,但也能夠說不是。準確的來講bar()對a的引用的方法是詞法做用域的查找規則。咱們再來看:
例子2:函數

function foo(){
    var a = 2;
    
    function bar(){
        console.log(a)
    }
    
    return bar;
}
var baz = foo();
baz(); // 2, 這就是閉包了

在例2中,咱們將bar()函數自己當作一個值類型進行傳遞,函數bar()可以訪問foo()的內部做用域。在這個例子中,它在本身定義的詞法做用域之外的地方執行。spa

3、怎麼造成的

要了解清楚,得先了解幾個概念設計

  • 做用域鏈(scope chain)
  • 詞法做用域

詞法做用域

每一個函數都有本身的執行環境。這個環境能夠訪問外部環境,以此類推。每一個環境能訪問到的標識符集合,稱之爲 做用域,也就是詞法做用域code

做用域鏈(scope chain)

將做用域一層一層的嵌套,就造成了做用域鏈對象

以下,一般咱們都但願foo()在執行完成之後,整個的內部做用域都被銷燬。由於咱們知道引擎有垃圾回收機制用來釋放再也不使用的內存空間。因爲看上去foo()的內容不會再被使用,因此很天然的想到會對其回收。可是,事實上內部做用域依然存在blog

var globalVar = 10;
function foo() {
    var fooVar = 20;
    function bar() {
        var barVar = 30;
        return globalVar + fooVar + barVar;
    }
    return bar;
}
var baz = foo();
baz();

如上,用一張圖表示
做用域鏈索引

這個做用域鏈在函數建立的時候就保存起來了。

baz()函數在執行的時候(執行bar()函數),將當前的變量對象(因爲當前的環境是函數,因此將其活動對象做爲變量對象)添加到做用域鏈的前端。此時,因爲bar()在執行,而做用域鏈也存在,因此能夠在做用域鏈上進行查找,去訪問foo()的變量。

4、閉包的應用場景有哪些

  • 建立私有變量或函數

5、閉包的缺點

  • 閉包中的值是存在於內存中,濫用的話會致使內存消耗過大

閉包經典問題

// 函數做用:但願它返回一個數組。該數組的元素爲遍歷的索引值
function hello(){
    var res = [];
    for (var i = 0,len = 5;i < len;i++){
        res[i] = function () {
            return i;
        }
    }
    return res;
}

返回的結果跟咱們期待的不同,由於:閉包保存的是整個變量對象,而不是每一個變量。
解決方案:

function hello(){
    var res = [];
    for (var i = 0,len = 5;i < len;i++){
        res[i] = (function(i){
            return i;
        })(i)
    }
    return res;
}

這裏,沒有沒有把閉包直接賦值給數組。而是定義了一個匿名函數,而且將當即執行該匿名函數的結果賦值給數組,因爲參數是按值傳遞的,因此會將當前值傳給參數num。

參考資料:《你不知道的js》(中卷)、《JavaScript高級程序設計》

相關文章
相關標籤/搜索