function test(){
for (var i = 0; i < 5; i ) {
(function() { // j = i
var j =i;
setTimeout(function() {
console.log(new Date, j);
}, 1000);
})();
}
console.log(new Date, i);
}
方法2:函数调用按值传递:
var output = function (i) {
setTimeout(function() {
console.log(new Date, i);
}, 1000);
};
function test(){
for (var i = 0; i < 5; i ) {
output(i); // 这里传过去的 i 值被复制,而不是引用
}
console.log(new Date, i);
}
方法2:函数调用按值传递技巧版(利用setTimeout第三个参数):
function test(){
for (var i = 0; i < 5; i++) {
setTimeout(function(i) {
console.log(new Date, i);
}, 1000, i);
}
console.log(new Date, i);
}
方法3: ES6 使用le指令声明:
function test(){
for (let i=0; i<5; i ) {
setTimeout( function timer() {
console.log(new Date(),i);
}, 1000 );
}
// console.log('end',new Date(),i); //因为变量作用域的问题,这里会报i 不存在,未声明
}
function test(){
for (var i = 0; i < 5; i ) {
(function() { // j = i
var j =i;
setTimeout(function() {
console.log(new Date, j);
}, 1000);
})();
}
console.log(new Date, i);
}
写于:2017-06-08
起
最近一道面试试题非常火热,堪称面试界网红:
不理解闭包,变量作用域和setTimeout函数的同学很多会给出答案A:0,1,2,3,4,5和答案B:5,0,1,2,3,4;不奇怪,但正确答案却是5,5,5,5,5,我以前也是。当然相比较,说出答案B至少比答案A多知道setTimeout函数的用法,重点不在那个延迟1000ms,重点在setTimeout函数,后面会细说。 首先三个概念:
在for循环声明的五个TimeOut Callback函数都有对变量i的引用,而不是拷贝。因为5个timeout函数都涉及到延迟执行的情况,所以当主线程执行完后(end被打印时),timeout这些回调依次执行(队列:FIFO),此时i的值已经为5了,知道以上这些,后面就简单多了。
解
开始回到正题: 其实写出这个函数期望输出5,0,1,2,3,4,要达到这个结果,方法有多种,这里列出典型的三种:
方法2:函数调用按值传递:
方法2:函数调用按值传递技巧版(利用setTimeout第三个参数):
方法3: ES6 使用le指令声明:
细度上面的三种方法,其实他们相似度很高。首先方法1(声明即执行)和方法2(提前声明,调用时执行),其实他们的思路完全一致,都利用了JavaSrcipt中函数基本类型变量传值,都是值的拷贝,而不是值的引用,然后通过在for循环中执行一个闭包函数,建立一个闭包作用域,来保证引用的i值为注册该回调函数时的值。立即即执行,如果看着别扭,下面这样写也是可以的:
然后方法3,是利用ES6 let命令声明变量块级作用域的概念,和前面for循环使用var声明i不同的是,var声明的i在整个test()函数作用域内有效,每一次循环, 新的i值都会覆盖旧值;而let声明的, 当前的i只在本轮循环有效, 在ES6称为块级作用域。所以每一次循环的i其实都是一个新的变量,所以也导致打印end时,报i不存在,未声明的错误,这就是块级作用域的效果,所以5个timeout回调函数虽然都引用了变量i,但实际上这5个i是独立的,仅在自己的块级作用域内有效,其写法类似于:
最后
所以总体来看,上面的方法解决的思路都是从作用域这个概念上下手的,前两者利用function声明形成了自己的作用域,后者利用let命令形成的块级作用域,而来确保对i值的正确引用。 以上就是自己对这个网红面试题的深入理解,如果有说的有错或模棱两可的地方,还请不吝指教。