We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
JavaScript作为单线程脚本语言,同一时刻只能做一件事情,所有任务都需要排队,前一个任务结束,才会执行后一个任务。想一想,如果中间有一个耗时长的任务(异步任务),例如:ajax从服务器获取数据,难道我们要等待数据返回了才继续完成后面的任务吗?这显然不合理。
ajax
所以,事件循环就是帮我们解决这个问题的,它规定了如何来处理这些异步任务(ajax、setTimeout等)。
setTimeout
事件循环的工作方式大概是这样:
进入程序,主线程从任务队列中取读第一个任务(script包裹的代码,我们可以认为它本身就是一个全局函数)加入执行栈(stack)执行,如果遇到异步任务并不会立即执行其中的代码,而是交给WebAPIs去处理,然后继续执行后面的代码,WebAPIs会在适当的时机将这些任务加入到任务队列(callback queue)中,执行栈中的代码执行完毕,就会取读任务队列中的任务,并加入执行栈执行,如此往复。
script
stack
WebAPIs
callback queue
引用一张经典的图片:
以上图片中,异步任务有结果了就会将它所定义的回调函数加入到任务队列(callback queue)中,执行栈清空后就会取读任务队列。
看看以下代码:
console.log(1) setTimeout(() => { console.log(2) }, 1000) console.log(3) // 1 // 3 // 2(1s后)
以上代码中,当JavaScript引擎遇到setTimeout,会把它的回调函数和毫秒值交给WebAPIs,主线程继续执行后面的代码,所以先打印了1和3,1s之后WebAPIs中的回调函数会被加入到任务队列中,由于主线程现在是空闲状态,所以会立即取读任务队列并执行打印出2。
1
3
看看另一个例子:
console.log(1) setTimeout(() => { console.log(2) }, 0) Promise.resolve().then( res => { console.log(3) }) console.log(4) // 1 4 3 2
以上代码中,setTimeout和Promise都是异步任务,但Promise先执行了,而不是setTimeout?
Promise
原来在任务队列中还分为了:
微任务队列(Promise.then) 以及宏任务队列(setTimeout)
Promise.then
微任务属于本次任务的附属任务,本次任务结束后会取读微任务队里的所有任务并依次执行,然后开始下一个宏任务,也就是宏任务队列中的第一个任务,结束后继续查看微任务队列,能看出来事件循环大概是这样进行的:
宏任务 -> 微任务(全部) -> 宏任务 -> 微任务(全部) -> ···
所以,在上面的例子中,script本身就是一个宏任务,执行完毕就会取读微任务队列(Promise.then),然后是下一个宏任务(setTimeout)。换句话说,微任务是被添加到了本次任务的末尾,而宏任务添加到了下次事件循环的开头,本次任务产生的微任务永远在宏任务之前执行。
常见的宏任务(macrotask)和微任务(microtask):
宏任务:
微任务:
const div = document.querySelector(".box") div.style.backgroundColor = "red" Promise.resolve().then( res => { div.style.backgroundColor = "blue" })
以上代码中,通过观察发现,元素.box的背景色自始至终都是蓝色
.box
const div = document.querySelector(".box") div.style.backgroundColor = "red" setTimeout(() => { div.style.backgroundColor = "blue" }, 0)
以上代码中,元素.box的背景色会先变成红色,然后立马变成蓝色
两段代码反映一个问题,就是关于UI重新渲染的时机,是在一次事件循环的末尾进行的,也就是微任务之后会进行UI的重新渲染。
所以完整的事件循环应该是这样:
宏任务 -> 微任务(全部) -> UI渲染 -> 宏任务 -> 微任务(全部) -> UI渲染 ···
The text was updated successfully, but these errors were encountered:
No branches or pull requests
事件循环(event loop)是什么?
JavaScript作为单线程脚本语言,同一时刻只能做一件事情,所有任务都需要排队,前一个任务结束,才会执行后一个任务。想一想,如果中间有一个耗时长的任务(异步任务),例如:
ajax
从服务器获取数据,难道我们要等待数据返回了才继续完成后面的任务吗?这显然不合理。所以,事件循环就是帮我们解决这个问题的,它规定了如何来处理这些异步任务(
ajax
、setTimeout
等)。事件循环的工作方式大概是这样:
进入程序,主线程从任务队列中取读第一个任务(
script
包裹的代码,我们可以认为它本身就是一个全局函数)加入执行栈(stack
)执行,如果遇到异步任务并不会立即执行其中的代码,而是交给WebAPIs
去处理,然后继续执行后面的代码,WebAPIs
会在适当的时机将这些任务加入到任务队列(callback queue
)中,执行栈中的代码执行完毕,就会取读任务队列中的任务,并加入执行栈执行,如此往复。引用一张经典的图片:
以上图片中,异步任务有结果了就会将它所定义的回调函数加入到任务队列(
callback queue
)中,执行栈清空后就会取读任务队列。看看以下代码:
以上代码中,当JavaScript引擎遇到
setTimeout
,会把它的回调函数和毫秒值交给WebAPIs
,主线程继续执行后面的代码,所以先打印了1
和3
,1s之后WebAPIs
中的回调函数会被加入到任务队列中,由于主线程现在是空闲状态,所以会立即取读任务队列并执行打印出2。看看另一个例子:
以上代码中,
setTimeout
和Promise
都是异步任务,但Promise
先执行了,而不是setTimeout
?原来在任务队列中还分为了:
微任务队列(
Promise.then
)以及宏任务队列(
setTimeout
)微任务属于本次任务的附属任务,本次任务结束后会取读微任务队里的所有任务并依次执行,然后开始下一个宏任务,也就是宏任务队列中的第一个任务,结束后继续查看微任务队列,能看出来事件循环大概是这样进行的:
宏任务 -> 微任务(全部) -> 宏任务 -> 微任务(全部) -> ···
所以,在上面的例子中,
script
本身就是一个宏任务,执行完毕就会取读微任务队列(Promise.then
),然后是下一个宏任务(setTimeout
)。换句话说,微任务是被添加到了本次任务的末尾,而宏任务添加到了下次事件循环的开头,本次任务产生的微任务永远在宏任务之前执行。常见的宏任务(macrotask)和微任务(microtask):
宏任务:
微任务:
一次完整的事件循环
看看另一个例子:
以上代码中,通过观察发现,元素
.box
的背景色自始至终都是蓝色以上代码中,元素
.box
的背景色会先变成红色,然后立马变成蓝色两段代码反映一个问题,就是关于UI重新渲染的时机,是在一次事件循环的末尾进行的,也就是微任务之后会进行UI的重新渲染。
所以完整的事件循环应该是这样:
宏任务 -> 微任务(全部) -> UI渲染 -> 宏任务 -> 微任务(全部) -> UI渲染 ···
The text was updated successfully, but these errors were encountered: