函数声明与闭包的问题

下面是这个闭包面试题一直一知半解的,对于输出的结果我是这么理解的,每次循环的时候执行一遍立即执行函数,然后往匿名函数里传入一个形参i,每次立即执行函数调用的时候产生的匿名函数都不是同一个,所以console.log所引用的变量i也不是同一个形参,所以每次定时器输出的都是当前循环时的i。

那是不是在每次for循环的过程中就产生了一个新的匿名函数呢,如果是这样的话就有另外一个问题了,function不是会声明提前吗,难道for循环真正执行之前已经提前声明了10个函数了?怎么从作用域或者内存的角度来解释为什么能得到这样的结果呢

function getNum() {
  for (var i = 0; i < 10; i++) {
    (function(i) {
        setTimeout(function() { console.log(i); }, 100 * i);
    })(i);
    }
}
//输出结果:0,1,2,3,4,5,6,7,8,9
阅读 2.3k
4 个回答
  1. 你在for里定义的匿名函数是个函数表达式,不是函数声明语句,不存在声明提升。
  2. 如果换成函数声明试试呢?
function getNum() {
  for (var i = 0; i < 10; i++) {
      function temp(i) {
          setTimeout(function() { console.log(i); }, 100 * i);
      }
      temp(i)
    }
}
//输出结果:0,1,2,3,4,5,6,7,8,9
getNum();

结果没变。这是为啥呢?
再验证下:

function getNum() {
  // "use strict"; // 严格模式和非严格模式都试试
  console.log(1, typeof temp)
  for (var i = 0; i < 10; i++) {
    console.log(2, typeof temp)
    
    function temp(i) {
        //setTimeout(function() { console.log(i); }, 100 * i);
    }
    temp(i)
  }
  console.log(3, typeof temp)
}

getNum();

验证后再看下Javascript块级域内的函数声明提升

先简化一下:

for (var i = 0; i < 10; i++) {
    setTimeout(function() {
        console.log(i);
    }, 100 * i);
}

此时执行的结果会是每隔 100ms 输出 10 个 10。因为当条件满足时执行的任务函数的作用域中并没有 i 变量,因此会向上查找作用域链,找到全局作用域,全局上下文中有 i 变量,其值为 10。

回到题中:

for (var i = 0; i < 10; i++) {
    (function() {
        setTimeout(function() {
            console.log(i);
        }, 100 * i);
    })();
}

这里使用了 立即执行函数表达式(IIFE),要注意的是它和函数声明不太一样。函数声明、变量声明是具有你说的 提升 的,但是 IIFE 不具有,你可以打开控制台试一试:

console.log(fn);
(function fn() {})();
// Uncaught ReferenceError: fn is not defined

所以这个时候执行的结果和预期的一致了:每隔 100 ms 依次输出 0 ~ 9。原因是:作用域链上多了匿名函数的上下文,当在作用域链上向上寻找时,先找到的是匿名函数的上下文,而它是有 i 变量的,也就不需要再到全局中找。

是的,每次都会产生一个匿名函数,并执行之。
JS 中只提升变量声明,如果都没有声明、只有一个表达式的话,就无所谓提升了。就算提升也不要紧,因为函数定义完毕就被执行了,不会出现差错,所以就算这里不是匿名函数也没有问题。

那是不是在每次for循环的过程中就产生了一个新的匿名函数呢,如果是这样的话就有另外一个问题了,function不是会声明提前吗,难道for循环真正执行之前已经提前声明了10个函数了?怎么从作用域或者内存的角度来解释为什么能得到这样的结果呢

    那是不是在每次 for 循环的过程中就产生了一个新的匿名函数呢?
    是的
    function 不是会声明提前吗?难道 for 循环真正执行之前已经提前声明了 10 个函数了?
    
    getNum() 是提前声明的。 在执行 for 循环时再创建的 10 个匿名函数
怎么从作用域或者内存的角度来解释为什么能得到这样的结果呢?

楼主提供的代码:

function getNum() {
  for (var i = 0; i < 10; i++) {
        (function(i) {
            setTimeout(function() {
                console.log(i); 
            }, 100 * i);
        })(i);
    }
}
代码拆解 1 :
(function(i) {
    ...
})(i);

JS 中 (function(){})(); 这串代码会被立刻执行
代码拆解 2 :
先贴上更加直观的代码:

function getNum() {
  for (var i = 0; i < 10; i++) {
          (function(x) {
            setTimeout(function(){
                console.log(x);
            }, x * 100);
        })(i);
    }
}
getNum();
    
在该匿名函数中接受一个参数 i ,在创建该匿名函数时每次将循环时 i 传递进去
    代码等同于:
        (function(接受参数的变量名) {
        })(0);
        
        (function(接受参数的变量名) {
        })(1);
        
        (function(接受参数的变量名) {
        })(2);
        ...
    此刻的 接受参数的变量名 就是 for 循环变量 i 的值
代码拆解 3 :
function 是块级作用域。

立刻执行 function 时会产生方法,会在内存中创建一个新的块几作用域,
    在该作用域内 i 指向于当前作用域下的变量参数 i,不引用 for 循环中初始化的变量 i。
为什么 i 的值不是最后的值

个人理解是这样的。当 JavaScript 立该执行该匿名函数时,在该匿名函数的执行上下文中,接受的参数的值就是在外部作用域中 i 的变量值。
当立刻执行该匿名函数时,会产生一个新的作用域,执行上下文。在该上下文中获取到的值就是外部作用域 i 的值。
由于就近原则不会访问到外部的作用域 i 的变量,自然不会随着 i 的值发生改变而改变,同时内部参数的 i 也不会影响到外部的 i 的值

推荐问题
宣传栏