Vue 原理解析概述 (4)

PY
8 min readFeb 16, 2019

--

Vue 響應式原理

Vue 是一個 MVVM 框架,雙向綁定資料與畫面,前面提到的都是關於 DOM 生成與渲染部分,這篇來講講渲染後的 DOM 如何變化。

<div id="app" @click="changeMsg">
{{ message }}
</div>
<script>
var app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
},
methods: {
changeMsg() {
this.message = 'Hello World!'
}
}
})
</script>

click 事件綁定method 觸發後修改數據直接更新畫面

  1. Object.defineProperty

Vue 利用這方法來對一個物件直接定義新的屬性,或修改這屬性,可以先來看看官方文檔如何解釋。

Object.defineProperty(obj, prop, descriptor)

obj 為要定義的物件對象,prop 是要定義的屬性或要修改的名稱,descriptor 是將被定義或修改的屬性描述符,而什麼是描述符,這裡有解釋,簡單來講就是為這些屬性增加一些限制或方法,所以 Vue 用這方法的核心就是用描述符裡的 get & set ,代表著對此對象取得與修改,一旦增加了這兩個描述符,就可以把物件變成響應式的物件。

2. proxy

Vue 的 proxy 作用可以讓 Vue 實例 (vm) 代理 props 與 data,所以我們才能通過 vm 來調用這兩個數據,/src/core/instance/state.js 中定義了 proxy 方法。

export function proxy (target: Object, sourceKey: string, key: string) 
{
sharedPropertyDefinition.get = function proxyGetter () {
return this[sourceKey][key] }
sharedPropertyDefinition.set = function proxySetter (val) { this[sourceKey][key] = val }
Object.defineProperty(target, key, sharedPropertyDefinition)
}

讓 vm._props.xxx 變成 vm.xxx

proxy(vm, `_props`, key)
proxy(vm, `_data`, key)

3. Observer

監測數據變化的方法,在 /src/core/observer/index.js 可找到 ,Observer 是一個類別,他給對象的屬性添加 getter & setter。

def(value, '__ob__', this)//對實例進行響應式

def 這方法可以在 /src/core/util/lang.js 可找到

export function def (obj: Object, key: string, val: any, enumerable?: boolean) {
Object.defineProperty(obj, key, {
value: val,
enumerable: !!enumerable,
writable: true,
configurable: true
})
}

回到 observer, 看到下面 defineReactive

export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
const dep = new Dep()

const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}

// cater for pre-defined getter/setters
const getter = property && property.get
const setter = property && property.set
if ((!getter || setter) && arguments.length === 2) {
val = obj[key]
}

let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
if (Dep.target) {
dep.depend()
if (childOb) {
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = !shallow && observe(newVal)
dep.notify()
}
})
}

defineReactive 讓 Dep 依賴收集內的 watcher 變成響應式的,在 /src/core/observer/dep.js 可以看到,Dep 這個類別對 Watcher 進行集中管理, 當數據改變了,Dep 會知道要通知 ( notify ) 哪個 Watcher 進行改變,然後來看看 Watcher,在 /src/core/observer/watcher.js 找到定義,Watcher 綁定了一個組件的 update 方法,所以只要 Watcher 被觸發, update 就被觸發進行 re-render。

來源 : https://segmentfault.com/a/1190000016208088

3. nextTick

把每個 tick 都當作一個任務,nextTick 顧名思義就是下一個準備要執行的任務,Vue 的 nextTick 可以視為在下次 re-render 後進行 callback function, /src/core/util/next-tick.js 可以找到定義,常常用於對 dom 進行操作後。

4. set

我們經常使用 Vue.set 來為新加入數據做響應式,而這個方法定義在 /src/core/observer/index.js 中,也是透過 observer -> watcher -> dep -> defineReactive ,在陣列的部分得使用 push、pop、slice等方法才能響應陣列,這裡有定義 src/core/observer/array.js,意思就是使用這些方法時也能re-render。

5. computed & watch

computed 跟 watch 是大家常用來響應數據變化的方法,我們從源碼來看看這兩者的不同,找到 /src/core/instance/state.js 看到了 initComputed 內,computedWatcher 對 computed 進行遍歷,並拿取數據的 getter 並為每個 getter 創建一個 watcher ,所以可以說 computed 有著自己的 computed watcher,re-render 的部分只有 computed 的數據而已。

在調用 computed 內的數據時,數據內的響應式對象也會同時 getter。

fullName: function () {
return this.firstName + ' ' + this.lastName
}

來看看 initWatch 吧,一樣 /src/core/instance/state.js 找到 watch 方法,先對 watch 裡面的數據做遍歷並使用 createWatcher 這方法來創建 watcher ,裡面塞入要執行的 function 後會調用 vm.$watch 並將要執行的 function 進行回調。

總結

以一張圖做總結

https://ustbhuangyi.github.io/vue-analysis/reactive/summary.html

核心流程就是 observer 負責將對象加入響應式,然後賦予對象一個 watcher 後,交給 dep 去做通知與管理。

--

--