前言
咳咳,葉大俠病了,昨天晚上回家時候在車上就不舒服,果然回來就掛了,本來還想今天接着上班撐下去的。但是昨天又看到一個IT巨子掛了,所以果斷請了一個假!!!
但是早上7.00左右就迷迷糊糊的醒了,於是我在想我是不是該“身殘志堅”一番。。。。
咳咳,以上玩笑,我們最近一起學習了很多CSS的東西,相信大家的CSS水平必定提高了吧???所以我們接下來一段時間來看看javascript吧,今天我們一起來看看閉包這個家伙!
本文參考:
http://www.cnblogs.com/lhb25/archive/2011/09/06/javascript-scope-chain.html
http://www.cnblogs.com/frankfang/archive/2011/08/03/2125663.html
http://www.cnblogs.com/xiaotie/archive/2011/08/03/2126145.html
執行上下文(execution context)
又是上下文。。。無論是曾經學習.net時候的httpcontext,還是最近學習的block formatting context,都是上下文!
我們可以感覺到上下文的存在,就如感覺得到空氣一樣,我們卻將它拿不出來,於是我們來看看執行上下文吧。
每次當控制器轉到ecmascript可執行代碼的時候,就會進入一個執行上下文。
活動的執行上下文組在邏輯上為一個堆棧,堆棧最底部一定是全局上下文(global context),頂部便是當前上下文
這個句話是很有玄機的哦,比如我們在一個函數環境中,我們首先用的是當前變量,若是沒有就在父級函數中找,最后就在window中找。
PS:我們在寫博客時候,中文沒有問題,突然打一個字母上,你會發現整行都下移了1/2px,各位知道神馬原因么???:)
eval在執行時候會產生一個調用上下文的東東(calling context),所以eval中定義的變量會影響函數環境中的變量。
作用域
閉包有作用域鏈有很多說不清道不明的關系,所以我們還是先看看作用域吧。
任何語言都會有作用域的概念,作用域便是變量與函數可訪問范圍,簡稱變量與函數的生命周期。
javascript中分為全局與局部兩種,直白點window與function
var name = '葉小釵'; function func() { var id = '刀狂劍痴'; function alertId() { alert(id); } alertId(); } alert(name);//葉小釵 alert(id);//錯誤 func(); //刀狂劍痴 alertId();//錯誤
此處name屬於window屬性便是全局的(有些瀏覽器自帶name屬性小心陷阱!)
func也是全局的,但是func這個作用域鏈包含了全局的,所以他可以訪問外部的,但是外部不能訪問進來。
鏈子。。。
在js中什么都是對象,函數和其它對象一樣擁有很多屬性,其中一個比較特殊的屬性是:
[[scope]],scope翻譯過來就是作用域,該屬性包含了函數被創建的作用域中的對象的集合:
於是傳說中的作用域鏈誕生了,他決定了哪些數據能被哪些函數訪問。
當一個函數創建后,他的作用域鏈會被創建此函數的作用域中可訪問的數據對象填充:
function add (a, b) { var sum = a + b; return sum; }
在函數add創建時,他的作用域鏈中會填入一個全局對象,我們可以想象為window被壓入了其[[scope]]
函數add的作用域在調用時會被用到,此時會創建一個“運行時上下文(execution context)”的內部對象,
他定義了函數執行時的環境,每個運行時上下文都有自己的作用域鏈,用於標識符解析,
當execution context創建時,而他的作用域鏈初始化為當前運行函數的[[scope]]所包含的對象。
所有相關的東東會按照其在函數中出現的順序被復制到運行時上下文的作用域鏈中,他們共同組成了“活動對象(activation object)”,該對象就包含了函數的所有局部變量,參數,以及this,然后會排在我們作用域堆棧的頂端,我們可以最先獲取,在運行時上下文結束(函數執行完畢)后,GC便會回收其空間。
在函數執行過程中,遇到一個變量便會經歷一次標識符解析以決定從哪里獲取以及存儲數據。
這個過程首先從我們的“堆棧”頂端開始搜索,也就是活動對象的開始,若是有該變量便獲取值,
若是沒有則向下,知道window為止,沒有就報錯!
內存溢出
是想我們的瀏覽器內存若是搞滿了,自然會瀏覽器崩潰,這是我們閉包之中需要考慮到的東西,因為他會導致我們一些東西無法GC。
閉包,神奇的魔法
閉包是神奇的魔法,因為他干了很多“壞事”,達到了出人意料的結果。
所謂閉包,便是function嵌套function,內部function可以訪問外部的變量。
若是外部函數返回了內部函數,那么就閉了一個包
1 function a() { 2 var name = '葉小釵'; 3 function alertName() { 4 alert(name); 5 } 6 return alertName; 7 } 8 9 var func = a(); 10 func();//葉小釵
這個家伙是個壞孩子,怎么說呢,壞孩子招人疼啊!
我們看第9行,按我們之前的理解a執行結束后,整個活動對象是不是該被銷毀?是不是與a有關的東西都該GC呢?
確實是這樣的,但是我們這里就產生了一個閉包,阻止了其銷毀,因為他里面的變量被用到了。
這里的一個事實便是外部的func訪問到了a內部的變量!
一個經典的例子:
1 function outer() { 2 var o = {}; 3 for (var i = 0; i < 10; i++) { 4 o[i] = function () { 5 alert(i); 6 } 7 } 8 return o; 9 } 10 var funcs = outer(); 11 for (var k in funcs) { 12 funcs[k](); 13 } 14 var s = '';
我們來看這個,大家應該都比較熟悉了,我們知道他全部會打印10。
這是因為我們將outer給予funcs時候,outer的任務就完成啦,他現在內部的i便是10,我們現在再來調用函數,所有的i自然是指向一個[[scope]],所以變量i就是10!!!
要怎么解決大家也一目了然:
1 function outer() { 2 var o = {}; 3 for (var i = 0; i < 10; i++) { 4 o[i] = (function (i) { 5 alert(i); 6 })(i) 7 } 8 return o; 9 } 10 var funcs = outer(); 11 for (var k in funcs) { 12 funcs[k](); 13 } 14 var s = '';
相當於每次i是以一個副本的方式重新復制了一番,這個是可以達到我們的目的,但是有時候會對性能有一點影響,我們還是需要注意。
我們這里就不扯遠了,在扯遠的話我估計也露餡了。。。呵呵,最后我們來說一點應用吧。
閉包的應用
jquery時如何神奇的使用閉包的我現在還看不透,那是我1年后的任務,我這不關注他。
我這里說的應用時原來項目過程中遇到的,所以沒有代碼,我這邊說下問題吧:
我們點擊一個按鈕,會向服務器發一個ajax請求,其中包含一些data,然后數據返回回來后我們又會用到開始傳過去的一些data中的敏感信息,比如id。
這個時候一種比較笨的方法就是服務器給我們傳回來,但是閉包出現后,我們便可以直接在函數中定義函數,而使用我們之前的data即可。
該例子有段時間了,大家看不清楚也沒有關系啦。。。。
結語
葉大俠病了,求安慰,求按頂。。。。。