任务管理
JavaScript 语言的一大特点就是单线程,也就是说同一个时间只能处理一个任务。为了协调事件、用户交互、脚本、UI 渲染和网络处理等行为,防止主线程的不阻塞,(事件循环)Event Loop 的方案应用而生。
JavaScript 处理任务是在等待任务、执行任务 、休眠等待新任务中不断循环中,也称这种机制为事件循环。
- 主线程中的任务执行完后,才执行任务队列中的任务
- 有新的任务到来时会将其放入队列,采取先进先出的执行策略执行队列中的任务
- 比如多个
setTimeout
同时到时间了,就要依次执行
任务包括 script(整体代码)、 setTimeout、setInterval、DOM 渲染、DOM 事件、Promise、XMLHTTPREQUEST 等
原理分析
宏任务与微任务
console.log(1) setTimeout(function(){ console.log(5); },0) Promise.resolve().then(function(){ console.log(3); }).then(function(){ console.log(4); }) console.log(2); // 1 2 3 4 5 复制代码
执行过程
- 先执行
console.log(1)
输出1 - 执行setTimeout宏任务 将
function(){console.log(5);},0
放入宏任务队列。 - 执行Promise微任务将
console.log(3);
放入微任务队列 - 执行同步代码
console.log(2);
输出2 - 主线程任务完成,微任务开始传入主进程
- 通过事件循环遍历微任务队列,将刚才放入的 Promise.then 微任务读取到主线程执行,然后输出 3
- 之后又执行 promse.then 产生新的微任务,并放入微任务队列
- 主线程任务执行完毕
- 再次事件循环遍历微任务队列,读取到 promise2 微任务放入主线程执行,然后输出4
- 主线程任务执行完毕
- 此时微任务队列已经无任务,然后从宏任务队列中读取到 setTimeout 任务并加入主线程,然后输出 5
脚本加载
引擎在执行任务时不会进行 DOM 渲染,所以如果把script 定义在前面,要先执行完任务后再渲染 DOM,建议将script 放在 BODY 结束标签前。
定时器
定时器会放入异步任务队列,也需要等待同步任务执行完成后执行。注意是宏任务消息队列
HTML 标准规定最小时间不能低于 4 毫秒,有些异步操作如 DOM 操作最低是 16 毫秒,总之把时间设置大些对性能更好。
定时器会在等待时间完成后将任务放入宏任务队列,只有当主进程的任务执行完毕之后才会将这个任务放入主进程执行。
setTimeout(()=>{ console.log(2); },1000) function fn() { let i = 0; console.log(1); while(i < 99999999) { i++; } } fn(); // 1 2 复制代码
这里主线程先执行setTimeout,等待1s后将console.log(2);放入宏任务队列,等待期间同时执行同步代码 fn函数,输出1 。在等待1s后将console.log(2);放入宏任务队列,这时主进程的同步代码还未完成,宏任console.log(2);继续待在宏任务队列。等主进程同步代码执行完成之后,将console.log(2);从宏任务队列中弹出到主进程执行。输出2。
这是对定时器的说明,其他的异步操作如事件、XMLHTTPREQUEST 等逻辑是一样的
微任务
微任务一般由用户代码产生,微任务较宏任务执行优先级更高,Promise.then 是典型的微任务,实例化Promise 时执行器代码是同步的,然而实例函数 then 注册的回调函数是异步微任务的。
任务的执行顺序是同步任务、微任务、宏任务所以下面执行结果是 1、2、3、4
setTimeout(() => { console.log(4); }, 0) new Promise((resolve) => { console.log(1) resolve() }).then((_) => console.log(3)) console.log(2); // 1 2 3 4 复制代码
执行过程
- 执行脚本
- 执行setTimeout异步函数,将
console.log(4);
放入宏任务队列 - 执行Promise函数,执行执行器函数代码(是同步的)直接输出1
- 执行Promise.then将
console.log(3))
放入微任务 - 执行同步代码
console.log(2);
输出2 - 主进程任务完成
- 将微任务
console.log(3))
放入主进程执行输出3 - 主进程任务完成
- 将宏任务
console.log(4);
放入主进程执行输出4