定时器引发的思考

定时器真的是定时执行的嘛?
会有误差,不是
定时器回调函数是在微任务(分线程)执行的嘛?
不是,在宏任务(主线程)中执行,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.thenPromise.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只有一个主线程主线程执行完执行栈的任务后去检查异步的任务队列,如果异步事件触发,则将其加到主线程的执行栈