最近有一道颇有意思的前端面试题前端
for (var i = 1; i <= 5; i++) { setTimeout( function timer() { console.log(i); }, i * 1000 ); } //要求改动上述代码,使其依次输出一、二、三、四、5
这道题涉及到的知识点有函数的执行顺序
、闭包
、块级做用域
等。面试
首先,咱们能够来看一下这道题本来会输出的结果是什么数据结构
如图,执行这段代码以后,当即输出了一个数字89,而后每过1秒钟输出了一个数字6闭包
在咱们学习setTimeout
的时候就知道,setTimeout
有两个参数,第一个参数是回调函数,第二个参数是毫秒数,表示要执行回调函数所要延迟的时间。函数
但咱们还须要知道的是,setTimeout
会返回一个Id
,即这个定时器的Id
,在上面的代码中其实已经建立了5个定时器,可是默认只返回了最后的一个Id
,咱们能够经过将Id
赋值给一个变量,来看到这个过程。性能
因为方法里面没有return
任何东西出来,因此返回值为undefined
。学习
经过这个定时器的Id,可使用clearTimeout(id)
方法清除掉这个定时器,这里就再也不赘述了。spa
接下来就该讨论为何会输出5个数字6,而不是一、二、三、四、5了。先来看一个例子3d
当setTimeout()
的毫秒数设置为0的时候,仍然是先执行完函数调用栈中的代码,而后当即调用定时器。这是由于,咱们的定时器都被放在了一个被称为队列的数据结构中,等待上下文的可执行代码运行完毕后,才开始运行定时器,也就是定时器才刚开始计时。code
例子以下:
因此在定时器的方法执行的时候,变量i已经变成了6,因此输出的所有是6。由于5个定时器所打印出来的是同一个i变量,因此想要实现输出不一样的数字,就须要把每一个定时器所访问的变量独立起来,这就用到了JavaScript的闭包。
咱们都知道,JavaScript的变量是从外往内开放的,函数内部能够访问到外部的变量,可是外部没法访问到内部。
内部的func()
方法就造成了一个闭包,闭包用途不少,能够很好地区分开各个做用域,避免变量的混淆,可是滥用闭包也会致使性能问题。
想要使用闭包完成文章开始的面试题,能够经过如下的方式
for (var i = 1; i <= 5; i++) { (function(i){ setTimeout( function timer() { console.log(i); }, i * 1000 ); })(i); } //上面的代码是标准答案,将变量i做为参数传到闭包中 //咱们也能够经过做用域在函数内部把变量隔离起来 //其实,在闭包内部访问i的时候,i就是一个常量 for (var i = 1; i <= 5; i++) { (function(){ var s = i;//把i赋值给另一个变量 setTimeout( function timer() { console.log(s); }, s * 1000 ); })(); } //固然,也能够把setTimeout的回调函数作成一个闭包,一样能获得正确的结果。
使用闭包能够获得正确的结果,缘由就是改变了i的做用域,那若是咱们把循环中的每一个setTimeout都独立成一个做用域是否是也能实现一样的结果呢?
咱们都知道,在JavaScript中,每一个函数是一个独立的做用域,可是“{}”是不能造成独立做用域的。
在ES6中提出了一个新的关键字let
,就能够声明一个仅对当前“{}”
内部有做用的变量。
如图,一样能够实现。
这个面试题考察了setTimeout方法的原理,间接涉及了函数调用栈。利用闭包或块级做用域能够实现想要的效果。