欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

一文詳解Vue3的watch是如何實(shí)現(xiàn)監(jiān)聽的

 更新時(shí)間:2024年11月26日 15:07:56   作者:前端歐陽(yáng)  
watch這個(gè)API大家都很熟悉,今天這篇文章小編來帶你搞清楚Vue3的watch是如何實(shí)現(xiàn)對(duì)響應(yīng)式數(shù)據(jù)進(jìn)行監(jiān)聽的,希望對(duì)大家有一定的幫助

前言

watch這個(gè)API大家都很熟悉,今天這篇文章歐陽(yáng)來帶你搞清楚Vue3的watch是如何實(shí)現(xiàn)對(duì)響應(yīng)式數(shù)據(jù)進(jìn)行監(jiān)聽的。注:本文使用的Vue版本為3.5.13

看個(gè)demo

我們來看個(gè)簡(jiǎn)單的demo,代碼如下:

<template>
  <button @click="count++">count++</button>
</template>

<script setup lang="ts">
import { ref, watch } from "vue";
const count = ref(0);
watch(count, (preVal, curVal) => {
  console.log("count is changed", preVal, curVal);
});
</script>

這個(gè)demo很簡(jiǎn)單,使用watch監(jiān)聽了響應(yīng)式變量count,在watch回調(diào)中進(jìn)行了console打印。如何有個(gè)button按鈕,點(diǎn)擊后會(huì)count++。

開始打斷點(diǎn)

現(xiàn)在我們第一個(gè)斷點(diǎn)應(yīng)該打在哪里呢?

我們要看watch的實(shí)現(xiàn),那么當(dāng)然是給我們demo中的watch函數(shù)打個(gè)斷點(diǎn)。

首先執(zhí)行yarn dev將我們的demo跑起來,然后在瀏覽器的network面板中找到對(duì)應(yīng)的vue文件,右鍵點(diǎn)擊Open in Sources panel就可以在source面板中打開我們的代碼啦。如下圖

然后給watch函數(shù)打個(gè)斷點(diǎn),如下圖:

接著刷新頁(yè)面,此時(shí)代碼將會(huì)停留在斷點(diǎn)出。將斷點(diǎn)走進(jìn)watch函數(shù),代碼如下:

function watch(source, cb, options) {
  return doWatch(source, cb, options);
}

從上面的代碼可以看到在watch函數(shù)中直接返回了doWatch函數(shù)。

將斷點(diǎn)走進(jìn)doWatch函數(shù),在我們這個(gè)場(chǎng)景中簡(jiǎn)化后的代碼如下(為了方便大家理解,本文中會(huì)將scheduler任務(wù)調(diào)度相關(guān)的代碼移除掉,因?yàn)檫@個(gè)不影響watch的主流程):

function doWatch(source, cb, options = EMPTY_OBJ) {
  const baseWatchOptions = extend({}, options);
  const watchHandle = baseWatch(source, cb, baseWatchOptions);
  return watchHandle;
}

從上面的代碼可以看到底層實(shí)際是在執(zhí)行baseWatch函數(shù),而這個(gè)baseWatch就是由@vue/reactivity包中導(dǎo)出的watch函數(shù)。關(guān)于這個(gè)baseWatch函數(shù)的由來可以看看歐陽(yáng)之前的文章: Vue3.5新增的baseWatch讓watch函數(shù)和Vue組件徹底分手

baseWatch函數(shù)

將斷點(diǎn)走進(jìn)baseWatch函數(shù),在我們這個(gè)場(chǎng)景中簡(jiǎn)化后的代碼如下:

const INITIAL_WATCHER_VALUE = {}

function watch(
  source: WatchSource | WatchSource[] | WatchEffect | object,
  cb?: WatchCallback | null,
  options: WatchOptions = EMPTY_OBJ
): WatchHandle {
  let effect: ReactiveEffect;
  let getter: () => any;

  if (isRef(source)) {
    getter = () => source.value;
  }

  let oldValue: any = INITIAL_WATCHER_VALUE;

  const job = () => {
    if (cb) {
      const newValue = effect.run();
      if (hasChanged(newValue, oldValue)) {
        const args = [
          newValue,
          oldValue === INITIAL_WATCHER_VALUE ? undefined : oldValue,
          boundCleanup,
        ];

        cb(...args);
        oldValue = newValue;
      }
    }
  };
  effect = new ReactiveEffect(getter);
  effect.scheduler = job;

  oldValue = effect.run();
}

