學習javaScript已經有一段時間了,在這段時間裡,已經感受到了JavaScript的種種魅力,這是一門神奇的語言,同時也是一門正在逐步完善的語言,相信在大家的逐步修改中,這門語言會逐步的完善下去,在上一篇隨筆中,和大家分享了JavaScript中獨有的類中的繼承方式,今天呢,就跟大家分享一下我這幾天一直在搞,卻還是搞的不是很透徹的閉包問題,對於一個初學者而言,JavaScript中的閉包無疑是一個難點,而且也是我們必須要掌握的一個重點,那麼今天我就跟大家分享一下我在學習閉包的時候的感悟以及相應的理解,首先,我們在一個函數中有一個變量: 代碼1
1 function people(){ var age = 12;//函數中有一個變量 ( function (){ //在函數中我們再定義一個自調用函數 alert(age); })(); }
在函數people中,有著變量age,大家應該之前都有了解到JavaScript中特有的變量作用域的問題,函數本身就是一個作用域,函數裡邊可以訪問外邊的變量,而函數外邊並不能訪問函數裡邊的變量,這樣,我們就遇到了一個問題,如果我們想要獲取函數中的變量age怎麼辦?如代碼1中所示,因為自調用函數在people中,所以它可以訪問它外邊的變量,所以自調用函數可以訪問age,那麼我們就想,將這個自調用函數return出來不就行了嗎?代碼2:
function people(){ var age = 12; return function (){ alert(age); } } var xiaoMing = new people(); xiaoMing();//會彈出文本框12,說明在people外邊調用到了內部變量age
好了,通過這種方法,我們獲得了函數內部的變量,那麼這就叫做閉包(closure),上邊的功能就是它的第一個功能,可以在函數外部通過閉包獲得函數內部的變量,從形狀上看,閉包就是函數裡邊再定義一個函數並返回,從書上以及各個博客上看到了對閉包的解釋各種各樣,我對閉包的理解而言,整個函數就相當於一個房子,這個房子沒有門,只有一個天窗,我們在下邊無法看見房子裡邊放的東西,而閉包就像是一個梯子,讓我們爬上去可以通過天窗看到房子裡邊放的東西,就是連接函數外邊和裡邊的一座橋梁,閉包對於我們的便利性是不言而喻的,然而,事物總是有著相反的一面,閉包有他的好,當然也有它的壞,現在,我們可以先用閉包做一個累加的例子:代碼3:
function func(){ var count=10; return function(){ return ++count; } } var ss = new func(); alert(ss());//彈出11 alert(ss());//彈出12
通過代碼3我們可以看出,每次調用func中返回的閉包時,count是在累加的,這時我們心中肯定就會有很多疑惑,不是說好的函數是一個作用域嗎?執行完了函數func它體內的變量應該不存在了呀?怎麼每次調用的時候它都在累加呢?這就是閉包的第二個作用了,當閉包在函數體外執行時,它的體內保存了原來算是它的父類的函數的整個執行環境以及它體內調用的變量,這時他們的關系就像我們蓋房子一樣,func就像地基一樣,ss就是我們的房頂,這時我們站在房頂上,那麼地基會消失嗎?顯然是不能的,這時閉包的一個優點,同時也算是它的一個缺點,就是它將一個內部變量一直放到了內存中釋放不了,有可能會造成內存洩露,但這也是它的一個優點,可以將作用域鏈拉長,但這個功能在for循環中是一個經常出錯的地方:
代碼4:
function func(){ var arr=[]; for (var i=0;i<5;i++){ arr[i]=function (){//注意此處,往數組中存放的是一個函數 return i; } } return arr; } var arr=func(); for(var i=0;i<5;i++){ console.log(arr[i]());//這時會返回五個5 }
這時我們是否也會感到同樣的疑惑,這不是應該返回0-4嗎?這正是閉包在應用中的一個坑,這時,我們可以這樣理解,當往arr[0]中存放第一個閉包函數時,函數並沒有執行,那麼這時閉包中就存放了i的一個鏈接,它也不知道i的值是多少,當func執行完了,這時閉包中存放在i的一個作用域鏈接,當執行arr數組中的函數時,由於數組中的函數沒有i這個變量,所以它要向上一級去尋找,這時,它就找到了func中,而此時func中i的值已經變為5了,所以輸出的每一個數都是5.那麼有因就有果,有好就有壞,有錯誤我們就有方法去解決:
function func(){ var arr=[]; for (var i=0;i<5;i++){ arr[i]=(function (num){ return i; })(i);//這時使用自調用函數 } return arr; } var arr=func(); for(var i=0;i<5;i++){ console.log(arr[i]);//這時會輸出0-4 }
修改的方法一:就是將閉包改為一個自調用函數這時,傳入arr[i]中的不再是一個函數,而是傳入的參數i,以此類推,我們還可以返回一個閉包,只不過要讓閉包中存放一個確定的值,而不再是一個變量:
function func(){ var arr=[]; for (var i=0;i<5;i++){ arr[i]=function (num){ return function(){ return num; } }(i); } return arr; } var arr=func(); for(var i=0;i<5;i++){ console.log(arr[i]());//這時會輸出0-4 }
使用這種方案的時候,用新創建的函數的參數綁定循環變量當前的值,無論該循環變量後續如何更改,已綁定到函數參數的值不變,所以在使用閉包時,盡量不要將後續會發生變化的變量放到閉包中.同時,閉包不僅會改變函數中的變量的使用范圍變廣,它同時也會改變this的作用域:
var name="kkkk"; function People(){ this.name="lala", this.getName=function(){ return function(){ alert(this.name) }; } } var xiaoMing=new People(); xiaoMing.getName()();//這時會彈出kkkk
這時,當我們返回一個閉包時,它當中存放的this綁定的對象是window,而不是People,其中的原理:我的理解就是,當你返回一個函數時,它其中的this是封閉在其中的那時候還沒有執行函數,所以this的指向還沒有確定,當在對象外執行閉包時,this回去找它要指向的對象,這時它就會指向window,我們可以用that來捕獲this:
var name="kkkk"; function People(){ this.name="lala", this.getName=function(){ var that=this; return function(){ alert(that.name) }; } } var xiaoMing=new People(); xiaoMing.getName()();
這時再輸出就是lala了,當然,我們不僅可以使用這種方式來捕獲this,我們還可以使用之前所學的對象冒充的方法來執行函數:
var name="kkkk"; function People(){ this.name="lala", this.getName=function(){ return function(){ alert(this.name) }; } } var xiaoMing=new People(); xiaoMing.getName().apply(xiaoMing,[]);
使用這種方式同樣可以達到我們想要的目的,同時,我們使用call也是可以的.到了這裡,大家應該對閉包有了一個初步的了解吧,閉包這種東西,可能理解的時候好理解一些,但是當我們實際應用的時候,剛開始可能會感覺到十分的吃力,但我們應該相信,熟能生巧,而且閉包這種東西,可能我們每天都在用,可對於它內在的原理還是一知半解,這個一定要多做練習,多做實驗,當你有疑問的時候,就把代碼打下來去運行一下,看一下結果,再想一下為什麼會是這種結果,大家,一起加油!
本博文是博主原創,如有轉載請說明出處!