国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

Vue 數據響應式原理

Mike617 / 2790人閱讀

摘要:接下來,我們就一起深入了解的數據響應式原理,搞清楚響應式的實現機制。回調函數只是打印出新的得到的新的值,由執行后生成。及異步更新相信讀過前文,你應該對響應式原理有基本的認識。

前言

Vue.js 的核心包括一套“響應式系統”。

“響應式”,是指當數據改變后,Vue 會通知到使用該數據的代碼。例如,視圖渲染中使用了數據,數據改變后,視圖也會自動更新。

舉個簡單的例子,對于模板:

{{ name }}

創建一個 Vue 組件:

var vm = new Vue({
  el: "#root",
  data: {
    name: "luobo"
  }
})

代碼執行后,頁面上對應位置會顯示:luobo。

如果想改變顯示的名字,只需要執行:

vm.name = "tang"

這樣頁面上就會顯示修改后的名字了,并不需要去手動修改 DOM 更新數據。

接下來,我們就一起深入了解 Vue 的數據響應式原理,搞清楚響應式的實現機制。

基本概念

Vue 的響應式,核心機制是 觀察者模式

數據是被觀察的一方,發生改變時,通知所有的觀察者,這樣觀察者可以做出響應,比如,重新渲染然后更新視圖。

我們把依賴數據的觀察者稱為 watcher,那么這種關系可以表示為:

data -> watcher

數據可以有多個觀察者,怎么記錄這種依賴關系呢?

Vue 通過在 data 和 watcher 間創建一個 dep 對象,來記錄這種依賴關系:

data - dep -> watcher

dep 的結構很簡單,除了唯一標識屬性 id,另一個屬性就是用于記錄所有觀察者的 subs:

id - number

subs - [Watcher]

再來看 watcher。

Vue 中 watcher 的觀察對象,確切來說是一個求值表達式,或者函數。這個表達式或者函數,在一個 Vue 實例的上下文中求值或執行。這個過程中,使用到數據,也就是 watcher 所依賴的數據。用于記錄依賴關系的屬性是 deps,對應的是由 dep 對象組成的數組,對應所有依賴的數據。而表達式或函數,最終會作為求值函數記錄到 getter 屬性,每次求值得到的結果記錄在 value 屬性:

vm - VueComponent

deps - [Dep]

getter - function

value - *

另外,還有一個重要的屬性 cb,記錄回調函數,當 getter 返回的值與當前 value 不同時被調用:

cb - function

我們通過示例來整理下 data、dep、watcher 的關系:

var vm = new Vue({
  data: {
    name: "luobo",
    age: 18
  }
})

var userInfo = function () {
  return this.name + " - " + this.age
}

var onUserInfoChange = function (userInfo) {
  console.log(userInfo)
}

vm.$watch(userInfo, onUserInfoChange)

上面代碼首先創建了一個新的 Vue 實例對象 vm,包含兩個數據字段:name、age。對于這兩個字段,Vue 會分別創建對應的 dep 對象,用于記錄依賴該數據的 watcher。

然后定義了一個求值函數 userInfo,注意,這個函數會在對應的 Vue 示例上下文中執行,也就是說,執行時的 this 對應的就是 vm。

回調函數 onUserInfoChange 只是打印出新的 watcher 得到的新的值,由 userInfo 執行后生成。

通過 vm.$watch(userInfo, onUserInfoChange),將 vm、getter、cb 集成在一起創建了新的 watcher。創建成功后,watcher 在內部已經記錄了依賴關系,watcher.deps 中記錄了 vm 的 name、age 對應的 dep 對象(因為 userInfo 中使用了這兩個數據)。

接下來,我們修改數據:

vm.name = "tang"

執行后,控制臺會輸出:

tang - 18

同樣,如果修改 age 的值,也會最終觸發 onUserInfoChange 打印出新的結果。

用個簡單的圖來整理下上面的關系:

vm.name -- dep1
vm.age  -- dep2
watcher.deps --> [dep1, dep2]

修改 vm.name 后,dep1 通知相關的 watcher,然后 watcher 執行 getter,得到新的 value,再將新的 value 傳給 cb:

vm.name -> dep1 -> watcher -> getter -> value -> cb

可能你也注意到了,上面例子中的 userInfo,貌似就是計算屬性的作用嘛:

var vm = new Vue({
  data: {
    name: "luobo",
    age: 18
  },
  computed: {
    userInfo() {
      return this.name + " - " + this.age
    }
  }
})
其實,計算屬性在內部也是基于 watcher 實現的,每個計算屬性對應一個 watcher,其 getter 也就是計算屬性的聲明函數。
不過,計算屬性對應的 watcher 與直接通過 vm.$watch() 創建的 watcher 略有不同,畢竟如果沒有地方使用到這個計算屬性,數據改變時都重新進行計算會有點浪費,這個在本文后面會講到。

上面描述了 data、dep、watcher 的關系,但是問題來了,這種依賴關系是如何建立的呢?數據改變后,又是如何通知 watcher 的呢?

接下來我們深入 Vue 源碼,搞清楚這兩個問題。

建立依賴關系
Vue 源碼版本 v2.5.13,文中摘錄的部分代碼為便于分析進行了簡化或改寫。

響應式的核心邏輯,都在 Vue 項目的 “vue/src/core/observer” 目錄下面。

我們還是先順著前面示例代碼來捋一遍,首先是 Vue 實例化過程:

var vm = new Vue(/* ... */)

跟將傳入的 data 進行響應式初始化相關的代碼,在 “vue/src/core/instance/state.js” 文件中:

observer/state.js#L149

// new Vue() -> ... -> initState() -> initData()
observe(data)

函數 observe() 的目的是讓傳入的整個對象成為響應式的,它會遍歷對象的所有屬性,然后執行:

observer/index.js#L64

// observe() -> new Observer() -> observer.walk()
defineReactive(obj, key, value)

defineReactive() 就是用于定義響應式數據的核心函數。它主要做的事情包括:

新建一個 dep 對象,與當前數據對應

通過 Object.defineProperty() 重新定義對象屬性,配置屬性的 set、get,從而數據被獲取、設置時可以執行 Vue 的代碼

OK,先到這里,關于 Vue 實例化告一段落。

需要要注意的是,傳入 Vue 的 data 的所有屬性,會被代理到新創建的 Vue 實例對象上,這樣通過 vm.name 進行操作的其實就是 data.name,這也是借助 Object.defineProperty() 實現的。

再來看 watcher 的創建過程:

vm.$watch(userInfo, onUserInfoChange)

上述代碼執行后,會調用:

instance/state.js#L346

// Vue.prototype.$watch()
new Watcher(vm, expOrFn, cb, options)

也就是:

new Watcher(vm, userInfo, onUserInfoChange, {/* 略 */})

在 watcher 對象創建過程中,除了記錄 vm、getter、cb 以及初始化各種屬性外,最重要的就是調用了傳入的 getter 函數:

observer/watcher.js#L103

// new Watcher() -> watcher.get()
value = this.getter.call(vm, vm)

在 getter 函數的執行過程中,獲取讀取需要的數據,于是觸發了前面通過 defineReactive() 配置的 get 方法:

if (Dep.target) {
  dep.depend()
}

這是做什么呢?

回到 watcher.get() 方法,在執行 getter 函數的前后,分別有如下代碼:

pushTarget(this)
// ... 
value = this.getter.call(vm, vm)
// ...
popTarget()

pushTarget() 將當前 watcher 設置為 Dep.target,這樣在執行到 vm.name 進一步執行對應的 get 方法時,Dep.target 的值就是這里的 watcher,然后通過 dep.depend() 就建立了依賴關系。

dep.depend() 執行的邏輯就比較好推測了,將 watcher(通過 Dep.target 引用到)記錄到 dep.subs 中,將 dep 記錄到 watcher.deps 中 —— 依賴關系建立了!

然后來看建立的依賴關系是如何使用的。

數據變更同步

繼續前面的例子,執行如下代碼時:

vm.name = "tang"

會觸發通過 defineReactive() 配置的 set 方法,如果數據改變,那么:

// defineReactive() -> set()
dep.notify()

通過 dep 對象來通知所有的依賴方法,于是 dep 遍歷內部的 subs 執行:

// dep.notify()
watcher.update()

這樣 watcher 就被通知到了,知道了數據改變,從而繼續后續的處理。這里先不展開。

到這里,基本就搞清楚響應式的基本機制了,整理一下:

通過 Object.defineProperty() 替換配置對象屬性的 set、get 方法,實現“攔截”

