浅析Js运行机制(一)
一.写在前面
每月一篇技术文章,加油加油。
在开发过程中,明明代码乍一看没什么毛病,但是一运行结果却出乎我们的意料。每当这时候,我的第一个念头就是,是不是执行顺序错了?js是一门单线程的语言。虽然我们有很多方式可以模拟出多线程,但是归根结底,我们使用的js仍是单线程。
二.js的执行机制。
由于JavaScript是单线程,当我们打开网页时候我们首先进入的主线程中有大量的同步任务(link标签引入,dom渲染)。这些同步任务的执行需要等到他前面的任务全部执行完成之后才会开始执行,所以在处理耗时久的任务时(音频,视频之类),需要使用异步加载的方式来先加载网页运行的基本的代码和文件,优先保证不影响页面的基本浏览使用。
接下来用一张图来说明:
js运行机制导图的内容用文字表述是:
1,当函数进入执行栈之后,会先判断执行代码的任务类型。
2,判断完类型之后,如果是同步任务则进入主线程执行,如果是异步任务则在注册完回调函数之后进入事件队列。(js监听异步的方式就是通过异步任务注册回调函数)
3,当主线程的同步任务执行完成之后,js会从事件队列内取出之前注册好的任务,进入主线程执行。
4,当事件队列内没有空任务时,现在进入执行栈中的代码块弹出执行栈,开始执行下一段代码。
下面将用一段代码来论述上面的结论。
该函数的运行结果是
// 你好
//2
//1
可见,首先执行的是console.log(‘你好’)。然后是time1,和time2依次回调函数注册,进入事件队列。因为time2的返回时间较短,所以先返回2。在这里其实只是模拟了最简单的js运行机制,让我们稍微改变一下代码。
通过上面代码可得出结果
// 2
// 1
// foo2
// foo1
这里的代码在执行时,先执行的是foo2,此时foo2进入执行栈,开始调用foo2内的代码块,当foo2执行完毕之时,弹出执行栈,引擎继续往下走轮到foo1,再让foo1进入执行栈,当同步代码全部执行完时,开始运行异步代码。
三.有关定时器的注意事项
1.定时器定时并非"准时".
我们所设置的定时器的时间并非准时,因为定时器开始计时的时间并非是js运行到计时器那一行所开始计时,而是等到所有同步代码执行完成之后,执行当前定时器异步任务时才开始计时。
如下列代码所示
js运行到setTimeout时,只是在注册回调函数之后放入任务队列,此时‘console.log(‘你好’)’;并没有执行。而是等到for循环结束之后,开始执行setTimeout。所以定时器完成的时间是 同步任务执行完成时间 + 定时器设置时间。
由此我们也可以推出这种写法的意思。
如下列代码所示
此时的定时器时间设置为0。这段代码的意思就是当主线程闲置时立即执行 setTimeout。
2.setInterval与setTimeout的区别。
setInterval是循环定时器,那么他在运行时会准时吗?
setInterval在的第一次执行代码内容所需的时间和上文所说的setTimeout一样,是同步任务执行完成时间 + 定时器设置时间。但是之后的每一次都是准时的,这是因为此时主线程上同步任务已经执行完,而setInterval在一直往事件队列内新增事件,所以之后的每一次都是准时的。
这次先说这么多,下一节说说js运行过程中宏任务与微任务的区别。