1.閉包是什么
官方解釋:閉包是一個擁有很多變量和綁定了這些變量的環境的表達式(其實就是函數),因而這些變量也是該表達式的一部分。這個定義雖然太學術,但是告訴我們兩個信息:
1)閉包是一個函數
2)函數中有很多變量
上面兩個是構成閉包的兩個主要條件。
下面我們用通俗的話來解釋一下:js中的所有函數都是閉包(因為函數中的局部變量只能函數內部訪問),但是嵌套函數產生的閉包更加強大,也是我們現在所探討的閉包。
如果上面的解釋還不夠通俗,下面的終極解釋我想你一定能夠看懂:
有一個函數a,函數a中嵌套了一個函數b,如果函數b被函數a外部的一個變量引用,就創建了一個閉包。
下面我們來看看具體如何通過代碼來創建閉包,以加深上面概念的理解。
2.創建閉包
在創建閉包之前,首先要明白兩個概念,一個是變量的作用域,一個事js中的作用域鏈,第一點我們簡單說一下,第二點自己去查資料。
在Js中變量根據作用域的不同可以分為全局變量和局部變量(事實上很多語言都是這樣),在js中,如果一個變量沒有定義在任何函數中,則為全局變量;相對應的,定義在函數中的變量就是局部變量,但是如果函數中變量在聲明時沒有使用var關鍵字,則其仍然會稱為全局變量。我們來看例子。
function f() { a = 1;//沒有使用var,所以在函數外部也可以訪問 } f(); alert(a);
下面我們看看如何創建閉包,看下面的函數
function f1() { var a = 10; a++; alert(a); } var func1 = f1(); func1; func1;
希望你能猜對上面代碼的運行結果,只輸出一個11。在函數f1的外部創建了變量func1,然后指向由函數f1的返回值。當執行完代碼func1之后,這個對象就沒有引用了,所以會被垃圾回收,對象中的變量a同樣也會被回收;所以當再次執行func1時就不會有輸出了。
從上面的代碼,希望你能明白這樣一個道理:js中是有垃圾回收機制的。當一個對象沒有變量引用的時候,這個對象就會被回收。
再來看下面的代碼:
function f1() { var a = 10; function f2() { a++; alert(a); } return f2; } var func1 = f1(); func1(); func1();
上面代碼的輸出結果為11,12。
執行完第一句func1()之后,對象應該被回收,第二句func1();應該沒有輸出猜對呢,這是為什么呢?
我們來分析一下。
首先看var func1=f1();這行代碼執行之后,func1是什么。在函數f1中返回的是函數f2,所以func1的值其實是函數f2。按理說當執行完這行代碼之后,函數f1的使命已經完成,應該被垃圾回收才是,你們變量a也會被清除,但是執行代碼func1()之后的結果居然為11,這說明函數f1中的變量a沒有被清除,那么肯定函數f1也沒有被垃圾回收。這是為什么?
我們前面說過,一個對象如果被垃圾回收的條件是什么,那就是沒有變量引用這個對象。我們來看看上面的代碼。函數f2中對函數f1中的變量a進行++操作,也就是說在函數f2中引用了函數f1中的變量,也就是函數f2引用了函數f1。而代碼var func1=f1();其實是將函數f2返回給變量func1,也就是說變量func1引用了函數f2,而函數f2由引用了函數f1,這種間接引用的結果就是函數f1一直被變量引用着,所以一直無法被垃圾回收。
上面的情況就是閉包,我們再回顧一下閉包的定義:如果函數a中的嵌套函數b被函數a外部的變量引用,就創建了閉包。
綜合上面的討論,我們可以看出閉包的作用是什么
3.閉包的作用
1)變量的安全性:我們無法在函數f1的外部直接訪問其局部變量a,只能通過函數f2來訪問,而在函數f2中我們可以寫代碼進行安全性的控制,這是不是和c#中類的屬性很像。所以我們可以將函數f2看成是函數f1的一個屬性,這個屬性只有setter方法,而將局部變量a看成是函數f1的私有字段,只能通過公共屬性f2才能訪問f1中的私有字段a。
2)讓變量的值始終保存在內存中。這個已經非常清晰了,通過閉包,函數f1中的變量a沒有被回收,而是一直保存在內存中。