程式碼例子
const { createApp } = Vue
const app = createApp({
components: [],
template: `
<div>
<button @click="handleClick">toggle</button>
</div>
`,
data() {
return {
visible: false,
test: 1,
}
},
methods: {
handleClick() {
this.visible = true; // 程式碼1
this.visible = false; // 程式碼2
this.visible = true; // 程式碼3
},
},
watch: {
visible(v) {
console.log('visible change', v)
},
}
}).mount('#app')
上述程式碼點選toggle後的效果如下圖。visible change只log了一次。
透過ReactiveEffect監聽改變
Vue 透過例項化 ReactiveEffect ,監聽getter的改變,回撥scheduler。透過這種方式去劫持 visible 的更改。
// packages\runtime-core\src\apiWatch.ts
let scheduler: EffectScheduler
if (flush === 'sync') {
scheduler = job as any
} else if (flush === 'post') {
scheduler = () => queuePostRenderEffect(job, instance && instance.suspense)
} else {
job.pre = true
if (instance) job.id = instance.uid
scheduler = () => queueJob(job)
}
const effect = new ReactiveEffect(getter, scheduler)
// getter在本場景中對應visible。scheduler會在getter更改時被呼叫
程式碼1執行 第一次賦值
程式碼 1 執行後, visible 設定為 true 。Vue 監聽到 visible 的更改。執行 queueJob ,將負責執行回撥函式的 job 推入 queue 。並利用 Promise.then 的回撥函式會被放入微任務佇列的特性,使用 then 將執行 queue 中所有方法的任務放入微任務佇列中。
// packages\runtime-core\src\scheduler.ts
export function queueJob(job: SchedulerJob) {
console.log('queueFlush called')
// 檢查queue是否為空、job是否存在queue中
if (
!queue.length ||
!queue.includes(
job,
isFlushing && job.allowRecurse ? flushIndex + 1 : flushIndex
)
) {
if (job.id == null) {
queue.push(job)
} else {
queue.splice(findInsertionIndex(job.id), 0, job)
}
queueFlush()
}
}
function queueFlush() {
if (!isFlushing && !isFlushPending) {
isFlushPending = true
currentFlushPromise = resolvedPromise.then(flushJobs) // flushJobs進入微任務
}
}
程式碼2執行 第二次賦值
程式碼 2 執行後, visible 設定為 false。Vue 知道 visible 更改後,發現 負責 visible 更新時回撥的 job 已經推入 queue,於是不再繼續執行推入操作。
程式碼3執行 第三次賦值
程式碼 3 執行後,visible 設定為 true。後面同上,不再繼續執行推入操作。
handleClick 執行完畢
handleClick 執行完畢。事件迴圈機制會去執行微任務佇列的任務。然後程式碼1執行後放入微任務佇列中的 job 得到了執行,回撥函式也得到了執行。
綜上所述
綜上所述,job因為只推入1次,所以只執行了一次,監聽回撥也只執行了一次。