watcher 在執行 getter 函數時觸發數據的 get 方法,從而建立依賴關系

寫入數據時觸發 set 方法,從而借助 dep 發布通知,進而 watcher 進行更新

這樣再看 Vue 官方的圖就比較好理解了:

圖片來源:https://vuejs.org/v2/guide/re...
上圖中左側是以組件渲染(render)作為 getter 函數來演示響應式過程的,這其實就是 RenderWatcher 這種特殊類型 watcher 的作用機制,后面還會再講。
計算屬性

本文前面提到過計算屬性,在 Vue 中也是作為 watcher 進行處理的。計算屬性(ComputedWatcher)特殊的地方在于,它其實沒有 cb(空函數),只有 getter,并且它的值只在被使用時才計算并緩存。

什么意思呢?

首先,ComputedWatcher 在創建時,不會立即執行 getter(lazy 選項值為 false),這樣一開始 ComputedWatcher 并沒有和使用到的數據建立依賴關系。

計算屬性在被“get”時,首先執行預先定義的 ComputedGetter 函數,這里有一段特殊邏輯:

instance/state.js#L238

function computedGetter () {
  if (watcher.dirty) {
    watcher.evaluate()
  }
  if (Dep.target) {
    watcher.depend()
  }
  return watcher.value
}

首先判斷 watcher 是不是 dirty 狀態,什么意思呢?

計算屬性對應的 watcher 初始創建的時候,并沒有執行 getter,這個時候就會設置 dirty 為 true,這樣當前獲取計算屬性的值的時候,會執行 getter 得到 value,然后標記 dirty 為 false。這樣后續再獲取計算屬性的值,不需要再計算(執行 getter),直接就能返回緩存的 value。

另外,計算屬性的 watcher 在執行 watcher.evaluate() 是,進一步調用 watcher.get(),從而進行依賴收集。而依賴的數據在改變后,會通知計算屬性的 watcher,但是 watcher 只是標記自身為 dirty,而不計算。這樣的好處是可以減小開銷,只在有地方需要計算屬性的值時才執行計算。

如果依賴的數據發生變更,計算屬性只是標記 dirty 為 true,會不會有問題呢?

解決這個問題的是上面代碼的這一部分:

if (Dep.target) {
  watcher.depend()
}

也就是說,如果當前有在收集依賴的 watcher,那么當前計算屬性的 watcher 會間接地通過 watcher.depend() 將依賴關系“繼承”給這個 watcher(watcher.depend() 內部是對每個 watcher.deps 記錄的 dep 執行 dep.depend() 從而讓依賴數據與當前的 watcher 建立依賴關系)。

所以,依賴數據改變,依賴計算屬性的 watcher 會直接得到通知,再來獲取計算屬性的值的時候,計算屬性才進行計算求值。

所以,依賴計算屬性的 watcher 可以視為依賴 watcher 的 watcher。這樣的 watcher 在 Vue 中最常見不過,那就是 RenderWatcher。

RenderWatcher 及異步更新

相信讀過前文,你應該對 Vue 響應式原理有基本的認識。那么 Vue 是如何將其運用到視圖更新中的呢?答案就是這里要講的 RenderWatcher。

RenderWatcher 首先是 watcher,只不過和計算屬性對應的 ComputedWatcher 類似,它也有些特殊的行為。

RenderWatcher 的創建,在函數 mountComponent 中:

// Vue.prototype.$mount() -> mountComponent()
let updateComponent = () => {
  vm._update(vm._render(), hydrating)
}
new Watcher(vm, updateComponent, noop, null, true /* isRenderWatcher */)

核心代碼就在這里了。這個 watcher 就是 Vue 實例對象唯一的 RenderWatcher,在 watcher 構造函數中,會記錄到 vm._watcher 上(普通 watcher 只會記錄到 vm._watchers 數組中)。

這個 watcher 也會在創建的最后執行 watcher.get(),也就是執行 getter 收集依賴的過程。而在這里,getter 就是 updateComponent,也就是說,執行了渲染+更新 DOM!并且,這個過程中使用到的數據也被收集了依賴關系。

那么,理所當然地,在 render() 中使用到數據,發生改變,自然會通知到 RenderWatcher,從而最終更新視圖!

不過,這里會有個疑問:如果進行多次數據修改,那么豈不是要頻繁執行 DOM 更新?

這里就涉及到 RenderWatcher 的特殊功能了:異步更新

