说一道面试题,不要栽跟头

老姚
老姚 发布于 2016-06-15 22:02:32 浏览:1.17万 类型:原创 - 随笔 分类:面试经验 - 我也来说说系列 二维码: 作者原创 版权保护
题如下:
for (var i = 0; i < 3; i++) {
	setTimeout(function() {
		console.log(i);
	}, 0);
	console.log(i);
}

结果是:0 1 2 3 3 3
很多公司面试都爱出这道题,此题考察的知识点还是蛮多的。
为了防止初学者栽在此问题上,此文稍微分析一下。
都考察了那些知识点呢?
异步、作用域、闭包,你没听错,是闭包。
我们来简化此题:
setTimeout(function() {
        console.log(1);
}, 0);
console.log(2);
先打印2,后打印1。
因为是setTimeout是异步的。
正确的理解setTimeout的方式(注册事件):
有两个参数,第一个参数是函数,第二参数是时间值。
调用setTimeout时,把函数参数,放到事件队列中。等主程序运行完,再调用。
没啥不好理解的。就像我们给按钮绑定事件一样:
btn.onclick = function() {
        alert(1);
};
这么写完,会弹出1吗。不会!!只是绑定事件而已!
必须等我们去触发事件,比如去点击这个按钮,才会弹出1。
setTimeout也是这样的!只是绑定事件,等主程序运行完毕后,再去调用。
setTimeout的时间值是怎么回事呢?
比如:
setTimeout(fn, 2000)
我们可以理解为2000之后,再放入事件队列中,如果此时队列为空,那么就直接调用fn。如果前面还有其他的事件,那就等待。
因此setTimeout是一个约会从来都不准时的童鞋。
继续看:
setTimeout(function() {
        console.log(i);
}, 0);
var i = 1;
程序会不会报错?
不会!而且还会准确得打印1。
为什么?
因为真正去执行console.log(i)这句代码时,var i = 1已经执行完毕了!
所以我们进行dom操作。可以先绑定事件,然后再去写其他逻辑。
window.onload = function() {
        fn();
}
var fn = function() {
        alert('hello')
};
这么写,完全是可以的。因为异步!

es5中是没有块级作用域的
for (var i = 0; i < 3; i++) {}
console.log(i);
也就说i可以在for循环体外访问到。所以是没有块级作用域。
但此问题在es6里终结了,因为es6,发明了let。

这回我们再来看看原题。
原题使用了for循环。循环的本质是干嘛的?
是为了方便我们程序员,少写重复代码。
让我们倒退50年,原题等价于:
var i = 0;
setTimeout(function() {
	console.log(i);
}, 0);
console.log(i);
i++;
setTimeout(function() {
	console.log(i);
}, 0);
console.log(i);
i++;
setTimeout(function() {
	console.log(i);
}, 0);
console.log(i);
i++;

因为setTimeout是注册事件。根据前面的讨论,可以都放在后面。
原题又等价于如下的写法:
var i = 0;
console.log(i);
i++;
console.log(i);
i++;
console.log(i);
i++;
setTimeout(function() {
	console.log(i);
}, 0);
setTimeout(function() {
	console.log(i);
}, 0);
setTimeout(function() {
	console.log(i);
}, 0);

这回你明白了为啥结果是0 1 2 3 3 3了吧。