首先定義了兩個(gè)變量effectgetter,effectReactiveEffect類的實(shí)例。

接著就是使用isRef(source)判斷watch監(jiān)聽的是不是一個(gè)ref變量,如果是就將getter函數(shù)賦值為getter = () => source.value。這么做的原因是為了保持一致(watch也可以直接監(jiān)聽一個(gè)getter函數(shù)),并且后面會(huì)對(duì)這個(gè)getter函數(shù)進(jìn)行讀操作觸發(fā)依賴收集。

我們知道watch的回調(diào)中有oldValuenewValue這兩個(gè)字段,在watch函數(shù)內(nèi)部有個(gè)字段也名為oldValue用于存舊的值。

接著就是定義了一個(gè)job函數(shù),我們先不看里面的代碼,執(zhí)行這個(gè)job函數(shù)就會(huì)執(zhí)行watch的回調(diào)。

然后執(zhí)行effect = new ReactiveEffect(getter),這個(gè)ReactiveEffect類是一個(gè)底層的類。在Vue的設(shè)計(jì)中,所有的訂閱者都是繼承的這個(gè)ReactiveEffect類。比如watchEffect、computed()、render函數(shù)等。

在我們這個(gè)場(chǎng)景中new ReactiveEffect時(shí)傳入的getter函數(shù)就是getter = () => source.value,這里的source就是watch監(jiān)聽的響應(yīng)式變量count。

接著將job函數(shù)賦值給effect.scheduler屬性,在ReactiveEffect類中依賴觸發(fā)時(shí)就會(huì)執(zhí)行effect.scheduler方法(接下來會(huì)講)。

最后就是執(zhí)行effect.run()拿到初始化時(shí)watch監(jiān)聽變量的值,這個(gè)run方法也是在ReactiveEffect類中。接下來也會(huì)講。

ReactiveEffect類

前面我們講過了ReactiveEffect是Vue的一個(gè)底層類,所有的訂閱者都是繼承的這個(gè)類。將斷點(diǎn)走進(jìn)ReactiveEffect類,在我們這個(gè)場(chǎng)景中簡(jiǎn)化后的代碼如下:

class ReactiveEffect<T = any> implements Subscriber, ReactiveEffectOptions {
  constructor(fn) {
    this.fn = fn;
  }

  run(): T {
    const prevEffect = activeSub;
    activeSub = this;
    try {
      return this.fn();
    } finally {
      activeSub = prevEffect;
    }
  }

  trigger(): void {
    this.scheduler();
  }
}

在new一個(gè)ReactiveEffect實(shí)例時(shí)傳入的getter函數(shù)會(huì)賦值給實(shí)例的fn方法。(實(shí)際的ReactiveEffect代碼比這個(gè)要復(fù)雜很多,感興趣的同學(xué)可以去看源代碼)

我們回到前面講過的baseWatch函數(shù)中的最后一塊:oldValue = effect.run()。這里執(zhí)行了effect實(shí)例的run方法拿到watch監(jiān)聽變量的值,并且賦值給oldValue變量。

因?yàn)槲覀內(nèi)绻皇褂?code>immediate: true,那么Vue會(huì)等watch監(jiān)聽的變量改變后才會(huì)觸發(fā)watch回調(diào),回調(diào)中有個(gè)字段叫oldValue,這個(gè)oldValue就是初始化時(shí)執(zhí)行run方法拿到的。

比如我們這里count初始化的值是0,初始化執(zhí)行oldValue = effect.run()后就會(huì)給oldValue賦值為0。當(dāng)點(diǎn)擊count++按鈕后,count的值就變成了1,所以在watch回調(diào)第一次觸發(fā)的時(shí)候他就知道oldValue的值是0啦。

除此之外,在run方法中還有收集依賴的作用。Vue維護(hù)了一個(gè)全局變量activeSub表示當(dāng)前active的訂閱者是誰(shuí),在同一時(shí)間只可能有一個(gè)active的訂閱者,不然觸發(fā)get攔截進(jìn)行依賴收集時(shí)就不知道該把哪個(gè)訂閱者給收集了。

run方法中將當(dāng)前的activeSub給存起來,等下面的代碼執(zhí)行完了后將全局變量activeSub改回去。

接著就是執(zhí)行activeSub = this;將當(dāng)前的watch設(shè)置為全局變量activeSub

