當前位置:
首頁 > 最新 > Vue的數據依賴實現原理簡析

Vue的數據依賴實現原理簡析

首先讓我們從最簡單的一個實例 入手:

const app = new Vue({ // options 傳入一個選項obj.這個obj即對於這個vue實例的初始化 })

通過查閱文檔,我們可以知道這個 可以接受:

選項/數據

data

props

propsData(方便測試使用)

computed

methods

watch

選項 / DOM

選項 / 生命周期鉤子

選項 / 資源

選項 / 雜項

具體未展開的內容請自行查閱相關文檔,接下來讓我們來看看傳入的 是如何管理數據之間的相互依賴的。

const app = new Vue({ el: #app , props: { a: { type: Object, default () { return { key1: a , key2: { a: b } } } } }, data: { msg1: Hello world! , arr: { arr1: 1 } }, watch: { a (newVal, oldVal) { console.log(newVal, oldVal) } }, methods: { go () { console.log( This is simple demo ) } } })

我們使用 這個構造函數去實例化了一個 實例 。傳入了 , , , 等屬性。在實例化的過程中, 提供的構造函數就使用我們傳入的 去完成數據的依賴管理,初始化的過程只有一次,但是在你自己的程序當中,數據的依賴管理的次數不止一次。

那 的構造函數到底是怎麼實現的呢?Vue

// 構造函數 function Vue (options) { if (process.env.NODE_ENV !== production && !(this instanceof Vue)) { warn( Vue is a constructor and should be called with the `new` keyword ) } this._init(options) } // 對Vue這個class進行mixin,即在原型上添加方法 // Vue.prototype.* = function () {} initMixin(Vue) stateMixin(Vue) eventsMixin(Vue) lifecycleMixin(Vue) renderMixin(Vue)

當我們調用 的時候,事實上就調用的 原型上的 方法.

// 原型上提供_init方法,新建一個vue實例並傳入options參數 Vue.prototype._init = function (options?: Object) { const vm: Component = this // a uid vm._uid = uid++ let startTag, endTag // a flag to avoid this being observed vm._isVue = true // merge options if (options && options._isComponent) { // optimize internal component instantiation // since dynamic options merging is pretty slow, and none of the // internal component options needs special treatment. initInternalComponent(vm, options) } else { // 將傳入的這些options選項掛載到vm.$options屬性上 vm.$options = mergeOptions( // components/filter/directive resolveConstructorOptions(vm.constructor), // this._init()傳入的options options || {}, vm ) } /* istanbul ignore else */ if (process.env.NODE_ENV !== production ) { initProxy(vm) } else { vm._renderProxy = vm } // expose real self vm._self = vm // 自身的實例 // 接下來所有的操作都是在這個實例上添加方法 initLifecycle(vm) // lifecycle初始化 initEvents(vm) // events初始化 vm._events, 主要是提供vm實例上的$on/$emit/$off/$off等方法 initRender(vm) // 初始化渲染函數,在vm上綁定$createElement方法 callHook(vm, beforeCreate ) // 鉤子函數的執行, beforeCreate initInjections(vm) // resolve injections before data/props initState(vm) // Observe data添加對data的監聽, 將data轉化為getters/setters initProvide(vm) // resolve provide after data/props callHook(vm, created ) // 鉤子函數的執行, created // vm掛載的根元素 if (vm.$options.el) { vm.$mount(vm.$options.el) } }

其中在 方法中調用 ,完成對 這個實例的數據的監聽,也是本文所要展開說的具體內容。

export function initState (vm: Component) { // 首先在vm上初始化一個_watchers數組,緩存這個vm上的所有watcher vm._watchers = [] // 獲取options,包括在new Vue傳入的,同時還包括了Vue所繼承的options const opts = vm.$options // 初始化props屬性 if (opts.props) initProps(vm, opts.props) // 初始化methods屬性 if (opts.methods) initMethods(vm, opts.methods) // 初始化data屬性 if (opts.data) { initData(vm) } else { observe(vm._data = {}, true /* asRootData */) } // 初始化computed屬性 if (opts.computed) initComputed(vm, opts.computed) // 初始化watch屬性 if (opts.watch) initWatch(vm, opts.watch) }initProps

我們在實例化 的時候,在構造函數裡面傳入的 中有 屬性:

props: { a: { type: Object, default () { return { key1: a , key2: { a: b } } } } }function initProps (vm: Component, propsOptions: Object) { // propsData主要是為了方便測試使用 const propsData = vm.$options.propsData || {} // 新建vm._props對象,可以通過app實例去訪問 const props = vm._props = {} // cache prop keys so that future props updates can iterate using Array // instead of dynamic object key enumeration. // 緩存的prop key const keys = vm.$options._propKeys = [] const isRoot = !vm.$parent // root instance props should be converted observerState.shouldConvert = isRoot for (const key in propsOptions) { // this._init傳入的options中的props屬性 keys.push(key) // 注意這個validateProp方法,不僅完成了prop屬性類型驗證的,同時將prop的值都轉化為了getter/setter,並返回一個observer const value = validateProp(key, propsOptions, propsData, vm) // 將這個key對應的值轉化為getter/setter defineReactive(props, key, value) // static props are already proxied on the component s prototype // during Vue.extend(). We only need to proxy props defined at // instantiation here. // 如果在vm這個實例上沒有key屬性,那麼就通過proxy轉化為proxyGetter/proxySetter, 並掛載到vm實例上,可以通過app._props[key]這種形式去訪問 if (!(key in vm)) { proxy(vm, `_props`, key) } } observerState.shouldConvert = true }

接下來看下 方法內部到底發生了什麼。

export function validateProp ( key: string, propOptions: Object, // $options.props屬性 propsData: Object, // $options.propsData屬性 vm?: Component ): any { const prop = propOptions[key] // 如果在propsData測試props上沒有緩存的key const absent = !hasOwn(propsData, key) let value = propsData[key] // 處理boolean類型的數據 // handle boolean props if (isType(Boolean, prop.type)) { if (absent && !hasOwn(prop, default )) { value = false } else if (!isType(String, prop.type) && (value === || value === hyphenate(key))) { value = true } } // check default value if (value === undefined) { // default屬性值,是基本類型還是function // getPropsDefaultValue見下面第一段代碼 value = getPropDefaultValue(vm, prop, key) // since the default value is a fresh copy, // make sure to observe it. const prevShouldConvert = observerState.shouldConvert observerState.shouldConvert = true // 將value的所有屬性轉化為getter/setter形式 // 並添加value的依賴 // observe方法的分析見下面第二段代碼 observe(value) observerState.shouldConvert = prevShouldConvert } if (process.env.NODE_ENV !== production ) { assertProp(prop, key, value, vm, absent) } return value }// 獲取prop的默認值 function getPropDefaultValue (vm: ?Component, prop: PropOptions, key: string): any { // no default, return undefined // 如果沒有default屬性的話,那麼就返回undefined if (!hasOwn(prop, default )) { return undefined } const def = prop.default // the raw prop value was also undefined from previous render, // return previous default value to avoid unnecessary watcher trigger if (vm && vm.$options.propsData && vm.$options.propsData[key] === undefined && vm._props[key] !== undefined) { return vm._props[key] } // call factory function for non-Function types // a value is Function if its prototype is function even across different execution context // 如果是function 則調用def.call(vm) // 否則就返回default屬性對應的值 return typeof def === function && getType(prop.type) !== Function ? def.call(vm) : def }

提供了一個 方法,在其內部實例化了一個 類,並返回 的實例。每一個 實例對應記錄了 中這個的 的所有依賴(僅限 類型),這個 實際上就是一個觀察者,它維護了一個數組 用以收集相關的 (即這個觀察者的依賴)。通過將 轉化為 形式,同時添加一個自定義 屬性,這個屬性就對應 實例。

說起來有點繞,還是讓我們看看我們給的 里傳入的 配置:

props: { a: { type: Object, default () { return { key1: a , key2: { a: b } } } } }

在往上數的第二段代碼裡面的方法 ,即對 進行依賴的管理,同時將這個 所有的屬性值都轉化為 形式。此外, 還會將 屬性都代理到 實例上,通過 , 就可以訪問到這個屬性。

此外,還需要了解下在 中管理依賴的一個非常重要的類:

export default class Dep { constructor () { this.id = uid++ this.subs = [] } addSub () {...} // 添加訂閱者(依賴) removeSub () {...} // 刪除訂閱者(依賴) depend () {...} // 檢查當前Dep.target是否存在以及判斷這個watcher已經被添加到了相應的依賴當中,如果沒有則添加訂閱者(依賴),如果已經被添加了那麼就不做處理 notify () {...} // 通知訂閱者(依賴)更新 }

在 的整個生命周期當中,你所定義的響應式的數據上都會綁定一個 實例去管理其依賴。它實際上就是 和 聯繫的一個橋樑。

剛才談到了對於依賴的管理,它的核心之一就是觀察者 這個類:

export class Observer { value: any; dep: Dep; vmCount: number; // number of vms that has this object as root $data constructor (value: any) { this.value = value // dep記錄了和這個value值的相關依賴 this.dep = new Dep() this.vmCount = 0 // value其實就是vm._data, 即在vm._data上添加__ob__屬性 def(value, __ob__ , this) // 如果是數組 if (Array.isArray(value)) { // 首先判斷是否能使用__proto__屬性 const augment = hasProto ? protoAugment : copyAugment augment(value, arrayMethods, arrayKeys) // 遍曆數組,並將obj類型的屬性改為getter/setter實現 this.observeArray(value) } else { // 遍歷obj上的屬性,將每個屬性改為getter/setter實現 this.walk(value) } } /** * Walk through each property and convert them into * getter/setters. This method should only be called when * value type is Object. */ // 將每個property對應的屬性都轉化為getter/setters,只能是當這個value的類型為Object時 walk (obj: Object) { const keys = Object.keys(obj) for (let i = 0; i

方法裡面調用 方法:通過遍歷這個 的 ,並將對應的 轉化為 形式,通過閉包維護一個 ,在 方法當中定義了這個 是如何進行依賴的收集,在 方法中定義了當這個 對應的值改變後,如何完成相關依賴數據的更新。但是從源碼當中,我們卻發現當 函數被調用的時候並非就一定會完成依賴的收集,其中還有一層判斷,就是 是否存在。

/** * Define a reactive property on an Object. */ export function defineReactive ( obj: Object, key: string, val: any, customSetter?: Function ) { // 每個屬性新建一個dep實例,管理這個屬性的依賴 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 // 遞歸去將val轉化為getter/setter // childOb將子屬性也轉化為Observer let childOb = observe(val) Object.defineProperty(obj, key, { enumerable: true, configurable: true, // 定義getter -->> reactiveGetter get: function reactiveGetter () { const value = getter ? getter.call(obj) : val // 定義相應的依賴 if (Dep.target) { // Dep.target.addDep(this) // 即添加watch函數 // dep.depend()及調用了dep.addSub()只不過中間需要判斷是否這個id的dep已經被包含在內了 dep.depend() // childOb也添加依賴 if (childOb) { childOb.dep.depend() } if (Array.isArray(value)) { dependArray(value) } } return value }, // 定義setter -->> reactiveSetter set: function reactiveSetter (newVal) { const value = getter ? getter.call(obj) : val /* eslint-disable no-self-compare */ if (newVal === value || (newVal !== newVal && value !== value)) { return } if (setter) { setter.call(obj, newVal) } else { val = newVal } // 對得到的新值進行observe childOb = observe(newVal) // 相應的依賴進行更新 dep.notify() } }) }

在上文中提到了 類是鏈接 和 的橋樑。同時在 的實現當中還有一個非常重要的屬性就是 ,它事實就上就是一個訂閱者,只有當 (訂閱者)存在的時候,調用屬性的 函數的時候才能完成依賴的收集工作。

Dep.target = null const targetStack = [] export function pushTarget (_target: Watcher) { if (Dep.target) targetStack.push(Dep.target) Dep.target = _target } export function popTarget () { Dep.target = targetStack.pop() }

那麼 是如何來實現 的呢? 裡面定義了一個類: ,在 的整個生命周期當中,會有4類地方會實例化 :

實例化的過程中有 選項

實例化的過程中有 計算屬性選項

原型上有掛載 方法: Vue.prototype.$watch,可以直接通過實例調用 方法

生成了 函數,更新視圖時

constructor ( vm: Component, expOrFn: string | Function, cb: Function, options?: Object ) { // 緩存這個實例vm this.vm = vm // vm實例中的_watchers中添加這個watcher vm._watchers.push(this) // options if (options) { this.deep = !!options.deep this.user = !!options.user this.lazy = !!options.lazy this.sync = !!options.sync } else { this.deep = this.user = this.lazy = this.sync = false } this.cb = cb this.id = ++uid // uid for batching this.active = true this.dirty = this.lazy // for lazy watchers .... // parse expression for getter if (typeof expOrFn === function ) { this.getter = expOrFn } else { this.getter = parsePath(expOrFn) if (!this.getter) { this.getter = function () {} } } // 通過get方法去獲取最新的值 // 如果lazy為true, 初始化的時候為undefined this.value = this.lazy ? undefined : this.get() } get () {...} addDep () {...} update () {...} run () {...} evaluate () {...} run () {...}

接收的參數當中 定義了用以獲取 的 函數。 可以有2種類型: 或 .若為 類型,首先會通過 方法去對 進行分割(僅支持 號形式的對象訪問)。在除了 選項外,其他幾種實例化 的方式都是在實例化過程中完成求值及依賴的收集工作: .在 的 方法中:

!!!前方高能get () { // pushTarget即設置當前的需要被執行的watcher pushTarget(this) let value const vm = this.vm if (this.user) { try { // $watch(function () {}) // 調用this.getter的時候,觸發了屬性的getter函數 // 在getter中進行了依賴的管理 value = this.getter.call(vm, vm) console.log(value) } catch (e) { handleError(e, vm, `getter for watcher "$"`) } } else { // 如果是新建模板函數,則會動態計算模板與data中綁定的變數,這個時候就調用了getter函數,那麼就完成了dep的收集 // 調用getter函數,則同時會調用函數內部的getter的函數,進行dep收集工作 value = this.getter.call(vm, vm) } // "touch" every property so they are all tracked as // dependencies for deep watching // 讓每個屬性都被作為dependencies而tracked, 這樣是為了deep watching if (this.deep) { traverse(value) } popTarget() this.cleanupDeps() return value }

一進入 方法,首先進行 的操作,此時 當中 ,接下來進行 操作,在這個操作中就完成了依賴的收集工作。還是拿文章一開始的 來說,在 實例化的時候傳入了 選項:

props: { a: { type: Object, default () { return { key1: a , key2: { a: b } } } } }, watch: { a (newVal, oldVal) { console.log(newVal, oldVal) } },

在 的 開始執行後,首先會初始化 的屬性為 函數,然後在進行 初始化的時候,這個時候初始化 實例,並調用 方法,設置 ,進而到 的操作。在調用 的方法中,便會訪問 選項中的 屬性即其 函數。在 屬性的 函數執行過程中,因為 已經存在,那麼就進入了 的過程:

if (Dep.target) { // Dep.target.addDep(this) // 即添加watch函數 // dep.depend()及調用了dep.addSub()只不過中間需要判斷是否這個id的dep已經被包含在內了 dep.depend() // childOb也添加依賴 if (childOb) { childOb.dep.depend() } if (Array.isArray(value)) { dependArray(value) } }

是一開始初始化的過程中,這個屬性上的 屬性。調用 函數:

depend () { if (Dep.target) { // Dep.target為一個watcher Dep.target.addDep(this) } }

也就剛才的那個 實例,這裡也就相當於調用了 實例的 方法: ,並將 觀察者傳入。在 方法中完成依賴收集:

addDep (dep: Dep) { const id = dep.id if (!this.newDepIds.has(id)) { this.newDepIds.add(id) this.newDeps.push(dep) if (!this.depIds.has(id)) { dep.addSub(this) } } }

這個時候依賴完成了收集,當你去修改 屬性的值時,會調用 屬性的 函數,裡面會執行 ,它會遍歷所有的訂閱者,然後調用訂閱者上的 函數。

過程和 類似,具體可參見源碼。

initComputed

以上就是在 過程中 是如何進行依賴收集的, 的過程和 類似,下來再來看看 的過程.

在 屬性初始化的過程當中,會為每個屬性實例化一個 :

const computedWatcherOptions = { lazy: true } function initComputed (vm: Component, computed: Object) { // 新建_computedWatchers屬性 const watchers = vm._computedWatchers = Object.create(null) for (const key in computed) { const userDef = computed[key] // 如果computed為funtion,即取這個function為getter函數 // 如果computed為非function.則可以單獨為這個屬性定義getter/setter屬性 let getter = typeof userDef === function ? userDef : userDef.get // create internal watcher for the computed property. // lazy屬性為true // 注意這個地方傳入的getter參數 // 實例化的過程當中不去完成依賴的收集工作 watchers[key] = new Watcher(vm, getter, noop, computedWatcherOptions) // component-defined computed properties are already defined on the // component prototype. We only need to define computed properties defined // at instantiation here. if (!(key in vm)) { defineComputed(vm, key, userDef) } } }

但是這個 在實例化的過程中,由於傳入了 的配置選項,那麼一開始是不會進行求值與依賴收集的: .在 的過程中, 會將 屬性定義到 實例上,同時將這個屬性定義為 。當你訪問 屬性的時候調用 函數:

function createComputedGetter (key) { return function computedGetter () { const watcher = this._computedWatchers && this._computedWatchers[key] if (watcher) { // 是否需要重新計算 if (watcher.dirty) { watcher.evaluate() } // 管理依賴 if (Dep.target) { watcher.depend() } return watcher.value } } }

在 存在的情況下,首先判斷 屬性,這個屬性主要是用於判斷這個 屬性是否需要重新求值,因為在上一輪的依賴收集的過程當中,觀察者已經將這個 添加到依賴數組當中了,如果觀察者發生了變化,就會 ,通知所有的 ,而對於 的 接收到變化的請求後,會將 即表明觀察者發生了變化,當再次調用 屬性的 函數的時候便會重新計算,否則還是使用之前緩存的值。

initWatch

的過程中其實就是實例化 完成觀察者的依賴收集的過程,在內部的實現當中是調用了原型上的 方法。這個方法也適用於 實例,即在 實例內部調用 方法去實例化 ,完成依賴的收集,同時監聽 的變化。

總結:

以上就是在 實例初始化的過程中實現依賴管理的分析。大致的總結下就是:

的過程中,將 , , 等屬性通過 來改造其 屬性,並為每一個響應式屬性實例化一個 觀察者。這個 內部 記錄了這個響應式屬性的所有依賴。

當響應式屬性調用 函數時,通過 方法去遍歷所有的依賴,調用 去完成數據的動態響應。

這篇文章主要從初始化的數據層面上分析了 是如何管理依賴來到達數據的動態響應。下一篇文章來分析下 中模板中的指令和響應式數據是如何關聯來實現由數據驅動視圖,以及數據是如何響應視圖變化的。

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 推酷 的精彩文章:

三個維度,解析產品的交互設計
Angular 中自定義 toJSON 操作符
InnoDB undo日誌與歷史系統基礎
為什麼我會選擇 React 而不是 Vue?–Steven Poulton–Medium

TAG:推酷 |

您可能感興趣

傳統媒體革新,Right項目簡析
Oracle資料庫壞塊簡析
Android進程&內存管理及內存泄露簡析
房租情況調查之數據簡析
簡析Python中的四種隊列
谷歌搜索排名僅次於 Supreme 的時尚品牌,為何是它?簡析 Fashion Nova 的發家史
不吹不黑,華為Mate RS保時捷設計簡析
Linux系統初始優化簡析
鑒古觀今!adidas T-Mac Millennium 實拍簡析
傳奇再現!Air Jordan 11 「Concord」 實拍簡析
假面騎士amazons角色簡析
還原真實色彩,OPPO Reno10倍變焦版炫彩模式樣張簡析
教你如何看懂Fate/EXTRA last Encore 「用奇葩對付奇葩」——簡析FE製作團隊
簡析摩拜與ofo小黃車策略差異
簡析鼬神的一生
XMR惡意挖礦案例簡析
四首有趣的數字的古詩詞簡析
實戰講標系列1:應標PPT內容量模式及簡析
IOT大數據大世界大未來,物聯網產業大數據應用簡析
內外兼修—adidas Dame DOLLA 實拍簡析