看過很多的關于閉包的講解嚷硫,寫個進階總結吧膨处。
閉包在《JavaScript權威指南》定義是:函數(shù)對象本身和這個函數(shù)關聯(lián)作用域鏈的結合筹误。
想理解上面??的話就要知道在JavaScript作用域相關的有:
- 變量作用域
- 全局作用域
- 本地作用域
- 函數(shù)作用域
函數(shù)作用域可以決定變量的作用域范圍思犁。一個在函數(shù)中被定義的變量,在這個函數(shù)體中,以及該函數(shù)體中嵌套定義的所有函數(shù)內部都可見。另外和同名本地變量覆蓋全局變量同理棉磨,嵌套函數(shù)中的同名本地變量也會覆蓋其上層函數(shù)中的本地變量缤剧。
其實變量的本質就是通過對象來組織到一起的屬性集合。定義全局變量就是在全局對象(window)上定義了一個屬性
var a = {};
window.a ==== a //true
所以對于本地變量的話缎除,相當于某個對象的屬性严就,只是沒有辦法獲取到這個對象。這個對象被稱為 調用對象 或者 聲明上下文 伴找。
引出作用域鏈
為了可以定位到具體指向哪個變量盈蛮,就需要作用域鏈。
作用域鏈:JavaScript每一個代碼塊都會與一個作用域鏈相關聯(lián)技矮,這個鏈是一個對象列表抖誉,對于每一個標識符,都依次從這個鏈的對象中查找具有相同標識符的屬性衰倦。而作用域鏈就是由全局變量和不定數(shù)量的由函數(shù)調用產(chǎn)生的調用對象構成的列表
var x = 1;
var y = 2;
// 代碼執(zhí)行到此袒炉,為了確定x和y,查詢其作用域鏈: [ window ]樊零,從 window.x 和 window.y 中取出了值
console.log( x + y );
function fnA(){
// 函數(shù)被調用我磁,產(chǎn)生了fnA的調用對象(即為a),用于查詢變量的作用域鏈為:[ a, window ]
var x = 2;
// a.x 定位到x驻襟,a.y不存在夺艰,繼續(xù)從 window.y 中定位到y(tǒng)
console.log( x + y );
}
fn();
注意函數(shù)剛定義的時候還沒有調用對象存在,所以當函數(shù)嵌套多的話沉衣,作用域鏈就很長
引出詞法作用域
詞法作用域(lexical scoping)是指郁副,函數(shù)在執(zhí)行時,使用的是它被定義時的作用域豌习,而不是這個函數(shù)被調用時的作用域存谎。
var scope = 'global scope';
function checkScope(){
var scope = 'local scope';
return function(){
console.log( scope );
}
}
var f = checkScope();
f(); // local scope
實際該函數(shù)執(zhí)行時‘fn()’時作用域中的‘scope’是 ‘global scope’ 但實際執(zhí)行使用的是定義函數(shù)時那個‘local scope‘,因為函數(shù)在執(zhí)行時使用的作用域鏈是它在定義時綁定的那個作用域鏈(準確地說是使用當時綁定的那個作用域鏈加上新建的調用對象組成的新的作用域鏈)肥隆,而不是函數(shù)調用時所處上下文的作用域鏈既荚。
最后理解閉包后,閉包的強大之處其實在于:JavaScript中的函數(shù)栋艳,通過作用域鏈和詞法作用域兩者的特性恰聘,將該函數(shù)定義時的所處的作用域中的相關函數(shù)進行了捕獲和保存,從而可以在完全不同的上下文中進行引用。
應用場景
保護函數(shù)內的變量安全:如迭代器憨琳、生成器诫钓。
在內存中維持變量:如果緩存數(shù)據(jù)、柯里化篙螟。