其實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的值正常的被访问到了】
顯然,濫用閉包會增加記憶體的使用。所以非特殊情況盡量不要使用閉包。如果用到了,記得手動設定空引用,記憶體才能被回收 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)之函數表達式閉包,希望大家喜歡。