Vue.js原理分析之nextTick實(shí)現(xiàn)詳解
前言
tips:第一次發(fā)技術(shù)文章,篇幅比較簡(jiǎn)短,主要采取文字和關(guān)鍵代碼表現(xiàn)的形式,希望幫助到大家。(若有不正確還請(qǐng)多多指正)
nextTick作用和用法
用法:nextTick接收一個(gè)回調(diào)函數(shù)作為參數(shù),它的作用是將回調(diào)延遲到下一次DOM更新之后執(zhí)行,如果沒(méi)有提供回調(diào)函數(shù)參數(shù)且在支持Promise的環(huán)境中,nextTick將返回一個(gè)Promise。
適用場(chǎng)景:開(kāi)發(fā)過(guò)程中,開(kāi)發(fā)者需要在更新完數(shù)據(jù)之后,需要對(duì)新DOM做一些操作,其實(shí)我們當(dāng)時(shí)無(wú)法對(duì)新DOM進(jìn)行操作,因?yàn)檫@時(shí)候還沒(méi)有重新渲染,這時(shí)候nextTick就派上了用場(chǎng)。
nextTick實(shí)現(xiàn)原理
下面我們介紹下nextTick工作原理:
首先我們應(yīng)該了解到更新完數(shù)據(jù)(狀態(tài))之后,DOM更新這個(gè)動(dòng)作并不是同步進(jìn)行的,而是異步的。Vue.js中有一個(gè)隊(duì)列,每當(dāng)需要渲染時(shí),會(huì)將Watcher推送到這個(gè)隊(duì)列中,等下一次事件循環(huán)中再讓W(xué)atcher觸發(fā)渲染流程。這里我們可能會(huì)有兩個(gè)疑問(wèn):
**1.為什么更新DOM是異步的?**
我們知道從Vue2.0開(kāi)始使用虛擬DOM進(jìn)行渲染,變化偵測(cè)只發(fā)送到組件級(jí)別,組件內(nèi)部則通過(guò)虛擬DOM的diff(比對(duì))而進(jìn)行局部渲染,而在同一次事件循環(huán)中組件假如收到兩份通知,組件是否會(huì)進(jìn)行兩次渲染呢?事實(shí)上一次事件循環(huán)組件會(huì)在所有狀態(tài)修改完畢之后只進(jìn)行一次渲染操作。
**2.什么是事件循環(huán)?**
javascript是單線(xiàn)程腳本語(yǔ)言,它具有非阻塞特性,之所以非阻塞是由于在處理異步代碼時(shí),主線(xiàn)程會(huì)掛起這個(gè)任務(wù),當(dāng)異步任務(wù)處理完畢之后會(huì)根據(jù)一定的規(guī)則去執(zhí)行異步任務(wù)的回調(diào),異步任務(wù)分宏任務(wù)(macrotast)和微任務(wù)(microtast),它們會(huì)被分配到不同的隊(duì)列中,當(dāng)執(zhí)行棧所有任務(wù)執(zhí)行完畢之后,會(huì)先檢查微任務(wù)隊(duì)列中是否有事件存在,優(yōu)先執(zhí)行微任務(wù)隊(duì)列事件對(duì)應(yīng)的回調(diào),直至為空。然后再執(zhí)行宏任務(wù)隊(duì)列中事件的回調(diào)。無(wú)限重復(fù)這個(gè)過(guò)程,形成一個(gè)無(wú)限循環(huán)就叫做事件循環(huán)。
常見(jiàn)微任務(wù)包括:Promise 、MutationObserver、Object.observer、process.nextTick等
常見(jiàn)宏任務(wù)包括:setTimeout、setInterval、setImmediate、MessageChannel、requestAnimation、UI交互事件等
微任務(wù)如何注冊(cè)?
nextTick會(huì)將回調(diào)添加到異步任務(wù)隊(duì)列中延遲執(zhí)行,在執(zhí)行回調(diào)前,反復(fù)調(diào)用nextTick,Vue并不會(huì)反復(fù)添加到任務(wù)隊(duì)列中,只會(huì)向任務(wù)隊(duì)列添加一個(gè)任務(wù),多次使用nextTick只會(huì)將回調(diào)添加到回調(diào)列表緩存起來(lái),當(dāng)任務(wù)觸發(fā)時(shí),會(huì)清空回調(diào)列表并依次執(zhí)行所有回調(diào) ,具體代碼如下:
const callbacks = []
let pending = false
function flushCallbacks(){ //執(zhí)行回調(diào)
pending = false
const copies = callbacks.slice(0)
callbacks.length = 0 //清空回調(diào)隊(duì)列
for(let i = 0; i < copies.length; i++) {
copies[i]()
}
}
let microTimerFunc
const p = Promise.resolve()
microTimerFunc = () => { //注冊(cè)微任務(wù)
p.then(flushCallbacks)
}
export function nextTick(cb,ctx){
callbacks.push(()=>{
if(cb){
cb.call(ctx)
}
})
if(!pending){
pending = true //將pending設(shè)置為true,保證任務(wù)在依次事件循環(huán)中不會(huì)重復(fù)添加
microTimerFunc()
}
}
由于微任務(wù)優(yōu)先級(jí)太高,可能在某些場(chǎng)景下需要使用到宏任務(wù),所以Vue提供了可以強(qiáng)制使用宏任務(wù)的方法withMacroTask。具體實(shí)現(xiàn)如下:
const callbacks = []
let pending = false
function flushCallbacks(){ //執(zhí)行回調(diào)
pending = false
const copies = callbacks.slice(0)
callbacks.length = 0 //清空回調(diào)隊(duì)列
for(let i = 0; i < copies.length; i++) {
copies[i]()
}
}
let microTimerFunc
//新增代碼
let macroTimerFunc = function(){
...
}
let useMacroTask = false
const p = Promise.resolve()
microTimerFunc = () => { //注冊(cè)微任務(wù)
p.then(flushCallbacks)
}
//新增代碼
export function withMacroTask(fn){
return fn._withTask || fn._withTask = function()=>{
useMacroTask = true
const res = fn.apply(null,arguments)
useMacroTask = false
return res
}
}
export function nextTick(cb,ctx){
callbacks.push(()=>{
if(cb){
cb.call(ctx)
}
})
if(!pending){
pending = true //將pending設(shè)置為true,保證任務(wù)在依次事件循環(huán)中不會(huì)重復(fù)添加
//修改代碼
if(useMacroTask){
macroTimerFunc()
}else{
microTimerFunc()
}
}
}
上面提供了一個(gè)withMacroTask方法強(qiáng)制使用宏任務(wù),通過(guò)useMacroTask變量進(jìn)行控制是否使用注冊(cè)宏任務(wù)執(zhí)行,withMacroTask實(shí)現(xiàn)很簡(jiǎn)單,先將useMacroTask變量設(shè)置為true,然后執(zhí)行回調(diào),回調(diào)執(zhí)行之后再改回false。
宏任務(wù)是如何注冊(cè)?
注冊(cè)宏任務(wù)優(yōu)先使用setImmediate,但是存在兼容性問(wèn)題,只能在IE中使用,所以使用MessageChannel作為備選方案,若以上都不支持則最后會(huì)使用setTimeout。具體實(shí)現(xiàn)如下:
if(typeof setImmediate !== 'undefined' && isNative(setImmediate)){
macroTimerFunc = ()=>{
setImmediate(flushCallbacks)
}
} else if(
typeof MessageChannel !== 'undefined' &&
(isNative(MessageChannel) || MessageChannel.toString() === '[Object MessageChannelConstructor]')
){
const channel = new MessageChannel()
const port = channel.port2
channel.port1.onmessage = flushCallbacks
macroTimerFunc = ()=>{
port.postMessage(1)
}
} else {
macroTimerFunc = ()=>{
setTimout(flushCallbacks,0)
}
}
microTimerFunc的實(shí)現(xiàn)方法是通過(guò)Promise.then,但是并不是所有瀏覽器都支持Promise,當(dāng)不支持的時(shí)候采取降級(jí)為宏任務(wù)方式
if(typeof Promise !== 'undefined' && isNative(Promise)){
const p = Promise.resolve()
microTimerFunc = ()=>{
p.then(flushCallbacks)
}
} else {
microTimerFunc = macroTimerFunc
}
若未提供回調(diào)且環(huán)境支持Promise情況下,nextTick會(huì)返回一個(gè)Promise,具體實(shí)現(xiàn)如下:
export function nextTick(cb, ctx) {
let _resolve
callbacks.push(()=>{
if(cb){
cb.call(ctx)
}else{
_resolve(ctx)
}
})
if(!pending){
pending = true
if(useMacroTask){
macroTimerFunc()
}else{
microTimerFunc()
}
}
if(typeof Promise !== 'undefined' && isNative(Promise)){
return new Promise(resolve=>{
_resolve = resolve
})
}
}
以上是nextTick運(yùn)行原理的設(shè)計(jì),完整代碼如下:
const callbacks = []
let pending = false
function flushCallbacks(){ //執(zhí)行回調(diào)
pending = false
const copies = callbacks.slice(0)
callbacks.length = 0 //清空回調(diào)隊(duì)列
for(let i = 0; i < copies.length; i++) {
copies[i]()
}
}
let microTimerFunc
let macroTimerFunc
let useMacroTask = false
//注冊(cè)宏任務(wù)
if(typeof setImmediate !== 'undefined' && isNative(setImmediate)){
macroTimerFunc = ()=>{
setImmediate(flushCallbacks)
}
} else if(
typeof MessageChannel !== 'undefined' &&
(isNative(MessageChannel) || MessageChannel.toString() === '[Object MessageChannelConstructor]')
){
const channel = new MessageChannel()
const port = channel.port2
channel.port1.onmessage = flushCallbacks
macroTimerFunc = ()=>{
port.postMessage(1)
}
} else {
macroTimerFunc = ()=>{
setTimout(flushCallbacks,0)
}
}
//微任務(wù)注冊(cè)
if(typeof Promise !== 'undefined' && isNative(Promise)){
const p = Promise.resolve()
microTimerFunc = ()=>{
p.then(flushCallbacks)
}
} else {//降級(jí)處理
microTimerFunc = macroTimerFunc
}
export function withMacroTask(fn){
return fn._withTask || fn._withTask = function()=>{
useMacroTask = true
const res = fn.apply(null,arguments)
useMacroTask = false
return res
}
}
export function nextTick(cb,ctx){
let _resolve
callbacks.push(()=>{
if(cb){
cb.call(ctx)
}else{
_resolve(ctx)
}
})
if(!pending){
pending = true //將pending設(shè)置為true,保證任務(wù)在依次事件循環(huán)中不會(huì)重復(fù)添加
//修改代碼
if(useMacroTask){
macroTimerFunc()
}else{
microTimerFunc()
}
}
if(typeof Promise !== 'undefined' && isNative(Promise)){
return new Promise(resolve=>{
_resolve = resolve
})
}
}
以上便是對(duì)nextTick的實(shí)現(xiàn)原理的全部介紹。
參考資料
Vue.js深入淺出
總結(jié)
到此這篇關(guān)于Vue.js原理分析之nextTick實(shí)現(xiàn)詳解的文章就介紹到這了,更多相關(guān)Vue.js原理之nextTick實(shí)現(xiàn)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Vue nextTick的原理解析
- Vue中的nextTick作用和幾個(gè)簡(jiǎn)單的使用場(chǎng)景
- 淺析vue中的nextTick
- 全面解析Vue中的$nextTick
- VUE異步更新DOM - 用$nextTick解決DOM視圖的問(wèn)題
- Vue中this.$nextTick的作用及用法
- 詳解vue中$nextTick和$forceUpdate的用法
- 深入學(xué)習(xí)Vue nextTick的用法及原理
- vue中nextTick用法實(shí)例
- vue源碼nextTick使用及原理解析
- 一文了解Vue中的nextTick
- 瀏覽器事件循環(huán)與vue nextTicket的實(shí)現(xiàn)
- vue中$nextTick的用法講解
- 從源碼里了解vue中的nextTick的使用
- vue2.0$nextTick監(jiān)聽(tīng)數(shù)據(jù)渲染完成之后的回調(diào)函數(shù)方法
- 詳解vue指令與$nextTick 操作DOM的不同之處
- 深入理解Vue nextTick 機(jī)制
- 談?wù)刅ue中的nextTick
相關(guān)文章
Element-ui?Dialog對(duì)話(huà)框基本使用
這篇文章主要為大家介紹了Element-ui?Dialog對(duì)話(huà)框基本使用示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06
詳解vue-admin和后端(flask)分離結(jié)合的例子
本篇文章主要介紹了詳解vue-admin和后端(flask)分離結(jié)合的例子,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-02-02
Vue2 監(jiān)聽(tīng)屬性改變watch的實(shí)例代碼
今天小編就為大家分享一篇Vue2 監(jiān)聽(tīng)屬性改變watch的實(shí)例代碼,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-08-08
vue-admin-element項(xiàng)目突然就起不來(lái)了的解決
這篇文章主要介紹了vue-admin-element項(xiàng)目突然就起不來(lái)了的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-10-10
vue?同局域網(wǎng)訪(fǎng)問(wèn)不到的問(wèn)題及解決
這篇文章主要介紹了vue?同局域網(wǎng)訪(fǎng)問(wèn)不到的問(wèn)題及解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-10-10
vue3的setup語(yǔ)法如何自定義v-model為公用hooks
這篇文章主要介紹了vue3的setup語(yǔ)法如何自定義v-model為公用hooks,文章分為兩個(gè)部分介紹,簡(jiǎn)單介紹vue3的setup語(yǔ)法如何自定義v-model和如何提取v-model語(yǔ)法作為一個(gè)公用hooks2022-07-07
vue3+ts+vite打包后靜態(tài)資源404無(wú)法加載js和css問(wèn)題解決辦法
這篇文章主要給大家介紹了關(guān)于vue3+ts+vite打包后靜態(tài)資源404無(wú)法加載js和css問(wèn)題的解決辦法,文中通過(guò)代碼以及圖文介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2024-04-04
vue.js實(shí)現(xiàn)選項(xiàng)卡切換
這篇文章主要為大家詳細(xì)介紹了vue.js實(shí)現(xiàn)選項(xiàng)卡切換功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03
vue.js實(shí)現(xiàn)回到頂部動(dòng)畫(huà)效果
這篇文章主要為大家詳細(xì)介紹了vue.js實(shí)現(xiàn)回到頂部動(dòng)畫(huà)效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-07-07