接下來就是執(zhí)行return this.fn(),前面我們講過了這個(gè)this.fn()方法就是watch監(jiān)聽的getter函數(shù)。由于我們watch監(jiān)聽的是一個(gè)響應(yīng)式變量count,在前面處理后他的getter函數(shù)就是getter = () => source.value;。這里的source就是watch監(jiān)聽的變量,這個(gè)getter函數(shù)實(shí)際就是getter = () => count.value;

那么這里執(zhí)行return this.fn()就是執(zhí)行() => count.value,將會(huì)觸發(fā)響應(yīng)式變量count的get攔截。在get攔截中會(huì)進(jìn)行依賴收集,由于此時(shí)的全局變量activeSub已經(jīng)變成了訂閱者watch,所以響應(yīng)式變量count在依賴收集的過程中收集的訂閱者就是watch。這樣響應(yīng)式變量count就和訂閱者watch建立了依賴收集的關(guān)系。關(guān)于Vue3.5依賴收集和依賴觸發(fā)可以看看歐陽(yáng)之前的文章: 看不懂來打我!讓性能提升56%的Vue3.5響應(yīng)式重構(gòu)

當(dāng)我們點(diǎn)擊count++后會(huì)修改響應(yīng)式變量count的值,就會(huì)進(jìn)行依賴觸發(fā),經(jīng)過一堆操作后最后就會(huì)執(zhí)行到這里的trigger方法中。在trigger方法中直接執(zhí)行this.scheduler(),在前面已經(jīng)對(duì)scheduler方法進(jìn)行了賦值,回憶一下baseWatch函數(shù)的代碼。如下:

const INITIAL_WATCHER_VALUE = {}

function watch(
  source: WatchSource | WatchSource[] | WatchEffect | object,
  cb?: WatchCallback | null,
  options: WatchOptions = EMPTY_OBJ
): WatchHandle {
  let effect: ReactiveEffect;
  let getter: () => any;

  if (isRef(source)) {
    getter = () => source.value;
  }

  let oldValue: any = INITIAL_WATCHER_VALUE;

  const job = () => {
    if (cb) {
      const newValue = effect.run();
      if (hasChanged(newValue, oldValue)) {
        const args = [
          newValue,
          oldValue === INITIAL_WATCHER_VALUE ? undefined : oldValue,
          boundCleanup,
        ];

        cb(...args);
        oldValue = newValue;
      }
    }
  };
  effect = new ReactiveEffect(getter);
  effect.scheduler = job;

  oldValue = effect.run();
}

這里將job函數(shù)賦值給effect.scheduler方法,所以當(dāng)響應(yīng)式變量count的值改變后實(shí)際就是在執(zhí)行這里的job函數(shù)。

job函數(shù)中首先判斷是否有傳入watch的callback函數(shù),然后執(zhí)行const newValue = effect.run()。

執(zhí)行這行代碼有兩個(gè)作用:

第一個(gè)作用是重新執(zhí)行g(shù)etter函數(shù),也就是getter = () => count.value;,拿到最新count的值,將其賦值給newValue。

第二個(gè)作用是watch除了監(jiān)聽響應(yīng)式變量之外還可以監(jiān)聽一個(gè)getter函數(shù),那么在getter函數(shù)中就可以類似computed一樣在某些條件下監(jiān)聽變量A,某些條件下監(jiān)聽變量B。這里的第二個(gè)作用是重新收集依賴,因?yàn)榇藭r(shí)watch可能從監(jiān)聽變量A變成了監(jiān)聽變量B。

接著就是執(zhí)行if (hasChanged(newValue, oldValue))判斷watch監(jiān)聽的變量新的值和舊的值是否相等,如果不相等才去執(zhí)行cb(...args)觸發(fā)watch的回調(diào)。最后就是將當(dāng)前的newValue賦值給oldValue,下次觸發(fā)watch回調(diào)時(shí)作為oldValue字段。

總結(jié)

這篇文章講了watch如何對(duì)響應(yīng)式變量進(jìn)行監(jiān)聽,其實(shí)底層依賴的是@vue/reactivity包的baseWatch函數(shù)。在baseWatch函數(shù)中會(huì)使用ReactiveEffect類new一個(gè)effect實(shí)例,這個(gè)ReactiveEffect類是一個(gè)底層的類,Vue的訂閱者都是基于這個(gè)類去實(shí)現(xiàn)的。

如果沒有使用immediate: true,初始化時(shí)會(huì)去執(zhí)行一次effect.run()對(duì)watch監(jiān)聽的響應(yīng)式變量進(jìn)行讀操作并且將其賦值給oldValue。讀操作會(huì)觸發(fā)get攔截進(jìn)行響應(yīng)式變量的依賴收集,會(huì)將當(dāng)前watch作為訂閱者進(jìn)行收集。

