通过定时器从第一个元素开始往后,每隔一秒输出arr数组中的一个元素。
<script>
var arr = ['one', 'two', 'three'];
for(var i = 0; i < arr.length; i++) {
setTimeout(function () {
console.log(arr[i]);
}, i * 1000);
}
</script>
但是运行过后,我们却会发现结果是每隔一秒输出一个“undefined”。
这是为什么呢?
setTimeout()函数与for循环在调用时会产生两个独立执行上下文环境,当setTimeout()函数内部的函数执行时,for循环已经执行结束,而for循环结束的条件是最后一次i++执行完毕,此时i的值为3,所以实际上setTimeout()函数每次执行时,都会输出arr[3]的值。而因为arr数组最大索引值为2,所以会间隔一秒输出“undefined”。
尝试如下代码,发现就可以了。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
<script>
var arr = ['one', 'two', 'three'];
for(var i = 0; i < arr.length; i++) {
(function (time) {
setTimeout(function () {
console.log(arr[time]);
}, time * 1000);
})(i);
}
</script>
</head>
<body>
</body>
</html>
对比两个代码的区别,有什么不同呢?
先谈谈什么是执行上下文环境。
JavaScript每段代码的执行都会存在于一个执行上下文环境中,而任何一个执行上下文环境都会存在于整体的执行上下文环境中。根据栈先进后出的特点,全局环境产生的执行上下文环境会最先压入栈中,存在于栈底。当新的函数进行调用时,会产生的新的执行上下文环境,也会压入栈中。当函数调用完成后,这个上下文环境及其中的数据都会被销毁,并弹出栈,从而进入之前的执行上下文环境中。
第二个代码通过立即执行函数将索引i作为参数传入,在立即函数执行完成后,由于setTimeout()函数中有对arr变量的引用,其执行上下文环境不会被销毁,因此对应的i值都会存在内存中。所以每次执行setTimeout()函数时,i都会是数组对应的索引值0、1、2,从而间隔一秒输出“one”“two”“three”。
下面引出闭包的概念,即函数声明和函数表达式可以位于另一个函数的函数体内,在内部函数中可以访问外部函数声明的变量,当这个内部函数在包含它们的外部函数之外被调用时,就会形成闭包。