1. 程式人生 > >Javascript 閉包淺析(一)

Javascript 閉包淺析(一)

javascrip turn 所在 閉包 for循環 err 永久 error ref

閉包

對於Javascript程序員來說,閉包(closure)是一個難懂又必須征服的概念。閉包的形成與變量的生存周期密切相關。

變量的作用域
變量的作用域,指變量的有效範圍。我們最常談到的是在函數中聲明的變量作用域。
當在函數中聲明一個變量的時候,如果該變量前面沒有帶上關鍵在var,這個變量就會成為全局變量,這當然是一種容易造成命名沖突的做法。(ES5嚴格模式下報錯)
另一種情況是用var關鍵字在函數中聲明變量,這時候的變量既是局部變量,只有在該函數內部才能訪問到這個變量,在函數外面是訪問不到的。
在Javascript中,函數可以用來創造函數作用域,在函數裏面可以看到外面的變量,而在函數外面看不到函數裏面的變量。因為在函數中搜索一個變量的時候,如果該函數內並沒有聲明這個變量,那麽此次搜索的過程會隨著代碼執行環境創建的作用域鏈往外層逐層搜索,一直搜索到全局對象為止。變量的搜索是從內到外而非從外到內。

例:
var a = 1
var func1 = function(){
var b = 1
var func2 = function(){
var c = 3
console.log(b) // 1
console.log(a) // 1
}
func2()
console.log(c) // 輸出: Uncaught ReferenceError: c is not defined
}
func1()

變量的聲明周期
除了變量的作用域之外,另外一個跟閉包有關的概念是變量的生存周期
對於全局變量來說,全局變量的生存周期當然是永久的,除非我們主動銷毀這個全局變量。
而對於在函數內用var關鍵字聲明的局部變量來說,當退出函數時,這些局部變量即失去了讓門的價值,它們都會隨著函數調用的結束而被銷毀。

例:
var func = function(){
var a = 1; // 退出函數後局部變量a將被銷毀
console.log(a)
}
func()

現在來看這段代碼
var func = function(){
var a = 1
return function(){
a++
console.log(a)
}
}
var f = func()

f() //2
f() //3
f() //4
f() //5

當退出函數後,變量a並沒有被銷毀,而是一直在某個地方存活著。這個因為當執行var f = func()時,f返回了一個匿名函數的引用,他可以訪問到func()被調用時產生的環境,而局部變量a一直處在這個環境裏。既然局部變量所在的環境還能被外界訪問,這個局部變量就有了不被銷毀的理由。在這裏產生了一個閉包的結構,局部變量的生命看起來被延續了

閉包經典例子:
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
<div>5</div>

<script>
var nodes = document.getElementsByTagName(‘div‘)

for(var i = 0, len = nodes.length; i < len; i++){
nodes[i].onclick = function(){
console.log(i)
}
}
</script>

這段代碼無論點擊那個div結果都是5,這是因為div節點的onclick事件是被異步觸發的,當事件被觸發的時候,for循環早已經結束了,此時的i的值已經是5,當onclick事件函數中順著作用域鏈從內到外查找變量i時,查找到的總是5

解決方案:
for(var i = 0, len = nodes.length; i < len; i++){
(function(i){
nodes[i].onclick = function(){
console.log(i)
}
})(i)
}

實際應用:
var Type = {}

for(var i = 0, type; type = [‘String‘, ‘Array‘, ‘Number‘][i++];){
(function(type){
Type[‘is‘ + type] = function(obj){
return Object.prototype.toString.call(obj) === ‘[object ‘ + type + ‘]‘
}
})(type)
}

Type.isArray([]) // true
Type.isString(‘str‘) // true

Javascript 閉包淺析(一)