您好,登錄后才能下訂單哦!
這篇文章主要介紹“Vue中Watcher和Scheduler的實(shí)現(xiàn)原理是什么”,在日常操作中,相信很多人在Vue中Watcher和Scheduler的實(shí)現(xiàn)原理是什么問(wèn)題上存在疑惑,小編查閱了各式資料,整理出簡(jiǎn)單好用的操作方法,希望對(duì)大家解答”Vue中Watcher和Scheduler的實(shí)現(xiàn)原理是什么”的疑惑有所幫助!接下來(lái),請(qǐng)跟著小編一起來(lái)學(xué)習(xí)吧!
Vue通過(guò)數(shù)據(jù)偵測(cè)機(jī)制感知狀態(tài)的變化,上一篇《Vue如何實(shí)現(xiàn)數(shù)據(jù)偵測(cè)》有提到Watcher對(duì)象,當(dāng)數(shù)據(jù)更新有更新,例如當(dāng)執(zhí)行this.title = '監(jiān)聽我變化了沒(méi)',在setter函數(shù)調(diào)用dep.notify通知watcher執(zhí)行更新(具體執(zhí)行watcher.update函數(shù))。
那么Vue在何時(shí)創(chuàng)建Watcher,如何通過(guò)Scheduler來(lái)調(diào)度Watcher隊(duì)列,watcher的更新最終如何體現(xiàn)到視圖的渲染,本篇內(nèi)容主要圍繞這三個(gè)問(wèn)題來(lái)介紹Vue的Watcher實(shí)現(xiàn)原理。
組件從創(chuàng)建到銷毀會(huì)經(jīng)歷一系列生命周期,其中我們比較熟悉的有beforeMount、mounted、beforeUpdate、updated, 了解了生命周期,理解Watcher在何時(shí)被創(chuàng)建就會(huì)容易很多。Vue共三處地方會(huì)創(chuàng)建Watcher對(duì)象,mount事件、$watch函數(shù)、computed和watch屬性, mount事件創(chuàng)建Watcher用于渲染通知,watch和computed創(chuàng)建的Watcher都用于監(jiān)聽用戶自定義的屬性變化。
文件core/instance/lifecycle.js包含了Vue生命周期相關(guān)的函數(shù),例如$forupdate、$destroy以及實(shí)例化Watcher的mountComponent函數(shù),mountComponent函數(shù)在組件掛載完成執(zhí)行$mount時(shí)觸發(fā),函數(shù)首先觸發(fā)beforeMount鉤子事件,在實(shí)例化Watcher時(shí)有傳入before函數(shù),before將觸發(fā)beforeUpdate hook。當(dāng)組件有屬性更新時(shí),watcher在更新(watcher.run)之前會(huì)觸發(fā)beforeUpdate事件。isRenderWatcher表明創(chuàng)建的是渲染W(wǎng)atcher,直接掛在vm._watcher屬性上,當(dāng)強(qiáng)制執(zhí)行$forceUpdate刷新渲染,會(huì)執(zhí)行vm._watcher.update觸發(fā)渲染過(guò)程以及對(duì)應(yīng)的update hook。
/** * 生命周期mount事件觸發(fā)函數(shù) * @param {*} vm * @param {*} el * @param {*} hydrating * @returns */ export function mountComponent ( vm: Component, el: ?Element, hydrating?: boolean ): Component { vm.$el = el callHook(vm, 'beforeMount') let updateComponent = () => { vm._update(vm._render(), hydrating) } // 實(shí)例化Watcher對(duì)象,在Watcher構(gòu)造函數(shù)中建立Watcher和vm的關(guān)系 new Watcher(vm, updateComponent, noop, { // 在執(zhí)行wather.run函數(shù)之前觸發(fā)before hook事件 before () { if (vm._isMounted && !vm._isDestroyed) { callHook(vm, 'beforeUpdate') } } // isRenderWatcher表示用于渲染的Watcher,在執(zhí)行$forceupdate時(shí)會(huì)手動(dòng)觸發(fā)watcher.update }, true /* isRenderWatcher */) return vm } export default class Watcher { constructor ( vm: Component, expOrFn: string | Function, cb: Function, options?: ?Object, isRenderWatcher?: boolean ) { this.vm = vm if (isRenderWatcher) { vm._watcher = this } vm._watchers.push(this) this.getter = expOrFn this.value = this.lazy ? undefined : this.get() } } Vue.prototype.$forceUpdate = function () { const vm: Component = this if (vm._watcher) { vm._watcher.update() } }
在組件中,除了使用watch、computed方法監(jiān)聽屬性變化,Vue定義了$watch函數(shù)用于監(jiān)聽屬性變化,例如當(dāng)a.b.c嵌套屬性變化,可以$watch來(lái)實(shí)現(xiàn)監(jiān)聽做后續(xù)處理,$watch相當(dāng)于在組件中直接寫watch屬性的函數(shù)式寫法,可支持在運(yùn)行時(shí)動(dòng)態(tài)的添加依賴監(jiān)聽,例如Vue源碼中的keep-alive組件在mounted事件中使用$watch監(jiān)聽include、exclude屬性變化。
vm.$watch( expOrFn, callback, [options] ) 參數(shù): {string | Function} expOrFn {Function | Object} callback {Object} [options] {boolean} deep {boolean} immediate 返回值:{Function} unwatch // 鍵路徑 vm.$watch('a.b.c', function (newVal, oldVal) { // 做點(diǎn)什么 }) // keep-alive.js文件 mounted () { this.cacheVNode() this.$watch('include', val => { pruneCache(this, name => matches(val, name)) }) this.$watch('exclude', val => { pruneCache(this, name => !matches(val, name)) }) }
$watch函數(shù)和mountComponent函數(shù)的區(qū)別是,mountComponent用于渲染監(jiān)聽,會(huì)觸發(fā)相關(guān)的hook事件,而$watch的職責(zé)比較專一,就處理expOrFn的監(jiān)聽。另外,$watch的cb參數(shù)可以是函數(shù)、對(duì)象或字符串,當(dāng)為字符串時(shí)表示定義在Vue對(duì)象的函數(shù)名,例如在Vue組件中定義了nameChange函數(shù),那么定義vm.$watch('name', 'nameChange')后,如果name有更新會(huì)觸發(fā)Vue實(shí)體的nameChange函數(shù)。
// 監(jiān)聽屬性變化 Vue.prototype.$watch = function ( expOrFn: string | Function, cb: any, options?: Object ): Function { const vm: Component = this // cb可能是純JS對(duì)象,那么回調(diào)為cb.handler if (isPlainObject(cb)) { return createWatcher(vm, expOrFn, cb, options) } const watcher = new Watcher(vm, expOrFn, cb, options) // 返回watch注銷監(jiān)聽函數(shù) return function unwatchFn () { watcher.teardown() } } function createWatcher ( vm: Component, expOrFn: string | Function, handler: any, options?: Object ) { // 當(dāng)執(zhí)行函數(shù)是一個(gè)對(duì)象的時(shí)候, 將 handler 的 handler調(diào)用給執(zhí)行函數(shù) // 這里的 options 是 watch 函數(shù)的配置信息 if (isPlainObject(handler)) { options = handler handler = handler.handler } if (typeof handler === 'string') { handler = vm[handler] } return vm.$watch(expOrFn, handler, options) }
使用Vue開發(fā)組件,這兩個(gè)屬性一定不陌生,例如使用watch定義firstName、secondName屬性的監(jiān)聽,使用computed定義fullName屬性監(jiān)聽,當(dāng)firstName和secondName更新時(shí)fullName也隨之觸發(fā)更新。
new Vue({ el: '#app', data() { return { firstName: 'Li', secondName: 'Lei' } }, watch: { secondName: function (newVal, oldVal) { console.log('second name changed: ' + newVal) } }, computed: { fullName: function() { return this.firstName + this.secondName } }, mounted() { this.firstName = 'Han' this.secondName = 'MeiMei' } })
當(dāng)我們?cè)趙atch和computed定義了對(duì)屬性的監(jiān)聽,Vue在何時(shí)將其轉(zhuǎn)換為Watcher對(duì)象執(zhí)行監(jiān)聽?Vue的構(gòu)造函數(shù)會(huì)調(diào)用_init(options)執(zhí)行初始化,源碼core/components/instance/init.js文件定義了_init函數(shù),執(zhí)行了一些列初始化操作,例如初始化生命周期、事件、狀態(tài)等,其中initState函數(shù)就包含了watch和computed的初始化。
// core/components/instance/init.js // Vue構(gòu)造函數(shù) function Vue (options) { this._init(options) } // core/components/instance/init.js Vue.prototype._init = function (options?: Object) { initLifecycle(vm) initEvents(vm) initRender(vm) callHook(vm, 'beforeCreate') initInjections(vm) // resolve injections before data/props initState(vm) initProvide(vm) // resolve provide after data/props callHook(vm, 'created') } // // core/components/state.js export function initState (vm: Component) { vm._watchers = [] const opts = vm.$options ... if (opts.computed) initComputed(vm, opts.computed) if (opts.watch && opts.watch !== nativeWatch) { initWatch(vm, opts.watch) } }
initComputed初始化computed屬性,每一個(gè)Vue實(shí)體都包含_computedWatchers對(duì)象用于存儲(chǔ)所有computed屬性的watcher對(duì)象。首先遍歷computed對(duì)象,為每個(gè)key創(chuàng)建一個(gè)新的Watcher對(duì)象,其lazy屬性為true,表示W(wǎng)atcher會(huì)緩存計(jì)算值,如果依賴其依賴的屬性(如firstName、secondName)沒(méi)有更新,當(dāng)前computed屬性(例如fullName)也不會(huì)觸發(fā)更新。computed中定義的屬性可以通過(guò)this(例如this.fullName)訪問(wèn),defineComputed將所有computed屬性掛載到Vue實(shí)體上。
// lazy為true表示需要緩存,一般只有computed屬性才會(huì)用到 const computedWatcherOptions = { lazy: true } function initComputed (vm: Component, computed: Object) { const watchers = vm._computedWatchers = Object.create(null) for (const key in computed) { const userDef = computed[key] // 用戶定義的執(zhí)行函數(shù)可能是{ get: function() {} }形式 const getter = typeof userDef === 'function' ? userDef : userDef.get // 為用戶定義的每個(gè)computed屬性創(chuàng)建watcher對(duì)象 watchers[key] = new Watcher( vm, getter || noop, noop, computedWatcherOptions ) // 組件自身的computed屬性已經(jīng)定義在組件原型鏈上,我們只需要定義實(shí)例化的computed屬性。 // 例如我們?cè)赾omputed定義了fullName,defineComputed會(huì)將其掛接到Vue對(duì)象的屬性上 if (!(key in vm)) { defineComputed(vm, key, userDef) } }
defineComputed函數(shù)將計(jì)算屬性轉(zhuǎn)換為{ get, set }形式,但計(jì)算屬性不需要set,所以代碼直接為其賦值了noop空函數(shù)。計(jì)算屬性的get函數(shù)通過(guò)createComputedGetter封裝,首先找到對(duì)應(yīng)屬性的watcher對(duì)象,如果watcher的dirty為true,表示依賴屬性有更新,需要調(diào)用evaluate函數(shù)重新計(jì)算新值。
// 將computed定義的屬性轉(zhuǎn)換為{ get, set }形式并掛接到Vue實(shí)體上,這樣就可以通過(guò)this.fullName形式調(diào)用 export function defineComputed ( target: any, key: string, userDef: Object | Function ) { if (typeof userDef === 'function') { sharedPropertyDefinition.get = createComputedGetter(key) sharedPropertyDefinition.set = noop } else { sharedPropertyDefinition.get = userDef.get ? createComputedGetter : noop sharedPropertyDefinition.set = userDef.set || noop } Object.defineProperty(target, key, sharedPropertyDefinition) } // 定義computed的專屬getter函數(shù) function createComputedGetter (key) { return function computedGetter () { // _computedWatchers上為每個(gè)computed屬性定義了Watcher對(duì)象 const watcher = this._computedWatchers && this._computedWatchers[key] if (watcher) { // dirty為true,表示依賴的屬性有變化 if (watcher.dirty) { // 重新計(jì)算值 watcher.evaluate() } if (Dep.target) { // 將Dep.target(watcher)附加到當(dāng)前watcher的依賴中 watcher.depend() } return watcher.value } } }
如果Dep.target有值,將其他依賴當(dāng)前計(jì)算屬性的Watcher(例如使用到fullName的依賴Watcher)附加到當(dāng)前計(jì)算屬性所依賴的屬性的dep集合中。如下面的代碼創(chuàng)建了對(duì)fullName計(jì)算屬性的監(jiān)聽, 我們將其命名為watcher3。那么firstName和secondName的dep對(duì)象都會(huì)附加上watcher3觀察者,只要其屬性有任何變化,都會(huì)觸發(fā)watcher3的update函數(shù),重新讀取fullName屬性值。
vm.$watch('fullName', function (newVal, oldVal) { // 做點(diǎn)什么 })
initWatch函數(shù)邏輯相對(duì)簡(jiǎn)單些,遍歷每個(gè)屬性的依賴項(xiàng),如果依賴項(xiàng)為數(shù)組,則遍歷數(shù)組,為每個(gè)依賴項(xiàng)單獨(dú)創(chuàng)建Watcher觀察者,createWatcher函數(shù)在前文中有提到,它使用$watch創(chuàng)建新的watcher實(shí)體。
// 初始化Watch屬性 function initWatch (vm: Component, watch: Object) { for (const key in watch) { const handler = watch[key] // 如果對(duì)應(yīng)屬性key有多個(gè)依賴項(xiàng),則遍歷為每個(gè)依賴項(xiàng)創(chuàng)建watcher if (Array.isArray(handler)) { for (let i = 0; i < handler.length; i++) { createWatcher(vm, key, handler[i]) } } else { createWatcher(vm, key, handler) } } }
Vue在core/observer/scheduler.js文件定義了調(diào)度函數(shù),一共有兩處使用,Watcher對(duì)象以及core/vdom/create-component.js文件。watcher對(duì)象在執(zhí)行更新時(shí),會(huì)被附加到調(diào)度隊(duì)列中等待執(zhí)行。create-component.js主要處理渲染過(guò)程,使用scheduler的主要作用是觸發(fā)activated hook事件。這里重點(diǎn)闡述Watcher對(duì)Scheduler的使用。
當(dāng)執(zhí)行watcher的update函數(shù),除了lazy(計(jì)算屬性watcher)、sync(同步watcher),所有watcher都將調(diào)用queueWatcher函數(shù)附加到調(diào)度隊(duì)列中。
export default class Watcher { /** * 通知訂閱,如果依賴項(xiàng)有更新,該函數(shù)會(huì)被觸發(fā) */ update () { /* istanbul ignore else */ if (this.lazy) { this.dirty = true } else if (this.sync) { this.run() } else { queueWatcher(this) } } }
queueWatcher函數(shù)定義如下,函數(shù)的目的是將watcher附加到調(diào)度隊(duì)列中,對(duì)調(diào)度隊(duì)列創(chuàng)建微任務(wù)(microTask),等待執(zhí)行。關(guān)于microTask和macroTask的區(qū)別,看查看參考8“宏任務(wù)macroTask和微任務(wù)microTask的區(qū)別”。如果微任務(wù)flushSchedulerQueue還未執(zhí)行(flushing為false),直接將watcher附加到queue即可。否則,還需判斷當(dāng)前微任務(wù)的執(zhí)行進(jìn)度,queue會(huì)按watcher的id做升序排序,保證先創(chuàng)建的watcher先執(zhí)行。index為微任務(wù)中正在被執(zhí)行的watcher索引,watcher將會(huì)插入到大于index且符合id升序排列的位置。最后隊(duì)列執(zhí)行函數(shù)flushSchedulerQueue將通過(guò)nextTick創(chuàng)建一個(gè)微任務(wù)等待執(zhí)行。
/* * 附加watcher到隊(duì)列中,如果有重復(fù)的watcher直接跳過(guò)。 * 如果調(diào)度隊(duì)列正在執(zhí)行(flushing為true),將watcher放到合適的位置 */ export function queueWatcher (watcher: Watcher) { // 所有watcher都有一個(gè)遞增的唯一標(biāo)識(shí), const id = watcher.id // 如果watcher已經(jīng)在隊(duì)列中,不做處理 if (has[id] == null) { has[id] = true if (!flushing) { // 如果隊(duì)列還未執(zhí)行,則直接附加到隊(duì)列尾部 queue.push(watcher) } else { // 如果正在執(zhí)行,基于id將其附加到合適的位置。 // index為當(dāng)前正在執(zhí)行的watcher索引,并且index之前的watcher都被執(zhí)行了。 // 先創(chuàng)建的watcher應(yīng)該被先執(zhí)行,和隊(duì)列中的watcher比較id大小,插入到合適的位置。 let i = queue.length - 1 while (i > index && queue[i].id > watcher.id) { i-- } // i的位置,表明 watcher[i - 1].id < watcher[i].id < watcher[i + 1].id queue.splice(i + 1, 0, watcher) } // 如果未排隊(duì),開始排隊(duì),nextick將執(zhí)行調(diào)度隊(duì)列。 if (!waiting) { waiting = true nextTick(flushSchedulerQueue) } } }
nextTick將會(huì)選擇適合當(dāng)前瀏覽器的微任務(wù)執(zhí)行隊(duì)列,例如MutationObserver、Promise、setImmediate。flushSchedulerQueue函數(shù)將遍歷所有watcher并執(zhí)行更新,首先需要將queue做升序排序,確保先創(chuàng)建的watcher先被執(zhí)行,例如父組件的watcher優(yōu)先于子組件執(zhí)行。接著遍歷queue隊(duì)列,先觸發(fā)watcher的before函數(shù),例如前文中介紹mountComponent函數(shù)在創(chuàng)建watcher時(shí)會(huì)傳入before事件,觸發(fā)callHook(vm, 'beforeUpdate')。接下來(lái)就具體執(zhí)行更新(watcher.run)操作。當(dāng)隊(duì)列執(zhí)行完后,調(diào)用resetSchedulerState函數(shù)清空隊(duì)列、重置執(zhí)行狀態(tài)。最后callActivatedHooks和callUpdatedHooks將觸發(fā)對(duì)應(yīng)的activated、updated hook事件。
/** * 遍歷執(zhí)行所有的watchers */ function flushSchedulerQueue () { currentFlushTimestamp = getNow() flushing = true let watcher, id // 遍歷之前先排序隊(duì)列 // 排序的隊(duì)列能確保: // 1.父組件先于子組件更新,因?yàn)楦附M件肯定先于子組件創(chuàng)建。 // 2.組件自定義的watcher將先于渲染watcher執(zhí)行,因?yàn)樽远xwatcher先于渲染watcher創(chuàng)建。 // 3.如果組件在父組件執(zhí)行wtcher期間destroyed了,它的watcher集合可以直接被跳過(guò)。 queue.sort((a, b) => a.id - b.id) // 不要緩存length,因?yàn)樵诒闅vqueue執(zhí)行wacher的同時(shí),queue隊(duì)列一直在調(diào)整。 for (index = 0; index < queue.length; index++) { watcher = queue[index] if (watcher.before) { // 通過(guò)before可觸發(fā)hook,例如執(zhí)行beforeUpdated hook watcher.before() } id = watcher.id has[id] = null // 執(zhí)行watcher的更新 watcher.run() } // 由于activatedChildren和queue兩個(gè)隊(duì)列一直在更新,因?yàn)樾枰截愄幚? const activatedQueue = activatedChildren.slice() const updatedQueue = queue.slice() // 重置掉隊(duì)隊(duì)列狀態(tài) resetSchedulerState() // 觸發(fā)activated和updated hooks callActivatedHooks(activatedQueue) callUpdatedHooks(updatedQueue) }
調(diào)度隊(duì)列會(huì)執(zhí)行watcher的run函數(shù)觸發(fā)更新,每個(gè)watcher有active狀態(tài),表明當(dāng)前watcher是否處于激活狀態(tài),當(dāng)組件執(zhí)行$destroy函數(shù),會(huì)調(diào)用watcher的teardown函數(shù)將active設(shè)置為false。在執(zhí)行更新通知回調(diào)cb之前,有三個(gè)條件判斷,首先判斷值是否相等,對(duì)于簡(jiǎn)單值string或number類型的可直接判斷;如果value為對(duì)象或需要深度遍歷(deep為true),例如用戶自定義了person屬性,其值為對(duì)象{ age: number, sex: number },我們使用$watch('person', cb)監(jiān)聽了person屬性,但當(dāng)person.age發(fā)生變化時(shí),cb不會(huì)被執(zhí)行。如果改成$watch('person', cb, { deep: true }),任何嵌套的屬性發(fā)生變化,cb都會(huì)被觸發(fā)。滿足三個(gè)條件其中之一,cb回調(diào)函數(shù)將被觸發(fā)。
export default class Watcher { /** * 調(diào)度接口,將被調(diào)度器執(zhí)行 */ run () { // 僅當(dāng)watcher處于激活狀態(tài),才會(huì)執(zhí)行更新通知 // 當(dāng)組件destroyed時(shí),會(huì)調(diào)用watcher的teardown將其重置到非激活狀態(tài) if (this.active) { // 調(diào)用get獲取值 const value = this.get() if ( // 如果新計(jì)算的值更新了 value !== this.value || // 如果value為對(duì)象或數(shù)組,不管value和this.value相等否,則其深度watchers也應(yīng)該被觸發(fā) // 因?yàn)槠淝短讓傩钥赡馨l(fā)生變化了 isObject(value) || this.deep ) { const oldValue = this.value this.cb.call(this.vm, value, oldValue) } } } } this.$watch('person', () => { this.message = '年齡為:' + this.person.age }, // 當(dāng)deep為true,當(dāng)age更新,回調(diào)會(huì)被觸發(fā);如果deep為false,age更新不會(huì)觸發(fā)回調(diào) { deep: true } )
run函數(shù)有調(diào)用get獲取最新值,在get函數(shù)中,首先調(diào)用pushTarget函數(shù)將當(dāng)前Watcher附加到全局Dep.target上,然后執(zhí)行g(shù)etter獲取最新值。在finally模塊中,如果deep為true,則調(diào)用traverse遞歸遍歷最新的value,value可能為Object或者Array,所以需要遍歷子屬性并觸發(fā)其getter函數(shù),將其dep屬性附加上Dep.target(當(dāng)前Watcher),這樣任何子屬性的值發(fā)生變化都會(huì)通知到當(dāng)前watcher,至于為什么,可以回顧下上篇《Vue如何實(shí)現(xiàn)數(shù)據(jù)狀態(tài)的偵測(cè)》。
export default class Watcher { /** * 執(zhí)行g(shù)etter,重新收集依賴項(xiàng) */ get () { // 將當(dāng)前Watcher附加到全局Dep.target上,并存儲(chǔ)targetStack堆棧中 pushTarget(this) let value const vm = this.vm try { // 執(zhí)行g(shù)etter讀取value value = this.getter.call(vm, vm) } catch (e) { if (this.user) { handleError(e, vm, `getter for watcher "${this.expression}"`) } else { throw e } } finally { // 如果deep為true,將遍歷+遞歸value對(duì)象 // 將所有嵌套屬性的dep都附加上當(dāng)前watcher,所有子屬性對(duì)應(yīng)的dep都會(huì)從push(Dep.target) if (this.deep) { // 遞歸遍歷所有嵌套屬性,并觸發(fā)其getter,將其對(duì)應(yīng)的dep附加當(dāng)前watcher traverse(value) } // 退出堆棧 popTarget() // 清理依賴 this.cleanupDeps() } return value } }
在get函數(shù)中為什么要執(zhí)行traverse遞歸遍歷子屬性,我們可以通過(guò)實(shí)際的例子來(lái)說(shuō)明,例如在data中定義了{(lán) person: { age: 18, sex: 0, addr: { city: '北京', detail: '五道口' } }, Vue會(huì)調(diào)用observe將person轉(zhuǎn)換為如下Observer對(duì)象,子屬性(如果為對(duì)象)也會(huì)轉(zhuǎn)換為Observer對(duì)象,簡(jiǎn)單屬性都會(huì)定義get、set函數(shù)。
當(dāng)watcher.get執(zhí)行traverse函數(shù)時(shí),會(huì)遞歸遍歷子屬性,當(dāng)遍歷到addr屬性時(shí),觸發(fā)get函數(shù),該函數(shù)將調(diào)用其dep.depend將當(dāng)前Watcher附加到依賴項(xiàng)中,這樣我們?cè)趫?zhí)行執(zhí)行this.person.age = 18,其set函數(shù)調(diào)用dep.notify觸發(fā)watcher的update函數(shù),實(shí)現(xiàn)person對(duì)象的監(jiān)聽。
get: function reactiveGetter () { const value = getter ? getter.call(obj) : val if (Dep.target) { dep.depend() ... } return value } set: function reactiveSetter (newVal) { ... dep.notify() }
到此,關(guān)于“Vue中Watcher和Scheduler的實(shí)現(xiàn)原理是什么”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注億速云網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)?lái)更多實(shí)用的文章!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。