標籤: JavaScript closure translategit
本文由 伯樂在線 - 劉健超-J.c 翻譯,Namco 校稿。未經許可,禁止轉載!github
英文出處:Igor Šarčevićweb
幾年前,我仍是一名高中生時,個人一個朋友向我講述了閉包的概念。雖然我當時一點也不明白他想表達的內容,但他在向我講述時卻表現得很是高大上。對於當時的我來講,閉包看來是一個深不可測的魔法。即便 Google 後也不能解除個人疑惑。而全部我能查閱的科技文章,都爲高中生所難以理解。編程
如今的我回想起高中編程時光,我都會不由一笑。這是一篇試圖用一些簡單項目去解釋閉包的文章,這會幫助個人學弟學妹們能輕易地駕馭強大的閉包。閉包
咱們將從一個簡單的問題開始。若是將閉包引入到該程序中,將能輕易解決這個問題。
咱們爲計數事件建立一個機制。該機制將有助於咱們跟蹤代碼的執行,甚至去調試一些問題。例如,我會如下面的方式調用計數器:編程語言
increment(); // Number of events: 1 increment(); // Number of events: 2 increment(); // Number of events: 3
正如你所看到的上述案例,咱們但願代碼會在咱們每次執行 increment()
函數時,會顯示一條信息「Number of events: x」。下面以簡單的方式實現該函數:函數式編程
var counter = 0; function increment() { counter = counter + 1; console.log("Number of events: " + counter); }
上述代碼很是簡單明確。然而,當咱們引入第二個計數器時,就會很快遇到問題。固然,咱們能實現兩個單獨的計數器機制,以下面的代碼,但很明顯有須要改進的地方:函數
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
上述代碼出現了沒必要要的重複。明顯地,這種解決辦法並不適用於超過二或三個記數器的狀況。咱們須要想出更好的解決方案。this
在保持與上述例子類似的狀況下,咱們以某種方式引入新的計數器,該計數器捆綁了一個能自增的函數,並且沒有大量重複的代碼。下面嘗試使用閉包:翻譯
function createCounter() { var counter = 0; function increment() { counter = counter + 1; console.log("Number of events: " + counter); } return increment; }
讓咱們看看這是如何工做的。咱們將建立兩個計數器,並讓它們跟蹤兩個獨立的事件:
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
變量值。若是你從未接觸過將函數做爲數據來處理的函數式編程,這也許對你很是陌生。然而,這是很是常見的,並且只須要一些練習就能適應這一律念。你應該注意到這一點,createCounter()
的實現與咱們原先的計數器實現幾乎一致。惟一不一樣的是它被包裝或封裝在一個函數體內。所以,這些構造器都被稱爲閉包。
如今是棘手的部分:
createCounter()
的最後一步返回了局部函數 increment
。請注意,這並非返回調用函數的運行結果,而是函數自己。這就意味着,當咱們在這個代碼段下面建立新的計數器時,其實是生成新函數。
// fancyNewCounter is a function in this scope // fancyNewCounter 是當前做用域的一個函數 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 }; }
在上述代碼段,咱們簡單地返回一個對象,該對象包含了該閉包的全部功能。在某種意義下,咱們能定義閉包能返回的一系列信息。
如今,咱們能很是簡單地爲咱們的計數器引入減量(decrement)。
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
。若是能建立一個專門用於顯示計數器值的函數將會更好。讓咱們調用 display
函數。
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 中,閉包並非堆棧數據類型的最佳實現方式。用 Prototype 實現會對內存更友好(譯者注:在當前對象實例找不會相應屬性或方法時,會到相應實例共同引用的 Prototype 屬性尋找相應屬性或方法(若是在當前Prototype屬性找不到時,會沿着當前原型鏈向上查找),而Prototype 上的屬性或方法是公用的,而不像實例的屬性或方法那樣,各自單首創建屬性或方法,從而節省更多的內存)。
若是你具備 面向對象編程 的經歷,那麼你應該會注意到上述構造器看來很是像類、對象、實例值和私有/公有方法。
閉包與類類似,都會將一些能操做內部數據的函數聯繫在一塊兒。所以,你能在任何地方像使用對象同樣使用閉包。
閉包是編程語言一個很棒的屬性。當咱們想在 JavaScript 建立「真正的」隱藏域,或者須要建立簡單的構造器時,閉包這個屬性是很是好用的。不過對於通常的類來講,閉包可能仍是有點過重了。
感謝您的閱讀。 若是你以爲這篇文章對您有幫助或者以爲我翻譯得不錯,那給我個 star 吧。