當(dāng)響應(yīng)式變量的值改變后會(huì)觸發(fā)set攔截,進(jìn)而依賴觸發(fā)。前一步將watch也作為訂閱者進(jìn)行了收集,依賴觸發(fā)時(shí)也會(huì)通知到watch,所以此時(shí)會(huì)執(zhí)行watch中的job函數(shù)。在job函數(shù)中會(huì)再次執(zhí)行effect.run()拿到響應(yīng)式變量最新的值賦值給newValue,同時(shí)再次進(jìn)行依賴收集。如果oldValuenewValue不相等,那么就觸發(fā)watch的回調(diào),并且將oldValuenewValue作為參數(shù)傳過去。

到此這篇關(guān)于一文詳解Vue3的watch是如何實(shí)現(xiàn)監(jiān)聽的的文章就介紹到這了,更多相關(guān)Vue3 watch實(shí)現(xiàn)監(jiān)聽內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Vue實(shí)現(xiàn)無限級(jí)樹形選擇器

    Vue實(shí)現(xiàn)無限級(jí)樹形選擇器

    這篇文章主要介紹了Vue實(shí)現(xiàn)無限級(jí)樹形選擇器,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的朋友可以參考一下
    2022-09-09
  • Vue.js實(shí)現(xiàn)按鈕的動(dòng)態(tài)綁定效果及實(shí)現(xiàn)代碼

    Vue.js實(shí)現(xiàn)按鈕的動(dòng)態(tài)綁定效果及實(shí)現(xiàn)代碼

    本文通過實(shí)例代碼給大家介紹了Vue.js實(shí)現(xiàn)按鈕的動(dòng)態(tài)綁定效果,代碼簡(jiǎn)單易懂,非常不錯(cuò),具有參考借鑒價(jià)值,需要的的朋友參考下吧
    2017-08-08
  • vue3-pinia-ts項(xiàng)目中的使用示例詳解

    vue3-pinia-ts項(xiàng)目中的使用示例詳解

    這篇文章主要介紹了vue3-pinia-ts項(xiàng)目中的使用,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-08-08
  • 如何利用vite快速搭建vue3項(xiàng)目

    如何利用vite快速搭建vue3項(xiàng)目

    這篇文章主要介紹了如何利用vite快速搭建vue3項(xiàng)目問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-03-03
  • vue3中emit('update:modelValue')使用簡(jiǎn)單示例

    vue3中emit('update:modelValue')使用簡(jiǎn)單示例

    這篇文章主要給大家介紹了關(guān)于vue3中emit('update:modelValue')使用的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2022-09-09
  • Nginx部署前端vue項(xiàng)目的全部步驟記錄

    Nginx部署前端vue項(xiàng)目的全部步驟記錄

    本文講解了如何在Linux環(huán)境中部署Vue項(xiàng)目,包括安裝依賴、編譯項(xiàng)目、安裝配置Nginx,并介紹了SSL證書安裝與配置,詳細(xì)說明了使用Nginx作為靜態(tài)資源服務(wù)器和反向代理的步驟,以及進(jìn)行性能、安全性測(cè)試和備份的重要性,需要的朋友可以參考下
    2024-09-09
  • 富文本編輯器vue2-editor實(shí)現(xiàn)全屏功能

    富文本編輯器vue2-editor實(shí)現(xiàn)全屏功能

    這篇文章主要介紹了富文本編輯器vue2-editor實(shí)現(xiàn)全屏功能,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值 ,需要的朋友可以參考下
    2019-05-05
  • vue?指令與過濾器案例代碼

    vue?指令與過濾器案例代碼

    這篇文章主要介紹了vue?指令與過濾器,本文通過案例代碼給大家詳細(xì)講解,給大家講解的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-11-11
  • 關(guān)于vue.js組件數(shù)據(jù)流的問題

    關(guān)于vue.js組件數(shù)據(jù)流的問題

    本篇文章主要介紹了關(guān)于vue.js組件數(shù)據(jù)流的問題,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-07-07
  • Vue2中無法監(jiān)聽數(shù)組和對(duì)象的某些變化問題

    Vue2中無法監(jiān)聽數(shù)組和對(duì)象的某些變化問題

    這篇文章主要介紹了Vue2中無法監(jiān)聽數(shù)組和對(duì)象的某些變化問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-08-08

最新評(píng)論