這7道關(guān)于閉包的面試題,你能答對幾個?
每個 JavaScript 程序員都必須知道閉包是什么。在 JavaScript 面試中,你很可能會被問到閉包的概念。
以下是 7 個有關(guān) JavaScript 閉包的面試題,比較有挑戰(zhàn)性。
不要查看答案或運行代碼,看看自己的水平到底如何。做完這些題大約需要半小時左右。
1. 熱身
有以下函數(shù) clickHandler,immediate和delayedReload:
- let countClicks = 0;
- button.addEventListener('click', function clickHandler() {
- countClicks++;
- });
- const result = (function immediate(number) {
- const message = `number is: ${number}`;
- return message;
- })(100);
- setTimeout(function delayedReload() {
- location.reload();
- }, 1000);
這3個函數(shù)中哪個能夠訪問外部范圍變量?
答案:
- clickHandler 能夠從外部作用域訪問變量 countClicks。
- immediate 無法訪問外部作用域中的任何變量。
- delayedReload 從全局作用域(也就是最外層作用域)中訪問全局變量 location。
2. 丟失的參數(shù)
下列代碼輸出什么:
- (function immediateA(a) {
- return (function immediateB(b) {
- console.log(a); // => ?
- })(1);
- })(0);
答案:
- 輸出為:0
- 用參數(shù) 0 調(diào)用 immediateA,因此 a 參數(shù)為 0。
- immediateB 函數(shù)嵌套在 immediateA 函數(shù)中,是一個閉包,它從外部 immediateA作用域中得到 a 變量,其中 a 為 0。因此 console.log(a) 的輸出為 0。
3. 誰是誰
下面的代碼將會輸出什么內(nèi)容?
- let count = 0;
- (function immediate() {
- if (count === 0) {
- let count = 1;
- console.log(count); // 輸出什么?
- }
- console.log(count); // 輸出什么?
- })();
答案:
- 輸出 1 和 0
- 第一個語句 let count = 0 聲明了一個變量 count。
- immediate() 是一個閉包,它從外部作用域得到 count 變量。在 immediate() 函數(shù)作用域內(nèi), count 是 0。
但是,在條件內(nèi),另一個 let count = 1 聲明了局部變量 count,該變量覆蓋了作用域之外的 count。第一個 console.log(count) 輸出 1。
第二個 console.log(count) 輸出為 0 ,因為這里的 count 變量是從外部作用域訪問的。
4. 棘手的閉包
下列代碼輸出什么:
- for (var i = 0; i < 3; i++) {
- setTimeout(function log() {
- console.log(i); // => ?
- }, 1000);
- }
答案輸出:
- 3, 3, 3。
- 代碼分為兩個階段執(zhí)行。
階段1:
- for() 重復(fù) 3 次。在每次循環(huán)都會創(chuàng)建一個新函數(shù) log(),該函數(shù)將捕獲變量 i。setTimout() 安排log() 在 1000 毫秒后執(zhí)行。
- 當(dāng) for() 循環(huán)完成時,變量 i 的值為 3。
階段2:
第二階段發(fā)生在 1000ms 之后:
- setTimeout() 執(zhí)行預(yù)定的 log() 函數(shù)。log() 讀取變量 i 當(dāng)前的值 3,并輸出 3
- 所以輸出 3, 3, 3。
5. 錯誤的信息
下面的代碼將會輸出什么:
- function createIncrement() {
- let count = 0;
- function increment() {
- count++;
- }
- let message = `Count is ${count}`;
- function log() {
- console.log(message);
- }
- return [increment, log];
- }
- const [increment, log] = createIncrement();
- increment();
- increment();
- increment();
- log(); // => ?
答案:
輸出:'Count is 0'
- increment() 函數(shù)被調(diào)用 3 次,將 count 增加到 3。
- message 變量存在于 createIncrement() 函數(shù)的作用域內(nèi)。其初始值為 'Count is 0'。但即使 count 變量已經(jīng)增加了幾次,message 變量的值也始終為 'Count is 0'。
- log() 函數(shù)是一個閉包,它從 createIncrement() 作用域中獲取 message 變量。console.log(message) 輸出錄'Count is 0'到控制臺。
6. 重新封裝
下面的函數(shù) createStack() 用于創(chuàng)建棧結(jié)構(gòu):
- function createStack() {
- return {
- items: [],
- push(item) {
- this.items.push(item);
- },
- pop() {
- return this.items.pop();
- }
- };
- }
- const stack = createStack();
- stack.push(10);
- stack.push(5);
- stack.pop(); // => 5
- stack.items; // => [10]
- stack.items = [10, 100, 1000]; // 棧結(jié)構(gòu)的封裝被破壞了
它能正常工作,但有一個小問題,因為暴露了 stack.items 屬性,所以任何人都可以直接修改 items 數(shù)組。
這是一個大問題,因為它破壞了棧的封裝:應(yīng)該只有 push() 和 pop() 方法是公開的,而 stack.items 或其他任何細(xì)節(jié)都不能被訪問。
使用閉包的概念重構(gòu)上面的棧實現(xiàn),這樣就無法在 createStack() 函數(shù)作用域之外訪問 items 數(shù)組:
- function createStack() {
- // 把你的代碼寫在這里
- }
- const stack = createStack();
- stack.push(10);
- stack.push(5);
- stack.pop(); // => 5
- stack.items; // => undefined
答案:
以下是對 createStack() 的重構(gòu):
- function createStack() {
- const items = [];
- return {
- push(item) {
- items.push(item);
- },
- pop() {
- return items.pop();
- }
- };
- }
- const stack = createStack();
- stack.push(10);
- stack.push(5);
- stack.pop(); // => 5
- stack.items; // => undefined
items 已被移至 createStack() 作用域內(nèi)。
這樣修改后,從 createStack() 作用域的外部無法訪問或修改 items 數(shù)組?,F(xiàn)在 items 是一個私有變量,并且棧被封裝:只有 push() 和 pop() 方法是公共的。
push() 和 pop() 方法是閉包,它們從 createStack() 函數(shù)作用域中得到 items變量。
7. 智能乘法
編寫一個函數(shù) multiply() ,將兩個數(shù)字相乘:
- function multiply(num1, num2) {
- // 把你的代碼寫在這里...
- }
要求:
如果用 2 個參數(shù)調(diào)用 multiply(num1,numb2),則應(yīng)返回這 2 個參數(shù)的乘積。
但是如果用 1個參數(shù)調(diào)用,則該函數(shù)應(yīng)返回另一個函數(shù):const anotherFunc = multiply(num1) 。返回的函數(shù)在調(diào)用 anotherFunc(num2) 時執(zhí)行乘法 num1 * num2。
- multiply(4, 5); // => 20
- multiply(3, 3); // => 9
- const double = multiply(2);
- double(5); // => 10
- double(11); // => 22
答案:
以下是 multiply() 函數(shù)的一種實現(xiàn)方式:
- function multiply(number1, number2) {
- if (number2 !== undefined) {
- return number1 * number2;
- }
- return function doMultiply(number2) {
- return number1 * number2;
- };
- }
- multiply(4, 5); // => 20
- multiply(3, 3); // => 9
- const double = multiply(2);
- double(5); // => 10
- double(11); // => 22
如果 number2 參數(shù)不是 undefined,則該函數(shù)僅返回 number1 * number2。
但是,如果 number2 是 undefined,則意味著已經(jīng)使用一個參數(shù)調(diào)用了 multiply() 函數(shù)。這時就要返回一個函數(shù) doMultiply(),該函數(shù)稍后被調(diào)用時將執(zhí)行實際的乘法運算。
doMultiply() 是閉包,因為它從 multiply() 作用域中得到了number1 變量。