講解JavaScript中對閉包的理解
1、JS中變數的作用域
- 在理解閉包之前,我們得弄清楚JS中變數的作用域原理,它分為全域性作用域和區域性作用域,它有一個特點就是區域性可以獲取全域性的宣告變數,而全域性卻不能得到區域性宣告的變數,我們先來看一個小例子:
var num = 99;
function foo(){
var hit = 88;
console.log(num);
}
foo(); //99
console.log(hit); //報錯,找不到hit變數
當然在區域性宣告變數的時候一定要用var或者let,不然會在全域性生成一個變數,容易照成全域性汙染,上面程式碼如果hit沒有var宣告:
var num = 99;
function foo(){
hit = 88;
console.log(num);
}
foo(); //99
console.log(hit); //88
2、什麼是閉包
那麼現在問題來了,如果我們非要從外部來讀取區域性變數中的宣告變數呢,尋常方式不行,我們可以變通一下,就是在函式內部再巢狀一個函式,然後返回這個巢狀函式:
function foo(){ var hit = 88; return function num(){ console.log(hit) } } var num1 = foo(); num1(); //88
這樣,控制檯就會打印出hit變數的值了,其實在上面的程式碼中,被返回的函式num()就產生了閉包,由於在js中,只有函式內部的子函式才能讀取區域性變數,所以可以把閉包理解成定義在一個函式內部的函式,簡單的說,JavaScript允許使用內部函式:即函式定義和函式表示式位於另一個函式的函式體內。而且,這些內部函式可以訪問它們所在的外部函式中宣告的所有區域性變數、引數和宣告的其他內部函式。當其中一個這樣的內部函式在包含它們的外部函式之外被呼叫時,就會形成閉包。
3、閉包的用途
- 相信大家對閉包的概念已經有了簡單的認識,我們接著探討閉包的表達形式以及用途。
(1)匿名自執行函式
(function(){ var foo = function(){ console.log('執行完函式後銷燬') }; foo(); })();
上面程式碼也是閉包的應用,運用於函式只會執行一次的場景,執行完便會被釋放。
(2)給物件設定私有變數
var result = function(){
var count = 1;
return function (){
count++;
console.log(count)
}
}()
result(); //2
result(); //3
result(); //4
result(); //5
上面程式碼可以儲存自己的私有變數,防止程式碼之間的衝突。
(3)非同步執行函式
下面先看一個小例子:
for(var i=0;i<5;i++){
console.log(i); //0,1,2,3,4
}
for(var i=0;i<5;i++){
setTimeout(()=>{
console.log(i); //5,5,5,5,5
},0)
}
為什麼會出現上述差異呢,原因在於setTimeout是非同步載入,所以為先迴圈結束後輸出最後結果,如果我們就是想實現輸出0,1,2,3,4呢。那就要用到閉包了:
for(var i=0;i<5;i++){
(function(i){
setTimeout(()=>{
console.log(i); //0,1,2,3,4
},0);
})(i);
}
上面就是非同步呼叫閉包,它可以讓變數值始終儲存在記憶體中,即使外部的執行環境已經結束了。
4、閉包的優缺點
(1)由於閉包會使得函式中的變數都被儲存在記憶體中,記憶體消耗很大,所以不能濫用閉包,否則會造成網頁的效能問題,在IE中可能導致記憶體洩露。解決方法是,在退出函式之前,將不使用的區域性變數全部刪除。
(2)閉包會在父函式外部,改變父函式內部變數的值。所以,如果你把父函式當作物件(object)使用,把閉包當作它的公用方法(Public Method),把內部變數當作它的私有屬性(private value),這時一定要小心,不要隨便改變父函式內部變數的值。