結合前面內容,我們知道數據更新后,依賴該數據的 watcher 會執行 watcher.update(),這個在前文中沒有展開,現在我們來看下這個方法:

observer/watcher.js#L161

if (this.lazy) {
  this.dirty = true
} else if (this.sync) {
  this.run()
} else {
  queueWatcher(this)
}

第一種情況,lazy 為 true,也就是計算屬性,上一節已經提到過,只是標記 dirty 為 true,并不立即計算,不再贅述。sync 為 true 的情況,這里也不管,不過看起來也很簡單,就是立即執行計算嘛。

最后的情況,就是這里 RenderWatcher 的場景,并不立即執行,也不是像計算屬性那樣標記為 dirty 就完了,而是放到了一個隊列中。

這個隊列是干什么的呢?

相關代碼在 observer/scheduler.js 中,簡單來說,就是實現了異步更新。

理解其實現,首先要對瀏覽器的事件循環(Event Loop)機制有一定了解。如果你對事件循環機制不是很了解,可以看下面這篇文章:

JavaScript 運行機制詳解:再談Event Loop - 阮一峰

事件循環機制其實有點復雜,但只有理解事件循環,才能對這里 Vue 異步更新的方案有深入的認識。

基于事件循環機制,RenderWatcher 將其 getter,也就是 updateComponent 函數異步執行,并且,多次觸發
RenderWatcher 的 update(),最終也只會執行一次 updateComponent,這樣也就解決了性能問題。

不過,隨之而來的新問題是,修改完數據,不能直接反應到 DOM 上,而是要等異步更新執行過后才可以,這也是為什么 Vue 提供了 nextTick() 接口,并且要求開發者將對 DOM 的操作放到 nextTick() 回調中執行的原因。

Vuex、Vue-Router

再來看 Vue 套裝中的 Vuex、Vue-Router,它們也是基于 Vue 的響應式機制實現功能。

先來看 Vuex,代碼版本 v3.0.1。

Vuex

在應用了 Vuex 的應用中,所有組件都可以通過 this.$store 來引用到全局的 store,并且在使用了 store 的數據后,還能在數據改變后得到同步,這其實就是響應式的應用了。

首先看 this.$store 的實現,這個其實是通過全局 mixin 實現,代碼在:

src/mixin.js#L26

this.$store = options.store || options.parent.$store

這樣在每個組件的 beforeCreate 時,會執行 $store 屬性的初始化。

而 store 數據的響應式處理,則是通過實例化一個 Vue 對象實現:

src/store.js#L251

// new Store() -> resetStoreVM()
store._vm = new Vue({
  data: {
    $$state: state
  },
  computed // 對應 store.getters
})

結合前文的介紹,這里就很好理解了。因為 state 以及處理為響應式數據,而 getters 也創建為計算屬性,所以對這些數據的使用,就建立依賴關系,從而可以響應數據改變了。

Vue-Router

Vue-Router 中,比較重要的數據是 $route,即當前的頁面路由數據,在路由改變的時候,需要替換展示不同組件(router-view 組件實現)。

vm.$route 實踐上是來自 Vue.prototype,但其對應的值,最終對應到的是 router.history.current

結合前面的分析,這里的 history.current 肯定得是響應式數據,所以,來找下對其進行初始化的地方,其實是在全局 mixin 的 beforeCreate 這里:

v2.8.1/src/install.js#L27

// beforeCreate
Vue.util.defineReactive(this, "_route", this._router.history.current)

這樣 this._route 就是響應式的了,那么如果頁面路由改變,又是如何修改這里的 _route 的呢?

答案在 VueRouter 的 init() 這里:

history.listen(route => {
  this.apps.forEach((app) => {
    app._route = route
  })
})

一個 router 對象可能和多個 vue 實例對象(這里叫作 app)關聯,每次路由改變會通知所有的實例對象。

再來看使用 vm.$route 的地方,也就是 VueRouter 的兩個組件:

兩個組件都是在 render() 中,與 $route 建立了依賴關系,根據 route 的值進行渲染。這里具體過程就不展開了,感興趣可以看下相關源碼(v2.8.1/src/components),原理方面在 RenderWatcher 一節已經介紹過。

實踐:watch-it