那个,老姚你说它是闭包,又是怎么回事?
为了很好的说明白这个事情,我们把它放到一个函数中:
var fn = function() {
        for (var i = 0; i < 3; i++) {
                setTimeout(function() {
                        console.log(i);
                }, 0);
                console.log(i);
        }
};
fn();
上面的函数跟我们常见另一个例子(div绑定事件)有什么区别:
var fn = function() {
        var divs = document.querySelectorAll('div');
        for (var i = 0; i < 3; i++) {
                divs[i].onclick = function() {
                        alert(i);
                };
        }
};
fn();
点击每个div都会弹出3。道理是一样的。因为alert(i)中的i是fn作用越中的,因而这是闭包。
《javascript忍者秘籍》书里把一个函数能调用全局变量,也称闭包。
因为作者认为全局环境也可以想象成一个大的顶级函数。
怎么保证能弹出0,1, 2呢。
解决之道:以毒攻毒!
再创建个闭包!!
var fn = function() {
        var divs = document.querySelectorAll('div');
        for (var i = 0; i < 3; i++) {
                divs[i].onclick = (function(i) {
                        return function() {
                                alert(i);
                        };
                })(i);
        }
};
fn();
或者如下的写法:
var fn = function() {
        var divs = document.querySelectorAll('div');
        for (var i = 0; i < 3; i++) {
                (function(i) {
                        divs[i].onclick = function() {
                                alert(i);
                        };
                })(i);
        }
};
fn();
因此原题如果也想setTimeout也弹出0,1,2的话,改成如下:
for (var i = 0; i < 3; i++) {
	setTimeout((function(i) {
		return function() {
			console.log(i);
		};
	})(i), 0);
	console.log(i);
}

本文完。
z
给个赞 72 人点赞
收藏 91 人收藏
评论 已有 48 条评论;以下用户言论只代表其个人观点,不代表 前端网(QDFuns) 的观点或立场。
登录 以后才能发表评论
最热评论
老姚
老姚2016-06-16 10:12:516F
此题是一个同事问我的。我才想起来说这个事情的。得需要专门去做搜集的工作。网上倒是有不少那样的帖子,哈哈。没事挑两道讲讲。 //@姚敏娇:嗯。是的。可以的。刚好最近在看js的书。多给些面试题,看的会更透彻emoticon //@老姚:哦是吗,我在想是否要开个系列,专门讲讲面试题的。以面试题来学js。估计能有不少人喜欢。 //@姚敏娇:看过很多人讲这道面试题了。你讲的最清楚明白了emoticon
举报 支持 (2) 回复 (1)
最新评论
doterlin
doterlin2016-06-16 16:59:3718F
最近也要换工作,老姚能出一个面试题系列那当然最好了。顶一个。 //@老姚:此题是一个同事问我的。我才想起来说这个事情的。得需要专门去做搜集的工作。网上倒是有不少那样的帖子,哈哈。没事挑两道讲讲。 //@姚敏娇:嗯。是的。可以的。刚好最近在看js的书。多给些面试题,看的会更透彻emoticon //@老姚:哦是吗,我在想是否要开个系列,专门讲讲面试题的。以面试题来学js。估计能有不少人喜欢。 //@姚敏娇:看过很多人讲这道面试题了。你讲的最清楚明白了emoticon
举报 支持 (0) 回复 (1)
doterlin
doterlin2016-06-16 16:57:2517F
写的让人豁然开朗。赞
举报 支持 (0) 回复 (0)
老姚
老姚2016-06-16 15:19:2816F
这样的写法等价于展开代码
for (var i = 0; i < 3; i++) {
	var fn = function(i) {
			console.log(i);
	};
	var result = fn(i);
	setTimeout(result, 0);
	console.log(i);
}
//@mano展开代码
for (var i = 0; i < 3; i++) {
	setTimeout((function(i) {
		return function() {
			console.log(i);
		};
	})(i), 0);
	console.log(i);
}

只要这样就可以了
展开代码
for (var i = 0; i < 3; i++) {
	setTimeout((function(i) {
		 
			console.log(i);
	 
	})(i), 0);
	console.log(i);
}
不用再包一个块了吧
举报 支持 (0) 回复 (1)
老姚
老姚2016-06-16 15:15:2615F
setTimeout的第一个参数最好是函数,不写字符串。你的写法,已经不是函数了。你再运行一下。就明白了。 //@mano展开代码
for (var i = 0; i < 3; i++) {
	setTimeout((function(i) {
		return function() {
			console.log(i);
		};
	})(i), 0);
	console.log(i);
}

