說起javascript中的閉包,首先要知道為什麼會存在閉包,其作用又是什麼。且為什麼閉包中就能讓外層函式的變數始終儲存呢?下面我們將從這兩個角度去剖析它。當然,大神繞道,謝謝哈。
開門見山,直接總結閉包的兩大核心作用:
- 讀取函式內部的變數;
- 讓變數始終儲存在記憶體中。
一、讀取函式內部的變數
眾所周知,變數在javascript中有全域性變數和區域性變數,函式內部可以訪問外部的全域性變數,而外部無法訪問函式內部的區域性變數,這是JS的一個特點:
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中只有函式內部的子函式才能讀取區域性變數,因此也可以理解為定義在一個函式內部的函式。
二、讓變數始終儲存在記憶體中
暫且我們說閉包是指有權訪問另一個函式作用域中的變數的函式吧,函式內部的函式使用到外層函式的變數,使得外層函式的變數的生存時間延長,造成常駐記憶體。
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的垃圾回收機制去考慮了。
1. 垃圾回收機制
通常我們可以使用引用計數法判斷程式碼中的變數是否被釋放,即語言引擎中有一張“引用表”,儲存了記憶體裡面所有的資源(通常是各種值)的引用次數,如果一個值的引用次數為0,則表示該值不再用到了,因此改值也被記憶體釋放了。比如:
cont arr = [1,2,3];
// 陣列[1,2,3]是一個值,會佔用記憶體變數arr是僅有的對這個陣列的引用,因此引用次數為1。儘管只賦值一次,後面程式碼中再沒有呼叫,但卻依然佔據記憶體.
複製程式碼
譬如JS這樣的高階程式語言中都嵌入了一種稱為垃圾回收器的機制,其工作是跟蹤記憶體的分配和使用,以便發現任何時候不再需要的記憶體並對其進行釋放。舉例如下:
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也可以說再見了...
複製程式碼
2. 解釋記憶體保留
好了,現在再回過頭來看看上面那個閉包題,用垃圾回收的思想來作出解答:
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
複製程式碼
參考文獻
- 阮一峰老師 《學習Javascript閉包》 www.ruanyifeng.com/blog/2009/0…
- 《重讀JavaScript高階程式設計》 reng99.cc/2018/03/01/…