「翻译系列」-- 你能回答7个闭包的面试题么?

1,786 阅读3分钟

原文:dmitripavlutin.com/javascript-… 作者:Dmitri Pavlutin

作为js开发,必须知道闭包是什么。在前端面试中,很可能会被问到闭包的概念。

我整理了7个有趣且比较有难度的问题。

准备好一只笔和一张纸,尽量不看答案或者敲代码运行。我估算你大概需要30分钟。

尽情开始吧!

如果你需要学习闭包,我推荐看闭包简单的例子

目录

1、哪个是闭包

2、参数问题

3、谁的谁

4、棘手的闭包

5、消息对还是错

6、恢复封装

7、智能相乘

小结

1、哪个是闭包

思考一下三个函数: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);

问题:以上哪个是闭包以及为什么?

答案:

判断是否是闭包的简单规则就是,一个函数是否能访问外部函数的变量
1、clickHandler函数是闭包,因为它能访问外部的countCLicks。
2、immediate函数不是闭包,因为它没有访问到外部的任何一个变量。
3、delayedReload函数是闭包,因为它访问到全局变量location,也就是最顶层的函数域。

2、 参数问题

以下代码打印出什么?

(function immediateA(a) {
  return (function immediateB(b) {
    console.log(a); // 打印出什么
  })(1);
})(0);
答案:

打印出 0
因为immediateA函数的参数是0,因此传输给a0.
然后immediateB又是在immediateA的函数里,而且它是一个闭包的,所以immediateB里的a能访问到外面immediateA的a,所以打印出 0

3: 谁的谁

以下的代码块打印出什么

let count = 0;
(function immediate() {
  if (count === 0) {
    let count = 1;
    console.log(count); // What is logged?
  }
  console.log(count); // What is logged?
})();
答案:

打印出 10
因为一开头声明了count = 0,然后在immediaye函数是一个闭包,因为它的count能访问到一开头声明的那个count,所以此时count是0,然后在条件块上,因为满足count===0的条件,所以进入条件块里,然后因为let具有块级作用域,所以用let声明count时,此时的count为1,所以第一个console.log(count)打印出1
第二个console.log(count)因为是在immediate函数里,而count是会访问到外部的count,也就是一开头声明的那个count,所以为0

4: 棘手的闭包

以下的代码块打印出什么

for (var i = 0; i < 3; i++) {
  setTimeout(function log() {
    console.log(i); // What is logged?
  }, 1000);
}
答案:

打印出 3,3,3
该代码块执行有两个阶段:
阶段一:
1、for循环有3次,每次循环时都会创建一个新的log函数,而log函数里的setTimeout()是在1000ms后开始执行的。
2、循环完成后,i就变成3,而setTimeout还没开始执行的。
阶段二:
第二个就是发生在1000ms后:
1、setTimeou()就开始执行的,因为是闭包的,所以里面的i能访问到外部的i,而外部的i此时就是3,所以打印出3,之后的setTimeou也是如此的。
这就是为什么打印出333的原因。


挑战另一个问题:如何让这个代码块打印出012?请在下面的评论写下你的答案。

挑战另一个问题:如何让这个代码块打印出0,1,2?请在下面的评论写下你的答案。

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(); // What is logged?
答案:

打印出 Count is 0
increment被调用3次,每次count都+13次后就成为3.
message变量是在createIncrement函数内,它的初始化是“count is 0"。然而,即使count增加1,message始终保持“count is 0"
log函数是一个闭包,它能访问到外部的message,所以打印出“count is 0"


挑战另一个问题:如何让message同步显示count的数?请在下面的评论写下你的答案。

挑战另一个问题:如何让message同步显示count的数?请在下面的评论写下你的答案。

6、恢复封装

createStack函数创建stack的实例:

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]; // 破坏封装

这stack运行看起来正常的,但有一个小小的问题,items属性被暴露了,所以任何人能直接修改这个属性。

这确实会破坏stack的封装,按理来说应该只有push和pop方法被公开的,而items就不应该被公开的。

利用闭包的概念来重构上面的createStack函数,实现items不能被初createStack函数之外访问。

function createStack() {
  // 写下你的代码
}

const stack = createStack();
stack.push(10);
stack.push(5);
stack.pop(); // => 5

stack.items; // => undefined
答案:

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

7、智能相乘

在multiply函数内,写下两个数相乘。

function multiply(num1, num2) {
  // Write your code here...
}

如果multiply有两个参数,则返回两个参数相乘的结果

如果multiply只有一个参数,比如说const anotherFunc = multiply(num1),则返回anotherFunc函数,然后anotherFunc函数又赋值给一个参数num2,则返回num1 * num2的结果

multiply(4, 5); // => 20
multiply(3, 3); // => 9

const double = multiply(2);
double(5);  // => 10
double(11); // => 22
答案:

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


小结

对比你的答案:

1、如果正确有5个以及以上,说明你的理解能力强;

2、如果没达到,我推荐你看看闭包的例子

还想挑战吗?来试试这个7个简单但比较棘手的js面试题