JS函数的执行时机
JS函数的执行时机
问题引入(五个5问题):
1 | |
为什么控制台打印出了五个5,而不是0、1、2、3、4,why?
深入理解JS函数的执行时机
输出以上结果的前提是:
① var声明使得变量提升。使用var命令声明,同时给未声明的变量赋值, 则执行赋值后, 该变量会被隐式地创建为全局变量(成为全局对象的属性)。
因此,此代码中,for循环的i被提升为全局变量(for循环全程只有一个变量i)。
以上代码等价于:
1 | |
感觉这个代码比较啰嗦,以下均使用for循环内部的var命令来声明全局的i。
② JavaScript是一门单线程语言。每一个任务只能按顺序依次进行。
上述代码中的window.setTimeout设置了一个定时器,该定时器在一段时期后(可以理解为之前的任务完成后)才执行一个函数或指定的一段代码。
因此在执行setTimeout(()=>{console.log(i);},0);时,for循环已经结束,此时i的值为5.
所以此时每次执行的console.log(i);结果都为5。
通俗的理解,在当前任务还没有完成时,就安排了下一个任务,而下一个任务的进行需要上一个任务得到的结果(这里前后两个任务分别是for循环和计时器)。那么JS会先把当前任务完成,然后再开始完成下一个任务。此时,下一个任务用到的数据应该是当前任务(for循环)完成之后(have done)的数据,而不是正在完成(v.ing)的数据。
异步任务
通常来说,程序都是顺序执行,同一时刻只会发生一件事。如果一个函数依赖于另一个函数的结果,它只能等待那个函数结束才能继续执行,从用户的角度来说,整个程序才算运行完毕。
使用异步函数可以设置函数队列的执行顺序。JS中最基本的异步函数有:
灵活使用(TL;DR)
如何打出想要的0-4?
理解关键点:只要 ①在局部作用域中创造它的局部变量i(方法一) 或者 ②在局部作用域中使用全局变量i(方法二和方法三)就能够达到目的。
方法一(for循环内部使用let声明):
可以将var声明改为let声明。它能声明一个块级作用域的本地变量。
1 | |
let肯定不能放在for循环外面,上文已经解释。
此时,尽管计时器开始执行时,for循环已经结束,但每一次的i都是该次所在的块中的i,所以打印的是当时的i值。
注意,此时for循环将产生六个局部变量i,分别是0,1,2,3,4,5。
同样的道理,也可以在for循环中单独声明一个变量j(使用let或const)。此代码比较无聊,但对于理解方法一有帮助:
1 | |
方法二(立即执行函数):
1 | |
使用立即执行函数可以创造一个局部作用域,进而达到想要的效果:
1 | |
方法三(setTimeout函数传参):
window.setTimeout的第三个参数用于设定function的参数
1 | |
此时,每一次进入for循环,第三个参数都告诉计时器:不用等for循环完成,你现在就能拿到i值给你的function执行了。所以每一次都能直接打印出当次的i值。
版权声明:本文作者为「Andy8421」.本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!