Vue 源碼閱讀十五 - 計算屬性和偵聽屬性

2021-01-08 尚學堂前端學院

computed

在初始化 Vue 實例階段會執行 initState 處理 data、props、computed、watcher 數據屬性,其中在 initComputed 是處理 computed 的方法,它的定義在 src/core/instance/state.js 中:

首先創建 vm._computedWatchers 為一個空對象,接著對 computed 對象做遍歷,拿到計算屬性的每一個 userDef,然後嘗試獲取這個 userDef 對應的 getter 函數。

接下來為每一個 getter 創建一個 watcher,這個 watcher 和渲染 watcher 有一點很大的不同,它是一個 computed watcher,因為 const computedWatcherOptions = { lazy: true }。

最後對判斷如果 key 不是 vm 的屬性,則調用 defineComputed(vm, key, userDef)。接下來我們看一下 defineComputed 的實現:

在 defineComputed 方法中會將每個計算屬性通過 Object.defineProperty 定義到 vm 實例上,重點關注這裡為每個計算屬性定義的 getter 和 setter 方法,平時計算屬性有 setter 的情況比較少,我們重點關注一下 getter 部分, getter 對應的是 createComputedGetter(key) 的返回值,來看一下它的定義:

可以看到將 computedGetter 作為屬性的 getter 方法,當訪問這個計算屬性時,會拿到 watcher.value 的值,即拿到在上面 initComputed 中 getter 方法的執行結果。

整個計算屬性的初始化過程到此結束。

watch

偵聽屬性的初始化也是發生在 Vue 的實例初始化階段的 initState 函數中,通過 initWatch 函數去處理:

首先對 watch 對象做遍歷,拿到每一個 handler,因為 Vue 是支持 watch 的同一個 key 對應多個 handler,所以如果 handler 是一個數組,則遍歷這個數組,調用 createWatcher 方法,否則直接調用 createWatcher:

這裡調用 vm.$watch(keyOrFn, handler, options) 函數,$watch 是 Vue 原型上的方法,它是在執行 stateMixin 的時候定義的:

也就是說,偵聽屬性 watch 最終會調用 $watch 方法,這個方法首先判斷 cb 如果是一個對象,則調用 createWatcher 方法,這是因為 $watch 方法是用戶可以直接調用的,它可以傳遞一個對象,也可以傳遞函數。

接著執行 const watcher = new Watcher(vm, expOrFn, cb, options) 實例化了一個 watcher,這裡需要注意一點這是一個 user watcher,因為 options.user = true。

通過實例化 watcher 的方式,一旦我們 watch 的數據發送變化,它最終會執行 watcher 的 run 方法,執行回調函數 cb,並且如果我們設置了 immediate 為 true,則直接會執行回調函數 cb。最後返回了一個 unwatchFn 方法,它會調用 teardown 方法去移除這個 watcher。

所以本質上偵聽屬性也是基於 Watcher 實現的,它是一個 user watcher。其實 Watcher 支持了不同的類型,下面我們梳理一下它有哪些類型以及它們的作用。

Watcher options

Watcher 的構造函數對 options 做的了處理,代碼如下:

所以 watcher 總共有 4 種類型,我們來一一分析它們,看看不同的類型執行的邏輯有哪些差別。

deep watcher

通常,如果我們想對一下對象做深度觀測的時候,需要設置這個屬性為 true,考慮到這種情況:

當我們修改 b 屬性時發現沒有任何列印,因為我們是 watch 了 a 對象,只觸發了 a 的 getter,並沒有觸發 a.b 的 getter,所以並沒有訂閱它的變化,當我們執行 vm.a.b = 2 賦值的時候,雖然觸發了 setter,但沒有可通知的對象,所以也並不會觸發 watch 的回調函數了。

而我們只需要對代碼做稍稍修改,就可以觀測到這個變化了:

這樣就創建了一個 deep watcher 了,在 watcher 執行 get 求值的過程中有一段邏輯:

在對 watch 的表達式或者函數求值後,會調用 traverse 函數,它的定義在 src/core/observer/traverse.js 中:

traverse 的邏輯也很簡單,它實際上就是對一個對象做深層遞歸遍歷,因為遍歷過程中就是對一個子對象的訪問,會觸發它們的 getter 過程,這樣就可以收集到依賴,也就是訂閱它們變化的 watcher,這個函數實現還有一個小的優化,遍歷過程中會把子響應式對象通過它們的 dep id 記錄到 seenObjects,避免以後重複訪問。

那麼在執行了 traverse 後,我們再對 watch 的對象內部任何一個值做修改,也會調用 watcher 的回調函數了。

