【譯】閉包並不神祕

計數器

首先,從一個計數器開始。編程

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可能會更好一些

閉包和OOP

若是你作過面向對象編程,你可能會注意到,上面的結構特別像類啊對象啊實例變量和公有/私有方法。

按:很久沒翻譯了,這篇文章翻譯的有些智障,可是不耽誤看代碼,看完仍是頗有收穫的!

英文原文:http://renderedtext.com/blog/...

相關文章
相關標籤/搜索