文章目录
老规矩 先放链接致敬作者
同步任务和异步任务详解
浏览器的线程有哪些
宏任务和微任务到底是什么?
js中的同步和异步的个人理解
浏览器线程
浏览器的渲染进程是多线程的。js是阻塞单线程的
GUI线程
GUI线程就是渲染页面的,他解析HTML和CSS,然后将他们构建成DOM树和渲染树就是这个线程负责的。
JS引擎线程
这个线程就是负责执行JS的主线程,前面说的"JS是单线程的"就是指的这个线程。大名鼎鼎的Chrome V8引擎就是在这个线程运行的。需要注意的是,这个线程跟GUI线程是互斥的。互斥的原因是JS也可以操作DOM,如果JS线程和GUI线程同时操作DOM,结果就混乱了,不知道到底渲染哪个结果。这带来的后果就是如果JS长时间运行,GUI线程就不能执行,整个页面就感觉卡死了。所以我们最开始例子的while(true)这样长时间的同步代码在真正开发时是绝对不允许的。
定时器线程
前面异步例子的setTimeout其实就运行在这里,他跟JS主线程根本不在同一个地方,所以“单线程的JS”能够实现异步。JS的定时器方法还有setInterval,也是在这个线程。
事件触发线程
定时器线程其实只是一个计时的作用,他并不会真正执行时间到了的回调,真正执行这个回调的还是JS主线程。所以当时间到了定时器线程会将这个回调事件给到事件触发线程,然后事件触发线程将它加到任务队列里面去。最终JS主线程从任务队列取出这个回调执行。事件触发线程不仅会将定时器事件放入任务队列,其他满足条件的事件也是由他负责放进任务队列。
异步HTTP请求线程
这个线程负责处理异步的ajax请求,当请求完成后,他也会通知事件触发线程,然后事件触发线程将这个事件放入任务队列给主线程执行。
所以JS异步的实现靠的就是浏览器的多线程,当他遇到异步API时,就将这个任务交给对应的线程,当这个异步API满足回调条件时,对应的线程又通过事件触发线程将这个事件放入任务队列,然后主线程从任务队列取出事件继续执行。
宏任务(macrotask) 微任务(microtask)
ES6 规范中,microtask 称为 jobs,macrotask 称为 task
宏任务是由宿主发起的,而微任务由JavaScript自身发起。
在ES3以及以前的版本中,JavaScript本身没有发起异步请求的能力,也就没有微任务的存在。在ES5之后,JavaScript引入了Promise,这样,不需要浏览器,JavaScript引擎自身也能够发起异步任务了。
同步异步
其实同步和异步,无论如何,做事情的时候都是只有一条流水线(单线程),同步和异步的差别就在于这条流水线上各个流程的执行顺序不同。最基础的异步是setTimeout和setInterval函数,很常见,但是很少人有人知道其实这就是异步,因为它们可以控制js的执行顺序。我们也可以简单地理解为:。
具体来说,异步运行机制如下:
(1)所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
(2)主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
(3)一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
(4)主线程不断重复上面的第三步。
同步异步和宏任务微任务没有必然联系,同步异步的任务重点是要放入浏览器的不同线程运行、
宏任务微任务主要关注于运行顺序,setTimeout虽然是异步,但是是宏任务,
Promise是宏任务(同步执行),但Promise 的回调函数属于异步任务,会在同步任务之后执行(比如说then、catch、finally)。 Promise 的回调函数不是正常的异步任务,而是微任务(microtask).
回调
回调函数的目的 传入一个函数,在一定时机调用
有promise之前 获取函数中异步操作的结果,则必须通过回调函数获取
生命周期函数其实就是是回调函数
Promise
Promise说明:Promise是一个容器,Promise是立即执行的,但是往往里面放异步任务.
pending 初始状态,既不是成功,也不是失败
fulfilled 操作成功完成
rejected 操作失败
处于 pending 状态的 Promise 对象可能会变为fulfilled 状态并传递一个值给相应的状态处理方法,也可能变为失败状态(rejected)并传递失败信息。
当其中任一种情况出现时,Promise 对象的 then 方法绑定的处理方法(handlers )就会被调用(then方法包含两个参数:onfulfilled 和 onrejected,它们都是 Function 类型。当Promise状态为fulfilled时,调用 then 的 onfulfilled 方法,当Promise状态为rejected时,调用 then 的 onrejected 方法, 所以在异步操作的完成和绑定处理方法之间不存在竞争)
setTimeout setInterval
ES6 async和await
async和await是如何处理异步任务的?简单说,async是通过Promise包装异步任务
比如有如下代码:
async function async1() {// 在执行到await之后会让线程给async2
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2 end')
}
async1()
改为ES5的写法:
new Promise((resolve, reject) => {
// console.log('async2 end')
async2()
...
}).then(() => {
// 执行async1()函数await之后的语句
console.log('async1 end')
})
例题
console.log("script start");//1.1
async function async1() {
await async2();
console.log("async1 end");//2.1
}
async function async2() {
console.log("async2 end");//1.2
}
async1();
setTimeout(function () {
console.log("setTimeout");//3
}, 0);
new Promise((resolve) => {// promose本身同步执行 回调是微任务
console.log("Promise");//1.3
resolve();
})
.then(function () {
console.log("promise1");//2.2
})
.then(function () {
console.log("promise2");//2.3
});
console.log("script end");//1.4
解答:
宏任务先运行,没有宏任务(js代码块),哪来的微任务(这题里的微任务都是promise的then,promise是立即执行的 在第一轮宏任务里)。
第一轮宏任务打印:script start 、async2 end、 Promise 、script end
产生微任务后 微任务优先级高于宏任务 所以:
第一轮微任务打印:async1 end 、promise1、promise2
(此时微任务队列清空,且存在其他宏任务,进入下一轮事件循环)
第二轮宏任务: setTimeout
注:
Event Loop中,每一次循环称为tick,每一次tick的任务如下:
执行栈选择最先进入队列的宏任务(一般都是script),执行其同步代码直至结束;
检查是否存在微任务,有则会执行至微任务队列为空;
如果宿主为浏览器,可能会渲染页面;
开始下一轮tick,执行宏任务中的异步代码(setTimeout等回调)。