首先,從一個計數器開始。編程
var counter = 0; function increment() { counter = counter + 1; console.log("Number of events: " + counter); }
increment(); // Number of events: 1 increment(); // Number of events: 2 increment(); // Number of events: 3
上面的代碼簡單粗暴,可是咱們很快會遇到下一個問題:若是想要再建立一個計數器怎麼辦呢。固然咱們能夠建立兩個變量,兩個函數,但這樣是否是太low了:閉包
var counter1 = 0; function incrementCounter1() { counter1 = counter1 + 1; console.log("Number of events: " + counter1); } var counter2 = 0; function incrementCounter2() { counter2 = counter2 + 1; console.log("Number of events: " + counter2); } incrementCounter1(); // Number of events: 1 incrementCounter2(); // Number of events: 1 incrementCounter1(); // Number of events: 2
並且當須要更多的計數器時,就更不可能用這種方法了。函數
上面的那段代碼,咱們更想作的是封裝成一個函數,去除冗餘代碼。下面咱們就初步嘗試一下閉包:優化
function createCounter() { var counter = 0; function increment() { counter = counter + 1; console.log("Number of events: " + counter); } return increment; }
如今來看一下發生了什麼。咱們將會建立兩個計數器,而且使用它們來跟蹤兩個獨立的事件:this
var counter1 = createCounter(); var counter2 = createCounter(); counter1(); // Number of events: 1 counter1(); // Number of events: 2 counter2(); // Number of events: 1 counter1(); // Number of events: 3
額,看起來有點複雜。。但實際上很簡單。咱們只須要分解一下實現的邏輯。翻譯
首先,建立了一個局部變量counter
而後,建立了一個局部函數increment
,能夠增長counter
的值。code
其實這個createCounter()
的實現幾乎跟最開始那個計數器同樣。惟一不一樣的就是它被函數包裹起來了。因而,這種結構就被稱做閉包。對象
而後就到了最重要的地方:blog
createCounter()
裏的最後一步 返回了 increment
局部函數 ,注意,這裏返回的不是函數的調用結果,而是函數自己。接口
這也就是說,當咱們使用下面的語句建立計數器,咱們其實是生成了一個新的函數。
// fancyNewCounter is a function in this scope var fancyNewCounter = createCounter();
這就是閉包強大的地方。每一個經過createCounter()
生成的函數都保持追蹤它們本身產生的counter
的值。也就是說,這個返回的函數會記住他被建立時的環境。
能夠看到的重要的一點就是內部的counter變量都是互相獨立的。建立了兩個計數器,它們會各自在閉包中分配一個新的counter變量。咱們能夠看到:
每一個計數器都從1開始計數:
var counter1 = createCounter(); counter1(); // Number of events: 1 counter1(); // Number of events: 2 var counter2 = createCounter(); counter2(); // Number of events: 1
第二個計數器沒有收到第一個計數器的值的影響。
counter1(); // Number of events: 3
「Number of events: x」這種消息是能夠的,可是若是信息能夠描述咱們正在計數的事件的類型,會不會更好。例如,若是咱們能夠在咱們的計數器裏面加一個名字:
var catCounter = createCounter("cats"); var dogCounter = createCounter("dogs"); catCounter(); // Number of cats: 1 catCounter(); // Number of cats: 2 dogCounter(); // Number of dogs: 1
咱們能夠往閉包裏傳一個參數
function createCounter(counterName) { var counter = 0; function increment() { counter = counter + 1; console.log("Number of " + counterName + ": " + counter); } return increment; }
能夠看出來,createCounter在實現過程當中不只能記住局部變量counter,也記住了傳進來的變量。
上面的寫法有個問題就是,當咱們在執行計數器的時候,很難直觀的看出來這個計數器是個將要計算增量的函數,下面的寫法會更簡潔:
var dogCounter = createCounter("dogs"); dogCounter.increment(); // Number of dogs: 1
function createCounter(counterName) { var counter = 0; function increment() { counter = counter + 1; console.log("Number of " + counterName + ": " + counter); }; return { increment : increment }; }
在上面的代碼中,咱們返回了一個包含閉包中全部函數的對象。從某種意義上來講,咱們正在定義咱們的閉包能夠響應的一系列消息
如今咱們能夠很是簡單的將遞減的函數加入到計數器中。
function createCounter(counterName) { var counter = 0; function increment() { counter = counter + 1; console.log("Number of " + counterName + ": " + counter); }; function decrement() { counter = counter - 1; console.log("Number of " + counterName + ": " + counter); }; return { increment : increment, decrement : decrement }; } var dogsCounter = createCounter("dogs"); dogsCounter.increment(); // Number of dogs: 1 dogsCounter.increment(); // Number of dogs: 2 dogsCounter.decrement(); // Number of dogs: 1
上面的代碼的console.log重複了兩次。應該明確的建立一個函數來顯示計數器的值。
function createCounter(counterName) { var counter = 0; function display() { console.log("Number of " + counterName + ": " + counter); } function increment() { counter = counter + 1; display(); }; function decrement() { counter = counter - 1; display(); }; return { increment : increment, decrement : decrement }; } var dogsCounter = createCounter("dogs"); dogsCounter.increment(); // Number of dogs: 1 dogsCounter.increment(); // Number of dogs: 2 dogsCounter.decrement(); // Number of dogs: 1
display
函數看起來和increment()
和decrement()
很像的樣子,但實際上是很是不一樣的。咱們沒有在對象的結果中返回這個函數,也就是說下面的語句會報錯:
var dogsCounter = createCounter("dogs"); dogsCounter.display(); // ERROR !!!
咱們讓display()
函數從外面的世界隱藏了,它只供createCounter()
內部訪問
讓咱們使用閉包來實現棧操做
function createStack() { var elements = []; return { push: function(el) { elements.unshift(el); }, pop: function() { return elements.shift(); } }; } var stack = createStack(); stack.push(3); stack.push(4); stack.pop(); // 4
注意:在Javascript中,閉包可能並非實現棧數據類型的最佳實現方式,Prototypes可能會更好一些
若是你作過面向對象編程,你可能會注意到,上面的結構特別像類啊對象啊實例變量和公有/私有方法。
按:很久沒翻譯了,這篇文章翻譯的有些智障,可是不耽誤看代碼,看完仍是頗有收穫的!