公司前端招人的时候,负责招聘伙伴想了这样一道题,但是自己解释的不是很清楚,也是难为了来参加面试的人.题目如下:
function test() {
for(var i=0; i<5; i++){
setTimeout(function(){
console.log(i);
},i*1000)
}
}
test();
大概在js方面有过了解的人,大都知道这样一道充满迷惑性的题的答案是间歇的输出了5个5.以此为突破口来窥探一下js的冰山一角.
首先,这道题有两个可能只是偶尔接触js(比如我)不会注意到的知识点:
1.for循环中,循环语句是循环体内部的父作用域.也就是说,循环语句以及循环体内部的作用域是不一样的.经常见到在循环体内部使用循环语句的变量,其实相当于当函数内部找不到变量时,会向上查找.
2.js单线程的任务执行顺序.setTimeout属于注册事件,绑定的事件是当前事件队列(同步事件)执行完毕后再执行.
根据第2个知识点来说.这道题的结果就很好理解了,源代码相当于:
var i = 0;
setTimeout(function() {
console.log(i);
}, 1000);
i++;
setTimeout(function() {
console.log(i);
}, 2000);
i++;
setTimeout(function() {
console.log(i);
}, 3000);
i++;
setTimeout(function() {
console.log(i);
}, 4000);
i++;
setTimeout(function() {
console.log(i);
}, 5000);
i++;
当同步任务完成后,也就是i自加后,setTimeout事件开始被触发,1000ms后打印出了i.这里要注意一下setTimeout这个函数的执行顺序,比如当100个延时1000ms的函数同时执行,那么这100个异步函数将同时执行.
看下一道题,不从异步函数来思考.
var test=function(){
var arr={};
for(var i =1;i<3;i++){
arr[i]=function(){
return i*i;
}
}
return arr;
}
var a=test();
a[1]();//?
a[2]();//?
请思考2分钟,再点开答案(当然如果你很确定答案了就直接点开吧)
var test=function(){View Code
var arr={};
for(var i =1;i<3;i++){
arr[i]=function(){
return i*i;
}
}
return arr;
}
var a=test();
a[1]();//9
a[2]();//9
如果你是一名像我一样很少把函数体直接赋值给变量的程序员,又或者你是一位c系列的程序员,你可能会相当困惑这个答案.产生这个困惑的原因是
1.我以为函数中变量在内存中的存储方式是以常量的形式存储的(这句话不严谨,会在未来我醒悟的某一天来修改).
2.产生1这种想法的原因是因为我的常识中是存在块级作用域的,而事实是这里并不存在块级作用域.
3.暂时不表
简单且不负责任的描述一下作用域:当你声明一个变量后,你能在花括号"{}"中找到这个变量的所有区域,则是这个变量的作用域.
最常见的作用域就是函数体,特别是像我这种php程序员可能接触到的唯一的作用域就是函数体而产生的作用域.
接下来以我的愚见来解释一下这道题
当程序执行到
var a=test();时
a被声明成一个数组函数对象(姑且这么叫吧,是由2个函数组成的数组对象)
并且,函数中的i并不是常量,而是变量i.(这里可以抛出一个问题,当函数中的变量被赋值后,内存中存储的函数中的变量是以变量还是常量的形式存储)
那么,这两个函数体相同且均为
function(){
return i*i;
}
到这里,你可能会顿悟为什么a[1],a[2]输出的是同一个值了.如果还是对输出同一个值有疑问,请回到上一个并且再多探索探索.
接下来,就是这个i是多少的问题了.再看一下这道对于初学者来说邪恶的函数体:
var test=function(){
var arr={};
for(var i =1;i<3;i++){
arr[i]=function(){
return i*i;
}
}
return arr;
}
i是在for语句的循环条件中声明的,当我们执行a[1]或者a[2]时,js将从声明js的作用域中去寻找i.
而i所在的作用域,则是整个test函数.
而此时i的值,是多少呢?不清楚的可以自己做个试验
for(var i=1;i<3;i++){
}
console.log(i); //3
如果对此感到疑惑的,说明你可能是一位勤恳扎实的c系列程序员,但是在js中花括号并不会单独生成一个作用域(块级作用域).
此事放眼函数体,唯一的作用域唯一的i,此时的值是3.
所以最后数组函数对象a的每一个函数体都相当于
function(){
return 3*3;
}
这也就是a[1]()==a[2]()==9的原因.
如果看到这里,你还存在很多疑惑,那就很对了.
因为写到这里的我依旧有很多不敢触及的分支,只敢畏畏缩缩的在我所熟悉的路上探索.这大概就是程序语言的魅力,一个细微的变量将影响到全局的走势.我会不定时更新这篇文章.