先看一段代碼
var scope = 'global scope'
function checkscope(){
var scope = 'local scope'
function f(){return scope}
return f()
}
checkscope()
最后的返回值是什么?肯定是'local scope'
再把代碼調(diào)整一下
var scope = 'global scope'
function checkscope(){
var scope = 'local scope'
function f(){return scope}
return f
}
var func = checkscope()
func()
到底是'globle scope'還是'local scope'可能你就拿不準了,正確的是'local scope'。可能你會納悶:checkscope()返回以后,其內(nèi)部的局部變量不是只在內(nèi)部可以訪問么,到外面怎么可以訪問了?而且checkscope()執(zhí)行完后,內(nèi)部變量不應該被回收了么?要明白為什么需要理解以下幾點:
- 詞法作用域
大多數(shù)現(xiàn)代編程語言包括Javascript都采用了詞法作用域:變量的作用域是在函數(shù)定義時決定的,而不是函數(shù)調(diào)用時決定的。所以,對f()內(nèi)部使用的變量,其作用域已經(jīng)被定義成了局部變量scope,不會再改變。 - 作用域鏈
(本段3.10節(jié))作用域鏈是一個對象列表或鏈表,這個鏈表定義了變量名被查找的順序。全局作用域鏈表只有一個元素,指向所有的全局變量;函數(shù)的作用域鏈表的第一個元素指向了本級作用域定義的所有變量,下一個元素指向上層作用域(嵌套當前函數(shù)的函數(shù)或全局代碼)的所有變量,再下一個元素又是更上層作用域的所有變量,直至全局作用域所有變量為止。
這就是為什么嵌套函數(shù)中的局部變量scope沒有被回收的原因,checkscope()執(zhí)行完畢后其作用域鏈不再指向局部變量,但其嵌套函數(shù)的作用域鏈還指向該局部變量,所以它就不會被垃圾回收。
以上就是閉包的含義:函數(shù)定義時的作用域鏈到函數(shù)執(zhí)行時依然有效。Javascript中所有的函數(shù)都是閉包的。
利用閉包有很多好處,它可以用局部變量用來存儲私有狀態(tài)。來看一個計數(shù)器的例子
function counter(){
var n = 0
return{
count:function(){return n++},
reset:function(){n=0}
}
}
var a = counter(), b = counter()
undefined
a.count() //0
b.count() //0
a.count() //1
b.count() //1
a.count() //2
b.reset()
a.count() //3
b.count() //0
利用參數(shù)也可以實現(xiàn)閉包:
function counter(n){
return{
get count(){return n++},
set count(m){
if (m>=n) n=m
else throw Error('count can only be set a larger value')
}
}
}
var a = counter(100)
a.count
100
a.count
101
a.count = 99 //VM539:6 Uncaught Error: count can only be set a larger value
a.count = 111
111
a.count
111
a.count
112
在循環(huán)中使用閉包要注意將循環(huán)代碼定義包閉包之外,先看負面例子
function funcgen(){
var funcs = []
for(var i=0;i<10;i++){
funcs[i] = function(){return i}
}
return funcs
}
var fs = funcgen()
fs[2]() //10
得到了意外的結果10,這是因為funcs[0]~funcs[9]這10個函數(shù)都共享了funcgen的局部變量i,當funcgen()返回時,i的值已經(jīng)變成了10。下面是正確的用法:
function funcgen(i){
return function (){ return i}
}
var funcs =[]
for (var i=0;i<10;i++){
funcs[i] = funcgen(i)
}
funcs[2]() //2
funcs[5]() //5
嵌套函數(shù)不會將上層函數(shù)的變量作為自己的私有成員,不會復制一份給自己用,它是和其他嵌套函數(shù)共享的,共享的方式就是各自的作用域鏈指向相同的變量地址。
需要注意
- this是關鍵字不是一個變量,不能直接在閉包中使用。需要訪問的話將它賦值給一個變量才行,
var self = this
- 每個函數(shù)調(diào)用時都會有自己的arguments,所以閉包內(nèi)無法直接訪問上層函數(shù)的arguments。如果需要訪問也要把它存放在一個變量之中,
var outerArugments = arguments
。