定时器引发的思考
定时器真的是定时执行的嘛?
会有误差,不是
定时器回调函数是在微任务(分线程)执行的嘛?
不是,在宏任务(主线程)中执行,JS是单线程的
定时器是如何实现的?
引出=事件循环
JavaScript单线程,若在JavaScript操作DOM时,多线程可能会出现误操作,例如:同时更新或者删除一个DOM元素
多线程会导致很多复杂同步问题。
事件循环
事件循环的步骤:
(1)所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
(2)主线程之外,还存在一个”任务队列”(task queue)。只要异步任务有了运行结果,就在”任务队列”之中放置一个事件。
(3)一旦”执行栈”中的所有同步任务执行完毕,系统就会读取”任务队列“,看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
(4)主线程不断重复上面的第三步。
- 同步和异步任务分别进入不同的执行”场所”,同步的进入主线程,异步的进入Event Table并注册函数
- 当指定的事情完成时,Event Table会将这个函数移入Event Queue。
- 主线程内的任务执行完毕为空,会去Event Queue读取对应的函数,进入主线程执行。
- 上述过程会不断重复,也就是常说的Event Loop(事件循环)。
任务队列
- 分类
宏任务:包含绑定DOM事件监听,设置定时器,发送Ajax请求等代码
微任务:Promise.then/ catch/ finally/process.nextTick(Node)
理解:
- 每个线程中,事件循环是唯一的,但是任务队列可以有多个;
- 任务队列又分为
task
(宏任务)和micro-task
(微任务); - 微任务和宏任务皆为异步任务,它们都属于一个队列;
- 宏任务大概包括:
script
(整体代码),setTimeout
,setInterval
,setImmediate,I/O,UI rendering; - 微任务大概包括:process.nextTick,
Promise.then
,Promise.catch
,Object.observe(已废弃),MutationObserver(html5新特性) setTimeout
/Promise
等我们称之为任务源。而进入任务队列的是他们指定的具体执行任务。来自不同任务源的任务会进入到不同的任务队列- 事件循环的顺序,决定了JavaScript代码的执行顺序。它从script(整体代码)开始第一次循环。之后全局上下文进入函数调用栈。直到调用栈清空(只剩全局),然后执行所有的微任务。当所有可执行的微任务执行完毕之后。循环再次从宏任务开始,找到其中一个任务队列执行完毕,然后再执行所有的微任务,这样一直循环下去,直到任务队列执行完毕。又开始新的调用执行栈。。。
先执行完同步代码(执行栈清空),每次准备取出第一个宏任务执行前,都要将所有的微任务依次执行完毕,任务队列里面的两种任务执行完毕后,又开新一轮执行同步任务(执行栈清空)。 ```javascript /**
- example one *
一开始,只有script(整体代码),分发到task队列
首先执行主线程这个宏任务,从上到下执行,遇到console.log(console1); 打印console1出来
setTimeout函数:宏任务源,分发到对应的task队列
- Promise实例:构造函数中的第一个参数是在new时执行的,不会进入其他队列
- .then被分发到micro-task的Promise队列中 script任务继续往下执行,输出global1,然后,全局任务执行完毕
- 第一个宏任务script执行完毕之后,就开始执行所有的可执行的微任务
- 这时候,微任务中,只有Promise队列中的任务then1,执行输出 *
- 当所有的微任务执行完毕后,第一轮循环结束,第二轮循环开始,第二轮循环仍然从宏任务macro-task开始 *
- 宏任务中,只有在setTimeout队列中还有一个timeout1的任务等待执行。因此就直接执行即可 *
- 结果:
- console1 => promise1 => promise2 => global1 => then1 => timeout1 */
console.log(‘console1’);
setTimeout(function() { console.log(‘timeout1’); });
new Promise(function(resolve) { console.log(‘promise1’); for(let i = 0; i < 1000; i++) { i === 99 && resolve(); } console.log(‘promise2’); }).then(function() { console.log(‘then1’); });
console.log(‘global1’); ```
总结:JS只有一个主线程,主线程执行完执行栈的任务后去检查异步的任务队列,如果异步事件触发,则将其加到主线程的执行栈。