深入Vue響應式原理

来源:https://www.cnblogs.com/yangjunhua/archive/2019/08/18/11374430.html
-Advertisement-
Play Games

深入Vue.js響應式原理 一、創建一個Vue應用 new Vue({ data() { return { name: 'yjh', }; }, router, store, render: h => h(App), }).$mount('#app'); 二、實例化一個Vue應用到底發生了什麼? v ...


深入Vue.js響應式原理

一、創建一個Vue應用

new Vue({
  data() {
    return {
      name: 'yjh',
    };
  },
  router,
  store,
  render: h => h(App),
}).$mount('#app');

 

二、實例化一個Vue應用到底發生了什麼?

  1. this._init()
  2. callHook(vm, 'beforeCreate')
  3. observe(vm._data)
vm._data = vm.$options.data()

proxy(vm, _data, key) 代理到vm上訪問

function proxy(vm, _data, key)() {
  Object.defineProperty(target, key, {
    get() {
      return vm._data.key
    },
    set(val) {
      vm._data.key = val
    }
  })
}
  1. callHook(vm, 'created')
  2. mountComponent(vm.$mount執行後執行mountComponent)
  3. callHook(vm, 'beforeMount')
  4. new Watcher(vm, updateComponent)
const updateComponent = () => {
  // 創建虛擬dom
  const vnode = vm._render()

  // 創建虛擬dom的過程等同於如下代碼行
  // const vnode = vm.$options.render.call(vm, vm.$createElement)

  // 更新$el
  vm._update(vnode)
}
  1. callHook(vm, 'mount')

在以上發生的行為當中,第3步與第7步兩者相輔相成;也是我們最需要關心的,弄清楚這兩者,vue響應式原理就基本掌握了

三、如何追蹤數據變化

我們都知道 數據發生變化視圖也隨之更新,那麼首先我們得知道如何監聽數據的變化

class Observer {
  constructor(value) {
    this.value = value
    this.walk(value)
  }

  walk(obj) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i])
    }
  }
}
function defineReactive(obj, key) {
  Object.defineProperty(obj, key, {
    get() {
      // 數據被訪問
      return obj.key
    },
    set(val) {
      if (val === obj.key) {
        return
      }
      // 數據更新了
      obj.key = val
    }
  })
}

四、定義一個發佈訂閱的Dep類

當我們在創建虛擬dom的過程中,也就是執行vm.$createElement方法,可能會在多個地方使用到同一個數據欄位(如:vm.name),即多個訂閱者訂閱了name的更新,因此在Vue中定義了一個發佈訂閱的Dep類

class Dep {
  constructor() {
    this.subs = []
  }

  addSub(sub) {
    this.subs.push(sub)
  }

  depend() {
    if (Dep.target) {
      this.addSub(Dep.target)
    }
  }

  notify() {
    this.subs.forEach(sub => sub.update())
  }

  removeSub(sub) {
    const i = this.subs.findIndex(sub)
    if (i > -1) {
      this.subs.splice(i, 1)
    }
  }
}

 

五、數據訂閱者

訂閱數據更新的到底是誰,我們先看看如下場景

<!-- 場景1 -->
<div>名字:{{ userInfo.name }},全名:{{ fullName }}</div>
export default {
  data() {
    return {
      userInfo: {
        name: 'junhua',
      },
    }
  },
  mounted() {
    // 場景2
    this.$watch('name', (newVal, val) => {
      // ...
    })
  },
  // 場景2
  watch: {
    name(newVal, val) {
      // ...
    }
  },
  computed() {
    // 場景3
    fullName() {
      return `yang${this.userInfo.name}`
    }
  }
}

 

從上面示例代碼看,訂閱數據更新的場景有:

  1. 模版插值 :new Watcher(vm, updateComponent)數據發生變化,更新組件
  2. vm.$watch : 監聽單個數據做一些邏輯操作
  3. computed使用場景:計算屬性

因此數據訂閱者包含一個參數expOrFn([Function|String]),數據更新後需要執行的callback,如下:

class Watcher {
  constructor(vm, expOrFn, cb) {
    this.vm = vm
      if (typeof expOrFn === 'function') {
      this.getter = expOrFn
    } else {
      this.getter = parsePath(expOrFn)
    }
    this.cb = cb || () => {}
    this.value = this.get()
  }

  get() {
    Dep.target = this
    const value = this.getter.call(this.vm, this.vm)
    Dep.target = undefined
    return value
  }

  update() {
    const val = this.value
    const newVal = this.get()
    this.cb.call(this.vm, newVal, val)
  }
}

 

六、最終的觀察者Observer

class Observer {
  constructor(value) {
    this.value = value
    this.walk(value)
  }

  walk(obj) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i],)
    }
  }
}

function defineReactive(obj, key) {
  const dep = new Dep()
  Object.defineProperty(obj, key, {
    get() {
      // 依賴收集,收集訂閱者Watcher實例
      dep.depend()
      // 數據被訪問
      return obj.key
    },
    set(val) {
      if (val === obj.key) {
        return
      }
      // 數據更新了
      obj.key = val
      // 通知訂閱者Watcher實例更新
      dep.notify()
    }
  })
}

 

七、總結

我們再來回顧下實例化Vue應用的最重要的兩點

observe(vm._data)
// vm.$mount()
const componentUpdateWatcher = new Watcher(vm, updateComponent)

updateComponent在更新渲染組件時,會訪問1或多個數據模版插值,當訪問數據時,將通過getter攔截器把componentUpdateWatcher作為訂閱者添加到多個依賴中,每當其中一個數據有更新,將執行setter函數,對應的依賴將會通知訂閱者componentUpdateWatcher執行update,即執行updateComponent;至此Vue數據響應式目的已達到,再來看官網的這張圖片就很好理解了

 

 

 

