其實js支援函數閉包的主要原因是因為js需要函數能夠儲存資料。這裡的儲存資料是只函數在運行結束以後函數內變數的值也會進行儲存。至於為什麼js需要在函數內可以儲存資料,那就是js是一種函數式語言。在函數內儲存資料是函數式語言的一大特徵。
回顧前面介紹過的三種定義函數方式
functiosu(numnumreturnunum//函式宣告文法定義
vasufunction(numnum)returnunum}//函數運算式定義
vasuneFunction("num""num""returnunum")//Functio建構函式
在分析閉包之前我們先來看看,定義和調用函數容易犯的錯誤。
例1:
sayHi(); //錯誤:函數還不存在var sayHi = function () { alert("test");};
例2:
if (true) { function sayHi() { alert("1"); }} else { function sayHi() { alert("2"); }}sayHi();//列印結果並不是我們想要的
例3:
var fun1 = function fun2() { alert("test");}fun2();//錯誤:函數還不存在
在例1中,我們不能在使用函式宣告式文法定義之前調用函數。解決方案:
1.如果使用函數運算式定義函數的話,需要在運算式定義後調用。
var sayHi = function () { alert("test");};sayHi()
2.使用函式宣告式。(這裡瀏覽器引擎會 函式宣告提升, 在所有代碼執行之前先讀取函式宣告)
sayHi(); function sayHi () { alert("test");};
在例2中,我們預期的結果應該是列印1,實際結果是列印2。
if (true) { function sayHi() { alert("1"); } } else { function sayHi() { alert("2"); }}sayHi();//列印結果並不是我們想要的
為什麼會這樣?正因為 函式宣告提升 ,所以瀏覽器在預解析的時候不會判斷if條件,直接解析第二個函數定義的時候覆蓋了第一個。
解決方案:
var sayHi;if (true) { sayHi = function () { alert("1"); } } else { sayHi = function () { alert("2"); }}sayHi();
在例3中,發現只能只用fun1()調用,而不能使用fun2()調用。
我自己的理解,真正原因不知道。沒找到資料。
因為1: function fun3() { }; 等效與 var fun3 = function fun3() { }; 如圖:
所以只能只用fun1()調用,而不能使用fun2()調用。
其實這裡我還是有疑問的?哪位大神知道,望告知。
既然,fun2在外面不能調用為什麼在函數內部能調用?雖然在debugger還是得不到fun1。
好了,通過上面的三道題目熱身。我們繼續今天的主題“閉包”。
1.什麼是閉包?
定義:就是有權訪問另一個函數範圍的變數的函數
我們先從一個樣本函數開始:
例1:
function fun() { var a = "張三";}fun();//在我們執行完後,變數a就被標記為銷毀了
例2:
function fun() { var a = "張三"; return function () { alert("test"); }}var f = fun();//同樣,在我們執行完後,變數a就被標記為銷毀了
例3:
function fun() { var a = "張三"; return function () { alert(a); }}var f = fun();//【現在情況發生變化了,如果a被銷毀,顯然f被調用的話就不能訪問到變數a的值了】f();//【然後變數a的值正常的被訪問到了】//這就是閉包,當函數A 返回的函數B 裡面使用到了函數A的變數,那麼函數B就使用了閉包。樣本:function fun() { var a = "張三"; return function () { alert(a); }}var f = fun();//【現在情況發生變化了,如果a被銷毀,顯然f被調用的話就不能訪問到變數a的值了】f();//【然後變數a的值正常的被訪問到了】
顯然,濫用閉包會增大記憶體的使用。所以非特殊情況盡量不要使用閉包。如果用到了,記得手動設定Null 參考,記憶體才能被回收 f = null ;
圖解:(不瞭解範圍鏈的同學請先看前面的文章 範圍和範圍鏈 )
2.什麼是匿名函數? (僅僅只是解釋這個概念)
如:(即,沒有名字的函數)
關於對象中函數的傳回值是匿名函數時,this的怪異現象
講解之前,先清醒下頭腦,不要越看越迷糊了。如果迷糊了,那就直接忽略下面的。
var name1 = "張三";var obj = { name1: "李四", fun2: function () { alert(this.name1); }, fun3: function () { return function () { alert(this.name1); } }}
obj.fun2();//列印結果"李四"意料之中的。
obj.fun3()();//因為這裡返回的是一個函數,所以要再加一對()來調用。列印結果是"張三",意料之外。
//真是百事不得其解啊,什麼this指向了全域?
我們前面講過“ 哪個對象點出來的方法,this就是哪個對象 ”,那我們的 obj.fun3()() 列印的是“張三”也就是說this執行了全域範圍。
我們看看下面的樣本也許就知道為什麼了。
var name1 = "張三";var obj = { name1: "李四", fun2: function () { alert(this.name1); }, fun3: function () { return function () { alert(this.name1); } }} //obj.fun3()();var obj2 = {};obj2.name1 = "test";obj2.fun = obj.fun3();obj2.fun();//列印結果"test",再次證明了“哪個對象點出來的方法,this就是哪個對象”.var name1 = "張三";var obj = { name1: "李四", fun2: function () { alert(this.name1); }, fun3: function () { return function () { alert(this.name1); } }}//obj.fun3()();var obj2 = {};obj2.name1 = "test";obj2.fun = obj.fun3();obj2.fun();//列印結果"test",再次證明了“哪個對象點出來的方法,this就是哪個對象”.
我們來分解下 obj.fun3()() 先是 obj.fun3() 返回一個匿名函數到了window範圍,然後接著調用this就指向了window了。( 感覺解釋有點勉強,也不知道對不,暫時自己先是這麼理解的 )
閉包形成的原因:記憶體釋放問題
一般,當函數執行完畢後,局部使用中的物件會被銷毀,記憶體中僅儲存全域範圍,但閉包的情況是不一樣的。
閉包的使用中的物件依然會儲存在記憶體中,於是像上例中,函數調用返回後,變數i是屬於使用中的物件裡面的,就是說其棧區還沒有釋放,但你調用c()的時候i變數儲存的範圍鏈從b()->a()->全域去尋找範圍var i聲明所在,然後找到了var i=1;然後在閉包內++i;結果,最後輸出的值就是2了;
以上所述是小編給大家分享的JavaScript基礎篇(6)之函數運算式閉包,希望大家喜歡。