以前一直對閉包這個概念模模糊糊的,網上也是長篇大論,如今經過本身瞭解和學習,總結了一下一些閉包知識點,寫得不對的地方能夠指出,你們互相學習.面試
先了解一些變量的做用域:markdown
變量的做用域包括兩種:全局變量和局部變量。閉包
全局變量:異步
var n = 999;//全局變量
function f1(){
console.log(n);
}
f1();//999
複製代碼
局部變量:函數
function f1(){
var n = 999;//局部變量
}
console.log(n);//n is not defined
複製代碼
先看一下MDN關於閉包的定義:學習
一個函數和對其周圍狀態(lexical environment,詞法環境)的引用捆綁在一塊兒(或者說函數被引用包圍),這樣的組合就是閉包(closure)。也就是說,閉包讓你能夠在一個內層函數中訪問到其外層函數的做用域。在 JavaScript 中,每當建立一個函數,閉包就會在函數建立的同時被建立出來。ui
重點的一句:閉包讓你能夠在一個內層函數中訪問到其外層函數的做用域。spa
如今不理解也不要緊,繼續往下看:code
學習一個概念時,最好的方法就是找它的demo,從demo中理解和分析,下面先看一段代碼,這是一個最簡單的閉包:orm
function f1(){
var n = 999;
function f2(){
console.log(n);
}
return f2//返回內部函數f2,這樣在f1中就能讀取f2的數據和函數等價於window.f2 = f2;
}
var result = f1();
result();//999
複製代碼
在上邊的代碼中,f1函數裏面嵌套了一個函數f2,而且f2調用了f1的變量,那麼變量n和函數f2組合就成了一個閉包。
那爲何是閉包呢?咱們能夠根據上邊MDN對閉包的定義這句話(閉包讓你能夠在一個內層函數中訪問到其外層函數的做用域。)進行分析,咱們再看一張圖:
f1是一個外部函數,變量n是外部函數的局部變量,f2是嵌套在f1中的一個內部函數,在內部函數f2中調用了外部函數f1的變量n,因此f2和變量n就組成了一個閉包。
那麼,咱們就能夠得出產生閉包的條件:
只要知足以上兩個條件,就產生了閉包。
那你可能會問爲何要return f1呢?
由於在JS中,只要內部函數纔可以讀取外部函數的內部變量或數據,反之則不行,若是你不return f2,那你將沒法使用f2這個閉包,return f2是爲了在f1中能使用f2的變量和數據,與閉包沒有關係的。
那到底什麼是閉包呢?
能夠通俗理解成:閉包就是有權訪問另外一個函數做用域中內部變量或數據的函數,由於在JS中,只要內部函數能可以讀取外部函數的變量或數據,反之就不行,全部能夠將閉包簡單理解成,定義在一個函數內部的函數。
總結:
閉包就是有權訪問另外一個函數內部變量的函數。
閉包產生的緣由:內部函數存在對外部函數局部變量的引用就會致使閉包。
到這裏相信你也已經對閉包有了一個簡單的瞭解了,可是單單是瞭解仍是不夠的,咱們學學習同樣技術,最重要的就是要學以至用,那咱們繼續往下了解吧。
最大的一個用途就是前面提到的能夠:讀取內部函數的變量;
function f1(){
var n = 999;
function f2(){
console.log(n);
}
return f2;
}
var result = f1();
result();//999
複製代碼
var n = 999;
function f1(){
var n = 1000;
function f2(){
console.log(n);
}
return f2
}
function f3(p){
var n = 1001;
p();
}
f3(f1());//1000
複製代碼
var n = 999;
(function f1(){
console.log(n);
})()
//999
複製代碼
上邊的代碼中f1( )是一個閉包,調用了全局變量n(即調用了window下的變量n);
for(var i = 0; i<10; i++){
(function(j){
setTimeout(function(){
console.log(j);
},1000)
})(i)
}
//1,2,3,4,5,6,7,8,9,10依次打印
複製代碼
window.n = 999;
setTimeout(function f1(){
console.log(window.n);
},1000)
複製代碼
能夠看下下面這段代碼:
function f1(){
var n = 999;
function f2(){
console.log(n++);
}
result f2
}
var result = f1();
result();//1000
複製代碼
上邊代碼中f1的內部變量n一直存在內存中,不會在f1調用結束後被自動清除。 再看另外一段代碼:
function f1(){
var n = 999;
nAdd = function(){
n+=1;
}
function f2(){
console.log(n);
}
result f2
}
var result = f1();
result();//999
nAdd();
result();//1000
複製代碼
上邊代碼中函數f1的返回值賦值給了全局變量result,函數f1的返回值實際上就是f2函數,能夠理解爲f2被賦值給了全局變量result,這就致使了f2始終在內存中,而f2的存在依賴於f1,所以f1也始終在內存中,不會在調用結束以後,被垃圾回收機制(GC機制)回收,全部很容易形成內存泄漏。
內存泄漏,就是一些你訪問不到或用不到的變量,還佔據着內存空間,不能被再次利用起來。
var Counter = (function(){
var privateCounter = 0;
return function changeBy(val){
privateCounter += val;
}
return {
increment:function(){
changeBy(1);
},
decrement:function(){
changeBy(-1);
},
value:function(){
return privateCounter;
}
}
})();
console.log(Counter.value());//0
Counter.increment();
Counter.increment();
console.log(Counter.value());//2
Counter.decrement();
console.log(Counter.value());//1
複製代碼
由於使用閉包會包含其餘函數的做用域,會比其餘函數佔據更多的內存空間,不會在調用結束以後被垃圾回收機制(簡稱GC機制)回收,多度使用閉包會過分佔用內存,形成內存泄漏。
一、簡述什麼是閉包,閉包的做用是什麼?寫出一個簡單的閉包例子。
二、閉包會形成內存泄漏嗎?
會,由於使用閉包會包含其餘函數的做用域,會比其餘函數佔據更多的內存空間,不會在調用結束以後被垃圾回收機制回收,多度使用閉包會過分佔用內存,形成內存泄漏。
三、for循環和閉包(必刷題)
var data = [];
for(var i = 0; i < 3; i++){
data[i] = function (){
console.log(i);
};
}
data[0]();//3
data[1]();//3
data[2]();//3
複製代碼
上邊代碼的變量i屬於一個全局變量,公用一個做用域,全部輸出是3個3; 使用閉包改善上邊的寫法達到預期的效果:
var data = [];
for(var i = 0; i < 3; i++){
(function(j){
setTimeout(data[j] = function(){
console.log(j);
},0)
})(i)
}
data[0]();
data[1]();
data[2]();
複製代碼
四、請寫出如下代碼的輸出結果:
第一題4-1:
var n = 10;
function f1(){
var n = 20;
function f2(){
n++
console.log(n);
}
f2();
return f2
}
var result = f1();//21
result();//22
result();//23
console.log(n);//10
複製代碼
第二題4-2:
function makeAdder(x){
return function(y){
return x+y;
}
}
var add5 = makeAdder(5);
var add10 = makeAdder(10);
console.log(add5(2));//7
console.log(add10(2));//12
複製代碼
第三題4-3:
var Counter = (function(){
var privateCounter = 0;
return function changeBy(val){
privateCounter += val;
}
return {
increment:function(){
changeBy(1);
},
decrement:function(){
changeBy(-1);
},
value:function(){
return privateCounter;
}
}
})();
console.log(Counter.value());//0
Counter.increment();
Counter.increment();
console.log(Counter.value());//2
Counter.decrement();
console.log(Counter.value());//1
複製代碼
第四題4-4:
var makeCounter = function() {
var privateCounter = 0;
function changeBy(val) {
privateCounter += val;
}
return {
increment: function() {
changeBy(1);
},
decrement: function() {
changeBy(-1);
},
value: function() {
return privateCounter;
}
}
};
var Counter1 = makeCounter();
var Counter2 = makeCounter();
console.log(Counter1.value()); //0
Counter1.increment();
Counter1.increment();
console.log(Counter1.value()); //2
Counter1.decrement();
console.log(Counter1.value()); //1
console.log(Counter2.value()); //0
複製代碼
Counter1和Counter2是兩個獨立的閉包,一個閉包變量的值改變不會影響到另外一個閉包的變量。
第五題4-5:
for(var i = 0; i < 10; i++){
setTimeout(
function(){
console.log(i);
},1000)
}
//10 10 10 10 10 10 10 10 10 10每隔1秒輸出10,一共10個10
複製代碼
由於setTimeout是異步的,for循環是同步的,同步代碼執行完,i已是10了,異步代碼纔開始執行,因此i最後打印的是10。
若是將var換成let,打印的結果也不同:
for(let i = 0; i < 10; i++){
setTimeout(
function(){
console.log(i);
},1000)
}//1,2,3,4,5,6,7,8,9,10
複製代碼
在for循環中使用var,那i就是一個全局變量,循環結束以後i的值爲10。
持續補充更新,記錄很差的地方望指出修改,共同進步~
天天都給本身打氣,今天也要加油鴨~