只要这样就可以了
展开代码
for (var i = 0; i < 3; i++) {
	setTimeout((function(i) {
		 
			console.log(i);
	 
	})(i), 0);
	console.log(i);
}
不用再包一个块了吧
举报 支持 (0) 回复 (0)
mano
mano2016-06-16 15:09:3914F
展开代码
for (var i = 0; i < 3; i++) {
	setTimeout((function(i) {
		return function() {
			console.log(i);
		};
	})(i), 0);
	console.log(i);
}

只要这样就可以了
展开代码
for (var i = 0; i < 3; i++) {
	setTimeout((function(i) {
		 
			console.log(i);
	 
	})(i), 0);
	console.log(i);
}
不用再包一个块了吧
举报 支持 (0) 回复 (2)
bluskytee
bluskytee2016-06-16 11:40:5313F
说干就干,老姚开始吧 //@老姚:哦是吗,我在想是否要开个系列,专门讲讲面试题的。以面试题来学js。估计能有不少人喜欢。 //@姚敏娇:看过很多人讲这道面试题了。你讲的最清楚明白了emoticon
举报 支持 (0) 回复 (0)
hugeannex
hugeannex2016-06-16 11:28:3112F
也对 //@老姚:哈哈,偶尔刷刷。突然发现除了效果之类的文章之外,基础知识点的文章也容易获得点赞。其他的文章没多少人看。。。。 //@hugeannexemoticon老姚刷基础的闭包题,太失水准了。。。
举报 支持 (0) 回复 (0)
qq807081817
qq8070818172016-06-16 11:23:0911F
在这之前,题目里的循环长度如果是3,那我可以答出来,但如果改成很大的数字我可能答不出来,但现在我明白了,3Q
举报 支持 (0) 回复 (0)
老姚
老姚2016-06-16 10:51:4910F
thank you! //@谜~:真的是通俗易懂啊~大写的赞
举报 支持 (0) 回复 (0)
老姚
老姚2016-06-16 10:50:309F
哈哈,偶尔刷刷。突然发现除了效果之类的文章之外,基础知识点的文章也容易获得点赞。其他的文章没多少人看。。。。 //@hugeannexemoticon老姚刷基础的闭包题,太失水准了。。。
举报 支持 (0) 回复 (1)
谜~
谜~2016-06-16 10:47:118F
真的是通俗易懂啊~大写的赞
举报 支持 (0) 回复 (1)
hugeannex
hugeannex2016-06-16 10:45:437F
emoticon老姚刷基础的闭包题,太失水准了。。。
举报 支持 (0) 回复 (1)
老姚
老姚2016-06-16 10:12:516F
此题是一个同事问我的。我才想起来说这个事情的。得需要专门去做搜集的工作。网上倒是有不少那样的帖子,哈哈。没事挑两道讲讲。 //@姚敏娇:嗯。是的。可以的。刚好最近在看js的书。多给些面试题,看的会更透彻emoticon //@老姚:哦是吗,我在想是否要开个系列,专门讲讲面试题的。以面试题来学js。估计能有不少人喜欢。 //@姚敏娇:看过很多人讲这道面试题了。你讲的最清楚明白了emoticon
举报 支持 (2) 回复 (1)
姚敏娇
姚敏娇2016-06-16 10:07:275F
嗯。是的。可以的。刚好最近在看js的书。多给些面试题,看的会更透彻emoticon //@老姚:哦是吗,我在想是否要开个系列,专门讲讲面试题的。以面试题来学js。估计能有不少人喜欢。 //@姚敏娇:看过很多人讲这道面试题了。你讲的最清楚明白了emoticon
举报 支持 (0) 回复 (1)
老姚
老姚2016-06-16 10:00:274F
哦是吗,我在想是否要开个系列,专门讲讲面试题的。以面试题来学js。估计能有不少人喜欢。 //@姚敏娇:看过很多人讲这道面试题了。你讲的最清楚明白了emoticon
举报 支持 (0) 回复 (2)
老姚 老姚 作者

与自己为敌,与自己为友,一边深挖思想,一边埋葬自己。

作者最新