user watcher

通過 vm.$watch 創建的 watcher 是一個 user watcher,而 user watcher 主要作用是在對 watcher 求值以及在執行回調函數的時候,當出現錯誤時,會調用 handleError 處理錯誤。

computed watcher

computed watcher 就是上面專為計算屬性而使用的 watcher。

sync watcher

當響應式數據發送變化後,觸發了 watcher.update(),只是把這個 watcher 推送到一個隊列中,在 nextTick 後才會真正執行 watcher 的回調函數。而一旦我們設置了 sync,就可以直接調用 run() 來同步執行 watcher 的回調函數。

只有當我們需要 watch 的值的變化到執行 watcher 的回調函數是一個同步過程的時候才會去設置該屬性為 true。

作者:明裡人 來源:掘金 著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。

相關焦點

  • Particle Effect for Vue
    演示地址Vue Particle Effect Buttons安裝教程將 particle-effect.vue 複製到你的項目目錄中,自行修改適配。通過修改hidden屬性的值,來啟動粒子動畫。比如用戶點擊了按鈕,程序把hidden綁定的變量設為true,按鈕便會在粒子動畫中慢慢消失。屬性參考類型:Boolean默認值:false說明:ParticleEffect組件會監控該屬性的變化,從而啟動相應的特效動畫。
  • 在vue項目中使用vuex實現狀態管理的案例
    並不需要使用vuex。vuex的使用與否在實際開發中應該視項目情況而定。準備工作:a.在vue項目中安裝vuex: npm install vuex -S。b.了解下vuex的五個屬性。vuex個屬性五個屬性的作用:state:用來存儲vuex的基本數據。
  • Vue.js 教程:構建一個特斯拉汽車餘電計算器
    行駛速度、氣溫和輪轂尺寸會對續航裡程有什麼影響?在本教程中,我們會使用 Vue.js 這個容易理解的 JavaScript 框架製作一個儀錶盤,通過它可以計算特斯拉電動汽車在不同情況下的行駛距離。cdworkshop-reactjs-vuejs/vuejs-app閱讀 README.md,了解我們要執行的任務。上圖是我們將要構建的應用程式的示例。我們先從一個有問題的應用程式開始入手,需要修復它的問題並做進一步的開發。在開始之前,首先解釋一下這個應用程式的結構。
  • vue高級進階系列——用typescript玩轉vue和vuex
    接下來,我不會過多介紹vuex的用法,而是介紹如何基於typescript,用class的方式來使用vue和vuex進行項目開發,相信使用過react的朋友們對class的寫法不會陌生,那就讓我們開始吧!為了省去一些配置上的麻煩,我們直接採用vue-cli3來搭建項目。在創建項目的時候選中typescript即可。
  • 什麼是VUE?VUE與JS的對比
    我們需要一個UI元素和屬性相互綁定的方法 2. 我們需要監視屬性和UI元素的變化 3.我們需要讓所有綁定的對象和元素都能感知到變化1.1.1. vue與js的對比1.1.1.1. js的實現(了解)<!
  • 5分鐘帶你入門vuex(vue狀態管理)
    ,然後我們在項目的src目錄下新建一個目錄store,在該目錄下新建一個index.js文件,我們用來創建vuex實例,然後在該文件中引入vue和vuex,創建Vuex.Store實例保存到變量store中,最後使用export default導出store:然後我們在main.js文件中引入該文件,在文件裡面添加 import store from 『.
  • 怪物獵人世界屬性傷害怎麼計算?屬性傷害計算公式介紹攻略
    怪物獵人世界是一款動作類手遊,相信不少玩家曾經玩過PSP版本的,那麼怪物獵人世界屬性傷害怎麼計算?今天小編就為大家帶來怪物獵人世界屬性傷害計算公式介紹攻略。
  • 供我們選擇的 Vue 組件庫還有很多!
    vue-movie:vuetify + vue 仿豆瓣電影項目。electron-vue-music:electron + vue + vuetify 仿網抑雲音樂。總結: 難道是我的錯覺嗎?國內的小夥伴們都非常愛國,這個組件庫在 Github 幾乎找不到什麼好的開源項目,不兼容 Edge 和 IE 瀏覽器讓它在我們國內可能不是很吃香。若是公司有對 IE 的支持需求,選它的時候要三思而後行。
  • 《槍火重生》源力球屬性如何計算 源力球屬性計算方法介紹
    導 讀 源力球是《槍火重生》中的一項物品,有很多小夥伴對於該物品的屬性方面還有著疑惑,不知道該物品的屬性到底是怎麼計算的
  • 什麼是MVVM,MVC和MVVM的區別,MVVM框架VUE實現原理
    前端的項目越來越大,項目的可維護性和擴展性、安全性等成了主要問題。當年為了解決瀏覽器兼容性問題,出現了很多類庫,其中最典型的就是jquery。但是這類庫沒有實現對業務邏輯的分成,所以維護性和擴展性極差。綜上兩方面原因,才有了MVVM模式一類框架的出現。比如vue,通過數據的雙向綁定,極大了提高了開發效率。
  • 分析屬性攻擊和屬性白字改動帶來的影響
    在這些天看了幾位玩韓服的玩家(計算式等等)對此次改版的闡述及測試之後,我對於韓服這次模糊不清的改動,總算是有一個明確的定義,下面就為各位說一說。[武器與技能屬性攻擊種類的改動]首先要明確:此次改動是一個優化改動,所帶來的影響也是偏好的,不存在「削弱」或者「刪減」的概念。
  • vue 具名插槽詳解專題及常見問題 - CSDN
    插槽含義:就是引入子組件後,在插入子組件元素中添加信息或者標籤,使得子組件的指定位置插入信息或者標籤插槽有三種:默認插槽、具名插槽、作用域插槽,由於vue2.6.0後對插槽進行修改,但是兼容2.6.0前的版本,博文中只說明2.6.0後的插槽,vue3.0後面會去除2.60前的版本兼容  一、默認插槽<
  • PostStack/PAL屬性提取——一般屬性和復地震道統計
    一、本期主要屬性歸納二、一般屬性一般屬性是指的變換,這些變換時指在每一個輸出樣點是一些組合或者是示例以前或者是示例以後。疊後的計算包括以下四個一般屬性。1、積分法:積分法就是通過應用傅立葉變換對道進行積分。
  • ref vue 獲取文本專題及常見問題 - CSDN
    ref的官網介紹https://cn.vuejs.org/v2/api/#ref需求在普通的js操作中,一般都是直接操作dom元素,但是對於Vue.js框架來說,一般是不允許直接操作dom元素的。那麼其實Vue.js框架提供了ref獲取dom元素,以及組件引用。
  • slot vue 具名專題及常見問題 - CSDN
    vue的slot1、什麼是slotslot是插槽,插即是可以插入,槽就是坑,即是可以再代碼中插入如果子組件模板中不包含插口,那麼父組件的內容將會被丟棄。當子組件模板中有一個麼有屬性的插槽時,父組件傳入的整個內容片段,將插入到插槽所在的dom的位置,並替換掉插槽標籤本身。
  • Vue.js深入學習
    vue.jsv-cloak:解決網速慢閃爍問題 ,不會替換掉標籤裡面的內容v-text:會替換掉標籤裡面的內容:原樣輸出v-html:會解析標籤v-bind:綁定屬性的指令,縮寫:'red','font-weight':200}v-for=" count in 10"in 後面可以放 普通數組,數組對象,對象,數字,如果是數字,count 從1 開始key 屬性只能使用數字和字符串,在使用的時候必須用v-bind屬性綁定的形式指定唯一的字符串或數字類型 :key的值,v-if 每次會重新刪除或創建元素,
  • 如何寫一個vue組件專題及常見問題 - CSDN
    如何使用vue寫一個組件庫新建vue項目使用vue-cli初始化一個項目:vue init webpack VueComponentcd VueComponentnpm installnpm run dev以上就新建好了一個vue項目項目目錄首先,定義好目錄,經過觀察大多數的組件庫,基本是這樣的目錄
  • 如何在vue項目中使用sass並設置元素樣式
    那麼,如何在vue項目中使用sass?新建項目,選擇vue模板2、在項目中src/components文件夾下,新建vue文件,輸入文件名稱,點擊創建導入Element模塊以及樣式文件5、在已新建的vue文件,利用Element進行頁面布局,插入一個輸入框和一個按鈕,綁定按鈕點擊事件
  • React源碼之組件的實現與首次渲染
    源碼中的兩種數據結構 貫穿源碼,常見的兩種數據結構,有助於快速閱讀源碼。 ReactElement 結構如下: { $$typeof // ReactElement標識符 type // 組件 key ref props // 組件屬性和
  • 深入淺出 Vue 響應式原理!
    computed: { totalPriceWithTax() {returnthis.price * this.quantity * 1.03; } }, methods: { changePrice() {this.price = 10; } }})上例中當price 發生變化的時候,Vue就知道自己需要做三件事情:更新頁面上price的值計算表達式