github地址   文章來源:博客園-楊君華,轉載請註明出處:楊君華

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 刷新許可權,將某些許可權從硬碟刷新到記憶體中(修改root密碼自帶隱式刷新許可權操作) mysql> flush privileges; Query OK, 0 rows affected (0.00 sec) mysql> 查看服務埠 mysql> show variables like 'port';... ...
  • MySQL管理——操作和查看資料庫的命令 摘要:本文主要學習了操作和查詢資料庫的常用命令。 查看資料庫的基本信息 查詢所有的資料庫 語法: 示例: 指定要使用的資料庫 語法: 示例: 查詢指定資料庫所有的表 語法: 示例: 查詢指定表的欄位 語法: 示例: 查詢指定表的索引 語法: 示例: ...
  • 概述 越來越多的企業選擇上雲,最基礎的雲服務就是IaaS(Infrastructure as a Service)服務,直觀理解就是虛擬主機,用戶不用再自建機房,自己購買伺服器,而是直接向雲廠商購買虛擬主機服務ECS(Elastic Compute Service),按時按量付費。對於資料庫而言,將 ...
  • 1.安裝 下載地址:https://dev.mysql.com/downloads/mysql/ 常見問題及解決辦法:https://blog.csdn.net/chen97_08/article/details/81484286 1.1.添加環境變數 將解壓後的mysql 5.7.21 winx6 ...
  • 需要安裝Navicat軟體 可以複製百度雲鏈接,若失效,請聯繫我,我會儘快回覆 將鏈接中的破解文件複製到軟體安裝的位置即完成破解 鏈接:https://pan.baidu.com/s/1sIkjsd3TXyNZF9vdhOAnUQ 提取碼:kq7t 複製這段內容後打開百度網盤手機App,操作更方便哦 ...
  • Drawer(抽屜組件)可以實現類似抽屜拉出和推入的效果,可以從側邊欄拉出導航面板。通常Drawer是和ListView組件組合使用的。 Drawer組件可以添加頭部效果,用DrawerHeader和UserAccountsDrawerHeader這兩個組件可以實現。 DrawerHeade... ...
  • Groovy語法,變數定義,方法聲明,集合操作,閉包,運算符,斷言等基礎知識。 ...
  • 博客園美化首頁隨筆同時一天發佈內容分開分開版塊展示 一.直接上js代碼 二.缺點 1.編輯欄我把他刪了 因為我用不到 要修改區域在 ...
一周排行
    -Advertisement-
    Play Games
  • .Net8.0 Blazor Hybird 桌面端 (WPF/Winform) 實測可以完整運行在 win7sp1/win10/win11. 如果用其他工具打包,還可以運行在mac/linux下, 傳送門BlazorHybrid 發佈為無依賴包方式 安裝 WebView2Runtime 1.57 M ...
  • 目錄前言PostgreSql安裝測試額外Nuget安裝Person.cs模擬運行Navicate連postgresql解決方案Garnet為什麼要選擇Garnet而不是RedisRedis不再開源Windows版的Redis是由微軟維護的Windows Redis版本老舊,後續可能不再更新Garne ...
  • C#TMS系統代碼-聯表報表學習 領導被裁了之後很快就有人上任了,幾乎是無縫銜接,很難讓我不想到這早就決定好了。我的職責沒有任何變化。感受下來這個系統封裝程度很高,我只要會調用方法就行。這個系統交付之後不會有太多問題,更多應該是做小需求,有大的開發任務應該也是第二期的事,嗯?怎麼感覺我變成運維了?而 ...
  • 我在隨筆《EAV模型(實體-屬性-值)的設計和低代碼的處理方案(1)》中介紹了一些基本的EAV模型設計知識和基於Winform場景下低代碼(或者說無代碼)的一些實現思路,在本篇隨筆中,我們來分析一下這種針對通用業務,且只需定義就能構建業務模塊存儲和界面的解決方案,其中的數據查詢處理的操作。 ...
  • 對某個遠程伺服器啟用和設置NTP服務(Windows系統) 打開註冊表 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\W32Time\TimeProviders\NtpServer 將 Enabled 的值設置為 1,這將啟用NTP伺服器功 ...
  • title: Django信號與擴展:深入理解與實踐 date: 2024/5/15 22:40:52 updated: 2024/5/15 22:40:52 categories: 後端開發 tags: Django 信號 松耦合 觀察者 擴展 安全 性能 第一部分:Django信號基礎 Djan ...
  • 使用xadmin2遇到的問題&解決 環境配置: 使用的模塊版本: 關聯的包 Django 3.2.15 mysqlclient 2.2.4 xadmin 2.0.1 django-crispy-forms >= 1.6.0 django-import-export >= 0.5.1 django-r ...
  • 今天我打算整點兒不一樣的內容,通過之前學習的TransformerMap和LazyMap鏈,想搞點不一樣的,所以我關註了另外一條鏈DefaultedMap鏈,主要調用鏈為: 調用鏈詳細描述: ObjectInputStream.readObject() DefaultedMap.readObject ...
  • 後端應用級開發者該如何擁抱 AI GC?就是在這樣的一個大的浪潮下,我們的傳統的應用級開發者。我們該如何選擇職業或者是如何去快速轉型,跟上這樣的一個行業的一個浪潮? 0 AI金字塔模型 越往上它的整個難度就是職業機會也好,或者說是整個的這個運作也好,它的難度會越大,然後越往下機會就會越多,所以這是一 ...
  • @Autowired是Spring框架提供的註解,@Resource是Java EE 5規範提供的註解。 @Autowired預設按照類型自動裝配,而@Resource預設按照名稱自動裝配。 @Autowired支持@Qualifier註解來指定裝配哪一個具有相同類型的bean,而@Resourc... ...