提及javascript中的閉包,首先要知道爲何會存在閉包,其做用又是什麼。且爲何閉包中就能讓外層函數的變量始終保存呢?下面咱們將從這兩個角度去剖析它。固然,大神繞道,謝謝哈。javascript
開門見山,直接總結閉包的兩大核心做用:html
衆所周知,變量在javascript中有全局變量和局部變量,函數內部能夠訪問外部的全局變量,而外部沒法訪問函數內部的局部變量,這是JS的一個特色:java
var n = 999;
function f1() {
console.log(n)
}
f1(); // 999
複製代碼
那麼擺在咱們面前的一個問題就是:如何在函數外部訪問到函數內部的變量?有一種辦法就是在函數內部再定義一個函數,經過這個內層函數去訪問外層函數的變量,由於內層函數同屬外層函數的做用域中(符合鏈式做用域結構規則,子對象能夠一級一級地向上尋找全部父對象的變量):面試
funtion f1() {
var n = 999;
function f2() {
console.log(n);
}
return f2;
}
var result = f1();
result(); // 999
複製代碼
對此:阮一峯老師給出了一個通俗的關於閉包的解釋: 閉包就是可以讀取其餘函數內部變量的函數(關於究竟是內層函數是閉包仍是外層函數是閉包的解釋各方不一致,但這不是重點)。數組
因爲在JS中只有函數內部的子函數才能讀取局部變量,所以也能夠理解爲定義在一個函數內部的函數。bash
暫且咱們說閉包是指有權訪問另外一個函數做用域中的變量的函數吧,函數內部的函數使用到外層函數的變量,使得外層函數的變量的生存時間延長,形成常駐內存。閉包
function foo(){
var a = 2;
return function(){
a += 1;
console.log(a);
}
}
var baz = foo();
baz(); // 3
baz(); // 4
baz(); // 5
baz(); // 6
複製代碼
上面的例子爲何每次調用baz時,a都沒有被初始化賦值呢? 接下來就要從JS的垃圾回收機制去考慮了。函數
一般咱們可使用引用計數法判斷代碼中的變量是否被釋放,即語言引擎中有一張「引用表」,保存了內存裏面全部的資源(一般是各類值)的引用次數,若是一個值的引用次數爲0,則表示該值再也不用到了,所以改值也被內存釋放了。好比:學習
cont arr = [1,2,3];
// 數組[1,2,3]是一個值,會佔用內存變量arr是僅有的對這個數組的引用,所以引用次數爲1。儘管只賦值一次,後面代碼中再沒有調用,但卻依然佔據內存.
複製代碼
譬如JS這樣的高級程序語言中都嵌入了一種稱爲垃圾回收器的機制,其工做是跟蹤內存的分配和使用,以便發現任什麼時候候再也不須要的內存並對其進行釋放。舉例以下:ui
var o1 = {
o2: {
x: 1
}
};
//建立2個對象o2和o1,其中o2被o1對象引用做爲其屬性,此時沒有垃圾可收集
var o3 = o1; //建立變量o3,引用由o1指向的對象的變量
o1 = 1 ; //如今將o1從新賦值爲1,最初的o1中的對象由o3變量表示
var o4 = o3.o2; //建立變量o4,引用對象o2,此時o2被兩個地方引用:一個是做爲o3變量的屬性,一個是做爲o4變量
o3 = '666'; // 此時最初o1對象應沒有再被引用了,能夠被垃圾收集了,可是最初的o2還在被o4引用,所以還不能被垃圾收集
o4 = 16; //此時 o2也能夠說再見了...
複製代碼
好了,如今再回過頭來看看上面那個閉包題,用垃圾回收的思想來做出解答:
function foo(){
var a = 2;
function outer() {
a += 1;
console.log(a);
}
return outer
}
var baz = foo(); //在全局做用域下建立變量baz
複製代碼
此時,baz指向的就是 outer,因此outer始終都沒有被銷燬,而根據垃圾回收機制,因爲在outer中有引用外層函數的變量a,所以a也一直沒有被銷燬,因此就出現這種現象:
baz(); //3
baz(); //4
baz(); //5
複製代碼
是否是如下就明白了,接下來看一個簡單的經典例題
看一下經典的面試題吧,用垃圾回收的思想來思想一下結果,就會很順利:
var callbacks;
for(var i = 0 ; i <= 5 ;i ++) {
callbacks = function() {
console.log(i);
}
}
callbacks(); //6
callbacks(); //6
callbacks(); //6
複製代碼