了解了以上這么多,也想自己試試,把 Vue 響應式相關的核心邏輯剝離出來,做一個單純的數據響應式的庫。由于只關注數據,所以在剝離過程中,將與 Vue 組件/實例對象相關的部分都移除了,包括 watcher.vm 也不再需要,這樣 watcher.getter 計算時不再指定上下文對象。

感興趣,想直接看代碼的,可以前往 luobotang/watch-it。

watch-it 只包括數據響應式相關的功能,暴露了4個接口:

defineReactive(obj, key, val):為對象配置一個響應式數據屬性

observe(obj):將一個數據對象配置為響應式,內部對所有的屬性執行 defineReactive

defineComputed(target, key, userDef):為對象配置一個計算屬性,內部創建了 watcher

watch(fn, cb, options):監聽求值函數中數據改變,變化時調用 cb,內部創建了 watcher

來看一個使用示例:

const { observe, watch } = require("@luobotang/watch-it")

const data = {
  name: "luobo",
  age: 18
}

observe(data)

const userInfo = function() {
  return data.name + " - " + data.age
}

watch(userInfo, (value) => console.log(value))

這樣,當數據修改時,通過會打印出新的 userInfo 的值。

去除虛擬 DOM,只通過響應式機制,我還構建了一個簡單的 Vue,并實現了一個 DEMO:

watch-it/example/

源碼在這里:

luobotang/watch-it/example/vue.js

總結

OK,以上就是有關 Vue 響應式原理的全部了,當然,只是我的理解和實踐。

在梳理和寫下這些內容的過程中,我收獲很多,也希望內容能夠對你有所幫助。

水平有限,錯漏難免,歡迎指出。

最后,感謝閱讀!

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/97431.html

相關文章

  • 淺究Vue響應原理

    摘要:在讀取訪問器屬性時,就會調用函數,該函數負責返回有效的值在寫入訪問器屬性時,會調用函數并傳入新值,該函數負責決定如何處理數據,但是這兩個函數不一定非要同時存在。 前言 Vue最明顯的特性之一便是它的響應式系統,其數據模型即是普通的 JavaScript 對象。而當你讀取或寫入它們時,視圖便會進行響應操作。文章簡要闡述下其實現原理,如有錯誤,還請不吝指正。個人博客鏈接:hiybm.cn ...

    Karuru 評論0 收藏0
  • Vue原理響應原理 - 白話版

    摘要:所以我今后打算把每一個內容分成白話版和源碼版。有什么錯誤的地方,感謝大家能夠指出響應式系統我們都知道,只要在實例中聲明過的數據,那么這個數據就是響應式的。什么是響應式,也即是說,數據發生改變的時候,視圖會重新渲染,匹配更新為最新的值。 寫文章不容易,點個贊唄兄弟專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內部詳情,讓我們一起學習吧研究基于 V...

    gggggggbong 評論0 收藏0
  • Vue原理】Props - 源碼版

    寫文章不容易,點個贊唄兄弟專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內部詳情,讓我們一起學習吧研究基于 Vue版本 【2.5.17】 如果你覺得排版難看,請點擊 下面鏈接 或者 拉到 下面關注公眾號也可以吧 【Vue原理】Props - 源碼版 今天記錄 Props 源碼流程,哎,這東西,就算是研究過了,也真是會隨著時間慢慢忘記的。 幸好我做...

    light 評論0 收藏0
  • vue - 響應原理梳理(一)

    摘要:問題為什么修改即可觸發更新和的關聯關系官方介紹的官網文檔,對響應式屬性的原理有一個介紹。因此本文在源碼層面,對響應式原理進行梳理,對關鍵步驟進行解析。 描述 ?我們通過一個簡單的 Vue應用 來演示 Vue的響應式屬性: html: {{message}} js: let vm = new Vue({ el: #ap...

    weknow619 評論0 收藏0
  • 淺析Vue響應原理(二)

    摘要:響應式原理之之前簡單介紹了和類的代碼和作用,現在來介紹一下類和。對于數組,響應式的實現稍有不同。不存在時,說明不是響應式數據,直接更新。如果對象是響應式的,確保刪除能觸發更新視圖。 Vue響應式原理之Observer 之前簡單介紹了Dep和Watcher類的代碼和作用,現在來介紹一下Observer類和set/get。在Vue實例后再添加響應式數據時需要借助Vue.set/vm.$se...

    rockswang 評論0 收藏0

發表評論

0條評論

Mike617

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<