一步步的理解閉包:javascript
下文的敘述以此圖爲標準,如有疑問請參照java
下面咱們來一些經典實例來進一步幫你解除閉包的困擾(js中最難的一個問題之一),嵌套函數因爲其自然的嵌套形式是閉包最多見的一種表現方式。由嵌套函數開始吧 緩存
1 function sayHello2(name){ 2 var text='hello'+name; 3 var sayAlert=function(){alert(text);} 4 return sayAlert; 5 } 6 var say2=sayHello2('jack'); 7 say2();//hello jack
上面的代碼就是一個嵌套函數,也是咱們見過的最多見閉包表現形式,由於匿名函數function(){alert(text);}能夠訪問到外部函數sayHello2中的text變量,這就造成了閉包。閉包雖然存在了,但它也不能徒有其表,還的作點事情。代碼第6行的賦值表達式後,外部函數sayHello2就被銷燬了,按常理其內部變量text也隨之銷燬。可第7行成功的調用說明,外部函數的變量對象始終保留在內存中,直到內部函數銷燬,再也不被調用爲止(總的有個頭,否則內存扛不住)。閉包
再看下面的例子函數
1 function say667(){ 3 var num=666; 4 var sayAlert=function(){alert(num);} 5 num++; 6 return sayAlert; 7 } 8 var sayNum=say667(); 9 sayNum();//667
爲何最後是667,不是你指望的666?(有的地方是這樣解釋的,說是對父變量的引用,而不是複製。筆者認爲基本類型不存在引用(地址)這一說吧)。保存着外部函數的活動對象,活動對象就是一個盒子。裏面的變量,父函數能夠隨意改變。性能
這個變量對象原本就是父函數的一部分,因此父函數能操做它是理所固然。而這個父變量對象也靜態的保存在內部函數的做用域鏈中,那內部函數應該也有權利去操做它。spa
1 function baz(){ 2 var x=1; 3 return { 4 foo:function foo(){return ++x;}, 5 bar:function bar(){return --x;} 6 }; 7 } 8 var closures=baz(); 9 alert(closures.foo(),//2 10 closures.bar()//1)
你會發現內部的foo函數改變了父變量,與此同時也影響到了bar函數的結果。這是爲何?這是由於內部的兩個函數在建立的時候都保存的是同一個父做用域—baz的做用域。這樣一來父變量對象就是共享的了,因此裏面的變量相互影響。(做用域包含變量對象,變量對象包含變量)code
再來看咱們遇到過的一個循環問題了,一樣是父函數一某種方式改變了這個變量對象中的變量值。使得內部函數最後訪問的值變成了循環最終值。對象
1 function list(){ 2 var result=[]; 3 for(var i=0;i<3;i++){ 4 result[i]=function(){ 5 alert(i); 6 } 7 } 8 } 9 result[0];//3 10 result[1];//3 11 result[2];//3
閉包的用途 |
下面看看閉包的用途吧,閉包能夠用在許多地方。blog
把你指望的值保存在一個地方,這與緩存的思想不謀而合。例如咱們須要處理一個過程很耗時的函數對象,每次調用都會花費很長時間,那麼咱們須要把計算的值保存起來。調用的時候首先在緩存中查找,若是找不到則進行計算,而後更新緩存的值。若是找到了,直接返回找到的值便可。閉包正是能夠作到這一點.
1 var cacheSearchBox=(function(){ 2 var cache={};
4 return { 5 attachSearchBox:function(boxid){ 6 if(boxid in cache){ 7 return cache[boxid]; 8 } 9 var fsb=new searchBox(boxid); 10 cache[boxid]=fsb; 11 return fsb 12 } 13 } 14 })(); 15 cacheSearchBox.attachSearchBox("input1");
這樣,當咱們第二次調用cacheSearchBox.attachSearchBox("input1")時,咱們能夠從緩存中獲取該對象,而不用再去建立一個新的searchbox對象。
3 面向對象中實現特權方法
在下面的例子中,在person以外的地方沒法訪問其內部變量。只有經過閉包構造的特權方法的形式來訪問。同時實現了私有變量和訪問私有變量的特權方法的封裝:
1 var person=function(){ 2 var name="deng"; 3 return{ 4 getName:function(){ 5 return name; 6 }, 7 setName:function(newName){ 8 name=newName; 9 } 10 } 11 }(); 12 alert(person.name);//undefined 13 alert(person.getName());//deng 14 person.setName("jack"); 15 alert(person.getName());//jack
使用閉包的注意點 |
1)因爲閉包會使得函數中的變量都被保存在內存中,內存消耗很大,因此不能濫用閉包,不然會形成網頁的性能問題,在IE中可能致使內存泄露。解決方法是,在退出函數以前,將不使用的局部變量所有刪除。
2)閉包會在父函數外部,改變父函數內部變量的值。實現面向對象封裝中特權方法時,慎重修改私有變量。
文章在上綱上線的同時,加入了不少本身的理解。也許存在紕漏,望探討。