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),這時一定要小心,不要隨便改變父函數內部變量的值。