[譯]玩轉 JavaScript 面試:何爲閉包?

原文地址 Medium - Master the JavaScript Interview: What is a Closure?javascript

坦白的講,不掌握閉包這個知識點的話你是不會在 JavaScript 這條路上走太遠的。你不只要掌握閉包的機制是什麼,還要知道閉包的重要性在哪,同時能輕鬆的寫出幾個可行的閉包用例。java

閉包在 JavaScript 中常常用來進行對象數據私有化,在事件處理程序和回調函數中也經常會用到。此外還有偏分函數和柯里化及其餘編程模式中也會用到閉包。面試

我根本不在乎一個面試者是否知道「閉包」這個詞或者其專業定義。我想知道的是他們是否懂得其基本的運行機制。若是面試者不知道的話,那就很明顯的表示他們並無足夠的構建實際 JavaScript 應用的經驗。編程

若是你不能回答這個問題,那你就是一個初級開發人員。我不會在意你到底有幾年的編程經驗。閉包

上面的話可能聽起來很刻薄,但請你認真考慮一下個人言外之意。大多數面試官都會問你有關閉包是什麼的問題,而大多數時候你的一個錯誤答案的代價就是失去一份工做。就算你夠幸運的拿到了這份工做的 offer,你也會在年薪上無形損失上萬美圓。由於你會以初級開發工程師的身份被招進公司,你的工做經驗有多久人家是不會在意的。app

快速小測驗:你能說出兩種閉包的使用場景嗎?函數式編程

什麼是閉包?

閉包即函數與其引用的周邊狀態(詞法環境)綁定在一塊兒造成的(封裝)組合。換句話說,閉包可讓咱們從函數內部訪問其外部函數的做用域。在 JavaScript 中,每當函數建立,閉包就被建立。函數

爲了使用閉包,咱們能夠簡單的將一個函數定義在另外一個函數的內部,而後將其暴露給外部,返回這個函數或者是把它傳給另外一個函數。ui

內部函數會擁有訪問外部函數做用域中變量的能力,即便是外部函數已經執行完畢並銷燬。this

使用閉包(示例)

閉包最經常使用於實現對象私有數據。數據私有是一項重要的特性,讓咱們可以面向接口編程而不是面向實現編程。這個重要的概念能幫助咱們構建健壯的軟件,由於實現細節相對於接口約定來講更容易被突發性改變。

在 JavaScript 中,閉包做爲首要方式被用來實現數據私有化。當你這麼作的時候,封裝的變量就只能在包含(外部)函數的做用域內。你沒法繞過對象被受權的方法在外部訪問這些數據。在 JavaScript 中,定義在閉包做用域下的公開方法才能夠訪問這些數據。例如:

const getSecret = (secret) => {
  return {
    get: () => secret
  };
};

test('Closure for object privacy.', assert => {
  const msg = '.get() should have access to the closure.';
  const expected = 1;
  const obj = getSecret(1);

  const actual = obj.get();

  try {
    assert.ok(secret, 'This throws an error.');
  } catch (e) {
    assert.ok(true, `The secret var is only available to privileged methods.`);
  }

  assert.equal(actual, expected, msg);
  assert.end();
});
複製代碼

在上例中,.get() 方法定義在 getSecret() 做用域內,這就使得它能訪問 getSecret() 中的任意變量,並使其成爲私有方法。在本例中它能夠訪問參數 secret

對象不是惟一能夠產生數據私有化的東西。閉包也能夠被用來建立有狀態的函數,而這些函數返回的值可能會受到其內部狀態的影響,例如:

const secret = msg => () => msg;
複製代碼
const secret = (msg) => () => msg;

test('secret', assert => {
  const msg = 'secret() should return a function that returns the passed secret.';

  const theSecret = 'Closures are easy.';
  const mySecret = secret(theSecret);

  const actual = mySecret();
  const expected = theSecret;

  assert.equal(actual, expected, msg);
  assert.end();
});
複製代碼

在函數式編程中,閉包常常被用於偏函數應用和柯里化。下面給出一些相關定義:

應用: 使用函數的參數得到返回值的過程

偏函數應用: 是傳給某個函數其中一部分參數,而後返回一個新的函數,該函數等待接收後續參數的過程。換句話說,偏函數應用是一個函數,它接受另外一個函數爲參數,這個做爲參數的函數自己接收多個參數,它返回一個函數,這個函數與它的參數相比接收更少的參數。偏函數應用提早給出一部分參數,而返回的函數則會等待調用時傳入剩餘的參數。

偏函數應用經過閉包做用域來提早給出參數。你能夠實現一個通用的函數來給出指定的函數部分參數,示例以下:

partialApply(targetFunction: Function, ...fixedArgs: Any[]) =>
  functionWithFewerParams(...remainingArgs: Any[])
複製代碼

它接收一個接收任意數量參數的函數,咱們只是將部分參數應用到函數上,用返回的函數來接收剩餘參數。

下面給出一個兩數相加的例子:

const add = (a, b) => a + b;
複製代碼

如今假設你想要一個函數,功能是對任意數字加 10,函數名爲 add10()。那麼 add10(5) 的結果應該是 15。所以 partialApply() 函數能夠這麼調用:

const add10 = partialApply(add, 10);
add10(5);
複製代碼

在本例中,參數 10 做爲固定參數在閉包做用域 add10() 中被記住了。

讓咱們來看一下 partialApply() 的一種實現:

const partialApply = (fn, ...fixedArgs) => {
  return function (...remainingArgs) {
    return fn.apply(this, fixedArgs.concat(remainingArgs));
  };
};


test('add10', assert => {
  const msg = 'partialApply() should partially apply functions'

  const add = (a, b) => a + b;

  const add10 = partialApply(add, 10);


  const actual = add10(5);
  const expected = 15;

  assert.equal(actual, expected, msg);
});
複製代碼

能夠看到,函數返回了一個保留了對 fixedArgs 訪問的函數,而 fixedArgs 就是咱們傳給 partialApply() 的參數。

相關文章
相關標籤/搜索