天天看點

掌握Javascript面試:什麼是閉包?

文章來源于: https://medium.com/javascript-scene/master-the-javascript-interview-what-is-a-closure-b2f0d2152b36

在JavaScript的面試中我通常将這個問題放在第一個或者最後一個問題。坦白地說,如果你沒有深入的學習閉包你的JavaScript不可能有很深的造詣。

你可能JavaScript稍好點兒,但你真的了解如何建構一個好的JavaScript應用嗎?你真的了解什麼正在運作,或者應用如何工作的嗎?我對此表示懷疑。不知道這個問題的答案在面試的過程中是一個危險的信号。

你不僅應該知道閉包的工作機制是什麼,你還應該知道他為什麼發生,并且你應該很輕松的回答幾種常用的閉包用例。

閉包常用在JavaScript對象的資料私有化,事件句柄,回調函數,和

在局部應用,柯裡化

,以及其他功能的變成形式。

我不在乎面試候選人是否知道‘closure’這個詞語或者技術定義。我想要弄明白的是他們是否了解基本的運作機制。如果他們不知道,顯而易見這些面試候選者并沒有大量的實際JavaScript應用開發經驗。

“如果你不能回答這個問題,你就是一個初級開發人員。我不管你工作了幾年。”

這聽起來意味着什麼,但實際上并不是。我的意思是大多數稱職的面試官會問你什麼是閉包,并且在大多數時候你回答錯誤将失去這份工作。如果你足夠幸運的話,你将得到一個offer,他們将給你一個初級開發人員的工資而不是一個進階的開發人員。

準備好快速跟進:“你能說出兩種常用的閉包嗎?”

什麼是閉包?

閉包就是一個函數(封閉的)的集合引用的環境(詞法環境)狀态。換句話說,閉包有能力從一個内部函數通路外部函數的作用域。在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()’通路任何變量,并使它 成為一個特權方法。

使對象的資料私有化并不是閉包的唯一用途。它也可以用來建立有狀态的函數,這些函數的傳回值可能受他們内部狀态的影響。示例如下:

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[])
           

它将接受一個帶有任意數量參數的函數,接下來我們想要部分的應用函數的參數然後傳回一個帶有剩餘參數的函數。

下面一個例子,一個求兩個數字和的函數:

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

現在你想要一個實作對任意數字都加10的函數,我們命名它為‘add10()’。‘add10(5)’的結果應該是‘15’,我們頂一個‘partialAply()’的函數,如下:

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

在這個例子中,參數‘10’變成了一個固定的參數被儲存在‘add10()’的閉包作用域中。

下面是‘partialApply()’的實作代碼:

// 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);
});
           

正如我們從上面的示例中看到的,這個簡單的傳回函數可以通路‘fixArgs’參數,這個參數是從‘partialApply()’中傳入的。

未經許可不得轉載!

繼續閱讀