闭包的经典面试题之一
首先,上题目:
问打印结果是什么?
结果为:
要做这道题,首先要理解以下概念:
1.js的执行机制:单线程执行,也就是所谓的stack(栈).
2.作用域链:可以看作一个集合,即当前作用域及父级的作用域的集合。
可以看下图一个函数的执行过程:
首先进行全局执行上下文,执行完毕后,根据上下文存在的books函数,就去执行books的执行上下文,再然后是匿名函数的上下文
在这里还需要再了解一下执行机制,由于js是单线程执行机制,在执行上下文的时候,若是出现了一个方法中的事情很多,每次都需要等待该方法执行完毕再执行下文,那就会出现很大的问题,所以这个时候就出现了任务队列机制。
例如setTimeout的执行过程,js执行不可能等待setTimeout里面的代码执行完毕再接着执行下文,毕竟它有个等待时间,假设这个时间很长呢?会造成下面的代码全部停止执行,所以这个任务队列执行机制是很有意义的。
接着面试题来说,只要用了setTimeout方法,即使里面的毫秒数为0,里面的函数不会立即放到执行栈中去,而是由浏览器先进行处理,得到的结果放到任务队列中,等待执行栈执行完毕之后,再根据setTimeout的时间间隔放到执行栈中,也就是说,这里的任务队列有浏览器先处理的5个i++,因为并未实际执行(因为全局执行上下文还没有完成,没有i值),所以这里没有计算结果,即相当于‘还在加工中,未完成产品’
根据if循环的执行顺序(要注意一下,忘记的可以去补),最后的打印i,来自for循环完成后的i=5;
然后,执行上下文完毕了,任务队列放到执行栈中,此时的已经是5,所以会间隔4000ms后,一下子打印出5,6,7,8,9
但是这里也要格外注意两点:
1.这个console.log(i++),如i=5,所以会从5开始打印,而不是6;而有五次任务,所以依次打印出以上结果。
2.这个setTimeout方法虽说间隔4000ms,但是是在执行上下文完毕之后的间隔,全局执行上下文也需要时间,所以是很大可能是大于4000ms的;而且在这里是4000ms后一下子打印出来,而不是4000ms打印一个数字。
那么考核重点来了,如何改代码才能打印出0,1,2,3,4呢?
答案:
为什么这样改呢,首先需要看懂代码。
在这里再普及以下匿名函数和立即执行函数的区别:
匿名函数:即没有名字的函数,匿名函数不能直接使用,只有在属性、方法、对象等有依赖时才可以使用
立即执行函数(也叫自执行函数):
首先什么叫自执行:我们定义一个函数,并且立即执行它即为自执行。
由于外部访问不到其内部的变量,因此执行完之后立即被释放,不会玷污到全局对象。自执行,即创建和调用为一体。
常见的立即执行函数方式,(推荐第一种写法,阅读性更强,但是第二种用用的人也比较多)
首先,全局执行上下文,从上到下,执行过程是这样的:
此时第一次循环,i=0,但是这里由于有一个立即执行函数,所以传入一个参数i=0,然后x=i=0,此时遇到setTimeout,所以交给浏览器处理,此时x=0,所以任务队列中有数值0。
上面的函数执行栈执行完后从栈中弹出,回到全局执行上下文。
然后进行第二次循环,此时的i=1,通过立即执行函数传入x=i=1,然后setTimeout,交给浏览器处理…和第一次一样,然后任务队列中加入数值1.
然后再弹出,再立即执行函数…以此循环,
然后这里循环执行完毕了,i=5结束循环,然后到下面的console.log(i),毫无疑问结果是5;
然后到这里全局执行上下文的代码已经没有了,所以会执行任务队列里面的代码,也就是将数值01234输出来,
后续:接着看下面几个循环的执行区别
图一:
图一的执行过程:全局执行上下文开始,但是遇到setTimeout所以放到任务队列中,所以任务队列中是未开始计算的5个i++。
然后循环完毕后i=0,任务队列里面的代码开始执行到执行栈,即i++开始计算,所以打印出0,1,2,3,4
图二:
和图一的区别是:任务队列里是5个i。
文中引用了多处博客和视频教程,且写的有任何错误欢迎指正!