js中爲何要使用閉包?瀏覽器
先介紹一下全局變量和局部變量的優缺點:閉包
全局變量:在全局環境下聲明的變量爲全局變量,全局變量在任何地方均可訪問,且一直保存在內存中只到應用程序退出(關閉網頁或瀏覽器)時才被銷燬。可是過多的聲明全局變量容易形成全局污染,且全局變量容易被修改。函數
局部變量:在函數環境下聲明的變量爲局部變量,局部變量僅在函數內部可訪問,當函數執行完畢時就會被銷燬。局部變量不會形成全局污染也不容易被修改。this
從上面能夠看出全局變量和局部變量的優缺點恰好是相對的,閉包的出現正好結合了全局變量和局部變量的優勢。閉包可以使已經執行結束的函數中的局部變量仍然留在內存中,且能被重複訪問使用。對象
閉包的定義是什麼?blog
閉包是指有權訪問另外一個函數做用域中的變量的函數。建立閉包的常見方式就是在一個函數內部建立另外一個函數。以一個函數爲例:ip
示例內存
加粗的兩行代碼是內部函數(一個匿名函數)中的代碼,這兩行代碼訪問了外部函數中的變量propertyName。作用域
示例io
在匿名函數從createComparisonFunction()中被返回後,他仍然能夠訪問在createComparisonFunction()中定義的全部變量。更爲重要的是,createComparisonFunction()函數在執行完畢後,其變量對象也不會被銷燬,由於匿名函數的做用域鏈仍然在引用這個變量對象。
什麼是做用域?
要理解什麼是做用域,要先理解什麼是執行環境
1.什麼是執行環境(執行上下文)
當代碼在JavaScript中運行的時候,代碼在環境中被執行是很是重要的,它會被評估爲如下之一類型來運行:
全局代碼:默認環境,代碼第一時間在這兒運行。
函數代碼:當執行流進入一個函數體的時候。
Eval代碼:在eval()函數中的文本。
來看一個例子:
示例
全局環境由紫色邊框表示,還有三個不一樣的函數環境分別由綠色邊框,藍色邊框和橙色邊框表示。這裏只能有一個全局環境,全局環境能夠被其餘環境訪問。能夠有不少的函數環境,每一個函數都會建立一個新的函數環境,在新的函數環境中,會建立一個私有做用域,在這個函數中建立的任何聲明都不能被當前函數做用域以外的地方訪問。
2.執行環境的詳情
一個函數被調用就會建立一個新的執行環境。然而解釋器的內部,每次調用執行環境會有兩個階段:
1). 建立階段
- 當函數被調用,可是爲執行內部代碼以前:
- 建立一個做用域鏈。
- 建立變量,函數和參數。
- 肯定this的值。
2). 激活/代碼執行階段
> - 賦值,引用函數,解釋/執行代碼。
這意味着每一個執行環境在概念上做爲一個對象並帶有三個屬性
executionContextObj = {
scopeChain: { /* variableObject + all parent execution context's variableObject */ },
//做用域鏈:{變量對象+全部父執行環境的變量對象}
variableObject: { /* function arguments / parameters, inner variable and function declarations */ },
//變量對象:{函數形參+內部的變量+函數聲明(但不包含表達式)}
this: {}
}
看下面例子:
在調用foo(22)時,建立階段像下面這樣:
fooExecutionContext = {
scopeChain: { ... },
variableObject: {
arguments: {
0: 22,
length: 1
},
i: 22,
c: pointer to function c()
a: undefined,
b: undefined
},
this: { ... }
}
建立階段處理了定義屬性的變量名,可是並不把值賦給變量,不包括形參和實參。一旦建立階段完成,執行流進入函數而且激活/代碼執行階段,在函數執行結束以後,看起來像這樣:
fooExecutionContext = {
scopeChain: { ... },
variableObject: {
arguments: {
0: 22,
length: 1
},
i: 22,
c: pointer to function c()
a: 'hello',
b: pointer to function privateB()
},
this: { ... }
}
上述過程也能夠印證了js中的變量提高,即變量和函數聲明會被提高到它們函數做用域的頂端。
理解了什麼是執行環境和做用域鏈以後,回到上文所講的閉包實例。
爲何createComparisonFunction()函數在執行完畢後,其變量對象不會被銷燬。
示例
在匿名函數從createComparisonFunction()被返回後,它的做用域鏈被初始化爲包含createComparisonFunction()函數的變量對象和全局變量對象。這樣,匿名函數就能夠訪問在createComparisonFunction()中定義的全部變量。當createComparisonFunction()函數返回後,其執行環境的做用域鏈會被銷燬,但它的變量對象仍然會留在內存中;只到匿名函數銷燬後,createComparisonFunction()函數的變量對象纔會被銷燬。下圖展現了調用compareNames()過程當中產生的做用域鏈之間的關係:
示例
解除對匿名函數的引用,只需compareNames=null便可,此時createComparisonFunction()函數的變量對象會被銷燬。
閉包的做用?
事實上,經過使用閉包,咱們能夠作不少事情。好比模擬面向對象的代碼風格;更優雅,更簡潔的表達出代碼;在某些方面提高代碼的執行效率。這些須要感興趣的人本身去實踐和探索,此處不一一列舉。
閉包的缺點?
因爲閉包會攜帶包含它的函數的做用域,所以會比其餘函數佔用更多的內存。過分使用閉包可能會致使內存佔用過多,因此要慎重使用閉包。