闭包

闭包的定义很简单:函数 A 返回了一个函数 B,并且函数 B 中使用了函数 A 的变量,函数 B 就被称为闭包。

  1. function A() {
  2. let a = 1
  3. function B() {
  4. console.log(a)
  5. }
  6. return B
  7. }

你是否会疑惑,为什么函数 A 已经弹出调用栈了,为什么函数 B 还能引用到函数 A 中的变量。因为函数 A 中的变量这时候是存储在堆上的。现在的 JS 引擎可以通过逃逸分析辨别出哪些变量需要存储在堆上,哪些需要存储在栈上。

经典面试题,循环中使用闭包解决 var 定义函数的问题

  1. for ( var i=1; i<=5; i++) {
  2. setTimeout( function timer() {
  3. console.log( i );
  4. }, i*1000 );
  5. }

首先因为 setTimeout 是个异步函数,所有会先把循环全部执行完毕,这时候 i 就是 6 了,所以会输出一堆 6。

解决办法两种,第一种使用闭包

  1. for (var i = 1; i <= 5; i++) {
  2. (function(j) {
  3. setTimeout(function timer() {
  4. console.log(j);
  5. }, j * 1000);
  6. })(i);
  7. }

第二种就是使用 setTimeout 的第三个参数

  1. for ( var i=1; i<=5; i++) {
  2. setTimeout( function timer(j) {
  3. console.log( j );
  4. }, i*1000, i);
  5. }

第三种就是使用 let 定义 i

  1. for ( let i=1; i<=5; i++) {
  2. setTimeout( function timer() {
  3. console.log( i );
  4. }, i*1000 );
  5. }

因为对于 let 来说,他会创建一个块级作用域,相当于

  1. { // 形成块级作用域
  2. let i = 0
  3. {
  4. let ii = i
  5. setTimeout( function timer() {
  6. console.log( ii );
  7. }, i*1000 );
  8. }
  9. i++
  10. {
  11. let ii = i
  12. }
  13. i++
  14. {
  15. let ii = i
  16. }
  17. ...
  18. }