征服 JavaScript 面試:什么是閉包?

czuh0543 7年前發布 | 9K 次閱讀 閉包 JavaScript開發 JavaScript

“征服 JavaScript 面試”是我寫的一系列文章,來幫助面試者準備他們在面試 JavaScript 中、高級職位中將可能會遇到的一些問題。這些問題我自己在面試中也經常會問。

在我面試時問出的一系列問題里,閉包通常是我問的第一個或最后一個問題。坦白地說,如果你連閉包也弄不明白,你是不會在 JavaScript 的道路上走多遠的。

你別東張西望,說的就是你。你真的理解如何構建一個嚴謹的 JavaScript 應用?你真的理解代碼背后發生的事情或者說一個應用程序是如何工作的?我表示懷疑。如果連個閉包問題都搞不清的話,真是有點夠嗆。

你不僅僅應該了解閉包的機制,更應該了解閉包為什么很重要,以及能夠很容易地回答出閉包的幾種可能的應用場景。

閉包在 JavaScript 中常用來實現對象數據的私有,在事件處理和回調函數中也常常會用到它,此外還有 偏函數應用(partial applications)和柯里化(currying) ,以及其他函數式編程模式。

我不在乎面試者是否知道“closure”這個單詞或者它的專業定義。我只想弄清他們是否理解基本原理。如果他們沒有,那么通常意味著這些面試者在構建實際 JavaScript 應用方面并沒有很多經驗。

如果你不能回答這個問題,你只是個初級開發者。不管你實際上已經干這個多久了。

為了快速理解下面的內容:你想一下能否舉出兩個閉包的通用場景?

什么是閉包?

簡言之, 閉包 是由函數引用其周邊狀態( 詞法環境 )綁在一起形成的(封裝)組合結構。在 JavaScript 中,閉包在 每個函數被創建時 形成。

這是基本原理,但為什么我們關心這些?實際上,由于閉包與它的詞法環境綁在一起,因此 閉包讓我們能夠從一個函數內部訪問其外部函數的作用域

要使用閉包,只需要簡單地將一個函數定義在另一個函數內部,并將它暴露出來。要暴露一個函數,可以將它返回或者傳給其他函數。

內部函數將能夠訪問到外部函數作用域中的變量,即使外部函數已經執行完畢。

閉包使用的例子

閉包的用途之一是實現對象的私有數據。數據私有是讓我們能夠面向接口編程而不是面向實現編程的基礎。而面向接口編程是一個重要的概念,有助于我們創建更加健壯的軟件,因為實現細節比接口約定相對來說更加容易被改變。

“面向接口編程,別面向實現編程。” 設計模式:可復用面向對象軟件的要素

在 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;
// Secret - creates closures with secret messages.
// https://gist.github.com/ericelliott/f6a87bc41de31562d0f9
// https://jsbin.com/hitusu/edit?html,js,output

// secret(msg: String) => getSecret() => msg: String
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[])

partialApply 接受一個多參數的函數,以及一串我們想要提前賦給這個函數的參數,它返回一個新的函數,這個函數將接受剩余的參數。

下面給一個例子來說明,假設你有一個函數,求兩個數的和:

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

現在你想要得到一個函數,它能夠對任何傳給它的參數都加 10,我們可以將它命名為 add10() 。 add10(5) 的結果應該是 15 。我們的 partialApply() 函數可以做到這個:

const add10 = partialApply(add, 10);
add10(5);

在這個例子里,參數 10 通過閉包作用域被提前賦予 add() ,從而讓我們獲得 add10() 。

現在讓我們看一下如何實現 partialApply() :

Let’s look at a possible partialApply() implementation:

// Generic Partial Application Function
// https://jsbin.com/biyupu/edit?html,js,output
// https://gist.github.com/ericelliott/f0a8fd662111ea2f569e

// partialApply(targetFunction: Function, ...fixedArgs: Any[]) =>
//   functionWithFewerParams(...remainingArgs: Any[])
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);
});

如你所見,它只是簡單地返回一個函數,這個函數通過閉包訪問了傳給 partialApply() 函數的 fixedArgs 參數。

輪到你來試試了

你用閉包來做什么?如果你有最喜歡的應用場景,舉一些例子,在評論中告訴我。

 

來自:http://www.zcfy.cc/article/master-the-javascript-interview-what-is-a-closure-2127.html

 

 本文由用戶 czuh0543 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!