摘要:通知某屬性改變,遍歷所有的訂閱者,就是實例,然后調用實例的方法些方法最核心的部分就是通過調用給的每個屬性添加和方法。
vue生命周期圖示:
Vue.js最顯著的功能就是響應式系統,它是一個典型的MVVM框架,Model只是普通的Javascript對象,修改它則View會自動更新,這種設計讓狀態管理變得非常簡單而直觀。
如何追蹤變化?我們先看一個簡單的例子:
count:{{times}}
運行后,我們可以從頁面中看到,count后面的times每隔一秒遞增1,視圖一直在更新,在代碼中僅僅是通過setInterval方法每隔一秒來修改vm.times的值,并沒有任何dom操作,那么vue是怎么實現這個過程呢,我們通過一張圖來看下:
圖中的Model就是data方法返回的{times:1},View是最終在瀏覽器中顯示的DOM,模型通過Observer,Dep,Watcher,Directive等一系列對象的關聯,最終和視圖建立起關系。總的來說,vue在些做了3件事:
通過Observer對data做監聽,并提供了訂閱某個數據項變化的能力。
把template編譯成一段document fragment,然后解析其中的Directive,得到每一個Directive所依賴的數據項和update方法。
通過Watcher把上述2部分結合起來,即把Directive中的數據依賴通過Watcher訂閱在對應數據的Observer的Dep上,當數據變化時,就會觸發Observer的Dep上的notify方法通知對應的Watcher的update,進而觸發Directive的update方法來更新dom視圖,最后達到模型和視圖關聯起來。
Observer我們來看下vue是如何給data對象添加Observer的,我們知道,vue的實例創建的過程會有一個生命周期,其中有一個過程就是調用vm.initData方法處理data選項,代碼如下:
src/core/instance/state.js function initData (vm: Component) { let data = vm.$options.data data = vm._data = typeof data === "function" ? getData(data, vm) : data || {} if (!isPlainObject(data)) { data = {} process.env.NODE_ENV !== "production" && warn( "data functions should return an object: " + "https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function", vm ) } // proxy data on instance const keys = Object.keys(data) const props = vm.$options.props const methods = vm.$options.methods let i = keys.length while (i--) { const key = keys[i] if (process.env.NODE_ENV !== "production") { if (methods && hasOwn(methods, key)) { warn( `Method "${key}" has already been defined as a data property.`, vm ) } } if (props && hasOwn(props, key)) { process.env.NODE_ENV !== "production" && warn( `The data property "${key}" is already declared as a prop. ` + `Use prop default value instead.`, vm ) } else if (!isReserved(key)) { proxy(vm, `_data`, key) } } // observe data observe(data, true /* asRootData */) }
我們要注意下proxy方法,它的功能是遍歷data的key,把data上的屬性代理到vm實例上,proxy源碼如下:
const sharedPropertyDefinition = { enumerable: true, configurable: true, get: noop, set: noop } 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) }
方法主要通過Object.defineProperty的getter和setter方法實現了代理,在 前面的例子中,我們調用vm.times就相當于訪問了vm._data.times.
在initData的最后,我們調用了observer(data,this)來對data做監聽,observer的源碼定義如下:
src/core/observer/index.js /** *嘗試創建一個值的觀察者實例, *如果成功觀察到新的觀察者, *或現有的觀察者,如果該值已經有一個。 */ export function observe (value: any, asRootData: ?boolean): Observer | void { if (!isObject(value) || value instanceof VNode) { return } let ob: Observer | void if (hasOwn(value, "__ob__") && value.__ob__ instanceof Observer) { ob = value.__ob__ } else if ( observerState.shouldConvert && !isServerRendering() && (Array.isArray(value) || isPlainObject(value)) && Object.isExtensible(value) && !value._isVue ) { ob = new Observer(value) } if (asRootData && ob) { ob.vmCount++ } return ob }
它會首先判斷value是否已經添加了_ob_屬性,它是一個Observer對象的實例,如果是就直接用,否則在value滿足一些條件(數組或對象,可擴展,非vue組件等)的情況下創建一個Observer對象,下面看下Observer這個類的源碼:
class Observer { value: any; dep: Dep; vmCount: number; // 將這個對象作為根$data的vm的數量。 constructor (value: any) { this.value = value this.dep = new Dep() this.vmCount = 0 def(value, "__ob__", this) if (Array.isArray(value)) { const augment = hasProto ? protoAugment : copyAugment augment(value, arrayMethods, arrayKeys) this.observeArray(value) } else { this.walk(value) } } /** *遍歷每個屬性并將其轉換為擁有getter / setter。這個方法應該只在什么時候調用。當obj是對象。 */ walk (obj: Object) { const keys = Object.keys(obj) for (let i = 0; i < keys.length; i++) { defineReactive(obj, keys[i], obj[keys[i]]) } } /** 觀察數組項的列表。 */ observeArray (items: Array) { for (let i = 0, l = items.length; i < l; i++) { observe(items[i]) } } }
這個構造函數主要做了這么幾件事,首先創建了一個Dep對象實例,然后把自身this添加到value的_ob_屬性上,最后對value的類型進行判斷,如果是數組則觀察數組,否則觀察單個元素。obsersverArray方法就是對數組進行遍歷,遞歸調用observer方法,最終都會調用walk方法觀察單個元素。walk方法就是對obj的key進行遍歷。然后調用了defineReactive,把要觀察的data對象的每個屬性都賦予getter和setter方法,這樣一旦屬性被訪問或者更新,我們就可以追蹤到這些變化。源碼如下:
/** * 在對象上定義一個反應性屬性 setter,getter。 */ 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 } // 滿足預定義的getter / setter const getter = property && property.get const setter = property && property.set let childOb = !shallow && observe(val) // 在這里添加setter,getter。 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) // 通知data某屬性改變,遍歷所有的訂閱者,就是watcher實例,然后調用watcher實例的update方法 dep.notify() } }) }
些方法最核心的部分就是通過調用Object.defineProperty給data的每個屬性添加getter和setter方法。當data的某個屬性被訪問時,則會調用getter方法,判斷當Dep.target不為空時調用dep.depend和childOb.dep.depend方法做依賴收集,如果訪問的屬性是一個數組則會遍歷這個數組收集數組元素的依賴,當改變data的屬性時,則會調用setter方法,這時調用dep.notify方法進行通知。其中用到的Dep類是一個簡單的觀察者模式的實現,然后我們看下源碼:
src/core/observer/dep.js class Dep { static target: ?Watcher; id: number; subs: Arraywatcher; constructor () { this.id = uid++ this.subs = [] // 用來存儲所有訂閱它的watcher } addSub (sub: Watcher) { this.subs.push(sub) } removeSub (sub: Watcher) { remove(this.subs, sub) } depend () { if (Dep.target) { // Dep.target表示當前正在計算的watcher,是全局唯一的,同一時間只能有一個watcher被計算 // 把當前Dep的實例添加到當前正在計算的watcher依賴中 Dep.target.addDep(this) } } // 遍歷所有的的訂閱watcher,調用它們的update方法。 notify () { // 首先穩定用戶列表。 const subs = this.subs.slice() for (let i = 0, l = subs.length; i < l; i++) { subs[i].update() } } }
src/core/observer/watcher.js let uid = 0 /** * 觀察者解析表達式,收集依賴項, *當表達式值發生變化時觸發回調。 這用于$watch() api和指令。 */ export default class Watcher { constructor ( vm: Component, expOrFn: string | Function, cb: Function, options?: Object ) { this.vm = vm 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 為批處理 this.active = true this.dirty = this.lazy // for lazy watchers this.deps = [] this.newDeps = [] this.depIds = new Set() this.newDepIds = new Set() this.expression = process.env.NODE_ENV !== "production" ? expOrFn.toString() : "" // parse expression for getter if (typeof expOrFn === "function") { this.getter = expOrFn } else { this.getter = parsePath(expOrFn) if (!this.getter) { this.getter = function () {} process.env.NODE_ENV !== "production" && warn( `Failed watching path: "${expOrFn}" ` + "Watcher only accepts simple dot-delimited paths. " + "For full control, use a function instead.", vm ) } } this.value = this.lazy ? undefined : this.get() } /** * 評估getter并重新收集依賴項。 */ get () { pushTarget(this) let value const vm = this.vm try { value = this.getter.call(vm, vm) } catch (e) { if (this.user) { handleError(e, vm, `getter for watcher "${this.expression}"`) } else { throw e } } finally { // “觸摸”每個屬性,所以它們都被跟蹤。 // 對深度觀察的依賴。 if (this.deep) { traverse(value) } popTarget() this.cleanupDeps() } return value } /** * Add a dependency to this directive.把dep添加到watcher實例的依賴中,同時通過 dep.addsup(this)把watcher實例添加到dep的訂閱者中。 */ 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) } } } /** * Clean up for dependency collection. */ cleanupDeps () { let i = this.deps.length while (i--) { const dep = this.deps[i] if (!this.newDepIds.has(dep.id)) { dep.removeSub(this) } } let tmp = this.depIds this.depIds = this.newDepIds this.newDepIds = tmp this.newDepIds.clear() tmp = this.deps this.deps = this.newDeps this.newDeps = tmp this.newDeps.length = 0 } /** *用戶界面。時將調用一個依賴的變化。 */ update () { /* istanbul ignore else */ if (this.lazy) { this.dirty = true } else if (this.sync) { this.run() } else { // 調用,把watcher實例推入隊列中,延遲this.run調用的時機。 queueWatcher(this) } } /** * 調度器的工作界面。會被調度器調用。 * 再次對watcher進行求值,重新收集依賴,接下來判斷求值結果和之前value的關系,如果不變,則什么也不做 * 此方法是directive實例創建watcher時傳入的,它對應相關指令的update方法來真實更新dom。這樣就完成了數據更新到對應視圖的變化過程。 * watcher把observer和directive關聯起來,實現了數據一旦更新,視圖就自動變化的效果。利用object.defineProperty實現了數據和視圖的綁定 */ run () { if (this.active) { const value = this.get() if ( value !== this.value || // 深入觀察和觀察對象/陣列甚至應該開火。當值相等時,因為值可以。有突變。 isObject(value) || this.deep ) { // set new value const oldValue = this.value this.value = value if (this.user) { try { this.cb.call(this.vm, value, oldValue) } catch (e) { handleError(e, this.vm, `callback for watcher "${this.expression}"`) } } else { this.cb.call(this.vm, value, oldValue) } } } } /** * 評估觀察者的價值。 這只會被稱為懶惰的觀察者。 */ evaluate () { this.value = this.get() // 對watcher進行求值,同時收集依賴 this.dirty = false // 不會再對watcher求值,也不會再訪問計算屬性的getter方法了 } /** * 要看這個觀察者收集的所有數據。 */ depend () { let i = this.deps.length while (i--) { this.deps[i].depend() } } /** * Remove self from all dependencies" subscriber list. */ teardown () { if (this.active) { // remove self from vm"s watcher list // this is a somewhat expensive operation so we skip it // if the vm is being destroyed. if (!this.vm._isBeingDestroyed) { remove(this.vm._watchers, this) } let i = this.deps.length while (i--) { this.deps[i].removeSub(this) } this.active = false } } }
Dep實例在初始化watcher時,會傳入指令的expression.在前面的例子中expression是times.get方法的功能是對當前watcher進行求值,收集依賴關系,設置Dep.target為當前watcher的實例,this.getter.call(vm,vm),這個方法相當于獲取vm.times,這樣就觸發了對象的getter.我們之前給data添加Observer時,通過上面defineReactive/Object.defineProperty給data對象的每一個屬性添加getter和setter了.
src/core/observer/index.js function defineReactive (obj,key,val,customSetter,shallow) { // 在這里添加setter,getter。 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 } }
當獲取vm.times時,會執行到get方法體內,由于我們在之前已經設置了Dep.target為當前Watcher實例,所以接下來就調用dep.depend()完成收集,它實際上是執行了Dep.target.addDep(this),相當于執行了Watcher實例的addDep方法,把Dep添加到Watcher實例的依賴中。
src/observer/watcher.js 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) } } }
addDep是把dep添加到Watcher實例的依賴中,同時又通過dep.addSup(this)把Watcher實例添加到dep的訂閱者中
src/observer/dep.js addSub (sub: Watcher) { this.subs.push(sub) }
至此,指令完成了依賴收集,并且通過Watcher完成了對數據變化的訂閱。
我們再看下,當data發生變化時,視圖是如何自動更新的,在前面的例子中,我們setInterval每隔一秒執行一次vm.times++,數據改變會觸發對象的setter,執行set方法體的代碼。
src/core/observer/index.js function defineReactive (obj,key,val,customSetter,shallow) { // 在這里添加setter,getter。 Object.defineProperty(obj, key, { enumerable: true, configurable: true, set: function reactiveSetter (newVal) { const value = getter ? getter.call(obj) : val if (newVal === value || (newVal !== newVal && value !== value)) { return } if (process.env.NODE_ENV !== "production" && customSetter) { customSetter() } if (setter) { setter.call(obj, newVal) } else { val = newVal } childOb = !shallow && observe(newVal) // 通知data某屬性改變,遍歷所有的訂閱者,就是watcher實例,然后調用watcher實例的update方法 dep.notify() } }
src/observer/watcher.js update () { /* istanbul ignore else */ if (this.lazy) { this.dirty = true } else if (this.sync) { this.run() } else { queueWatcher(this)// 調用,把watcher實例推入隊列中,延遲this.run調用的時機。 } }
src/core/observer/scheduler.js /** * 把一個觀察者watcher推入觀察者隊列。 *將跳過具有重復id的作業,除非它是。 *當隊列被刷新時被推。 * 通過nextTick在下一個事件循環周期處理watcher隊列,是一種優化手段。因為如果同時觀察的數據多次變化,比如同步執行3次vm.time++,同時調用就會觸發3次dom操作 * 而推入隊列中等待下一個事件循環周期再操作隊列里的watcher,因為是同一個watcher,它只會調用一次watcher.run,從而只觸發一次dom操作。 */ export function queueWatcher (watcher: Watcher) { const id = watcher.id if (has[id] == null) { has[id] = true if (!flushing) { queue.push(watcher) } else { // 如果已經刷新,則根據其id將監視器拼接起來。 // 如果已經超過了它的id,它將會立即運行。 let i = queue.length - 1 while (i > index && queue[i].id > watcher.id) { i-- } queue.splice(i + 1, 0, watcher) } // 隊列的沖 if (!waiting) { waiting = true nextTick(flushSchedulerQueue) } } }
function flushSchedulerQueue () { flushing = true let watcher, id // 在刷新前排序隊列。 // 這確保: // 1。組件由父元素更新為子元素。(因為父母總是在孩子面前創建) // 2。組件的用戶觀察者在它的呈現觀察者之前運行(因為用戶觀察者是在渲染觀察者之前創建的 // 3。如果組件在父組件的監視程序運行期間被銷毀, 它的觀察者可以跳過。 queue.sort((a, b) => a.id - b.id) // 不要緩存長度,因為可能會有更多的觀察者被推。 // 當我們運行現有的觀察者時。遍歷queue中watcher的run方法 for (index = 0; index < queue.length; index++) { watcher = queue[index] id = watcher.id has[id] = null watcher.run() // in dev build, check and stop circular updates. if (process.env.NODE_ENV !== "production" && has[id] != null) { circular[id] = (circular[id] || 0) + 1 if (circular[id] > MAX_UPDATE_COUNT) { warn( "You may have an infinite update loop " + ( watcher.user ? `in watcher with expression "${watcher.expression}"` : `in a component render function.` ), watcher.vm ) break } } } // keep copies of post queues before resetting state const activatedQueue = activatedChildren.slice() const updatedQueue = queue.slice() resetSchedulerState() // call component updated and activated hooks callActivatedHooks(activatedQueue) callUpdatedHooks(updatedQueue) // devtool hook /* istanbul ignore if */ if (devtools && config.devtools) { devtools.emit("flush") } }
遍歷queue中Watcher的run方法,
src/core/observer/watcher.js run () { if (this.active) { const value = this.get() if ( value !== this.value || // 深入觀察和觀察對象/陣列甚至應該開火。當值相等時,因為值可以。有突變。 isObject(value) || this.deep ) { // set new value const oldValue = this.value this.value = value if (this.user) { try { this.cb.call(this.vm, value, oldValue) } catch (e) { handleError(e, this.vm, `callback for watcher "${this.expression}"`) } } else { this.cb.call(this.vm, value, oldValue) } } } }
run方法再次對Watcher求值,重新收集依賴,接下來判斷求值結果和之前value的關系,如果不變則什么也不做,如果變了則調用this.cb.call(this.vm,value,oldValue)方法,這個方法是Directive實例創建watcher時傳入的,它對應相關指令的update方法來真實更新 DOM,這樣就完成了數據更新到對應視圖的變化過程。Watcher巧妙的把Observer和Directive關聯起來,實現了數據一旦更新,視圖就會自動變化的效果,vue利用了Object.defineProperty這個核心技術實現了數據和視圖的綁定。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/93030.html
摘要:問題為什么修改即可觸發更新和的關聯關系官方介紹的官網文檔,對響應式屬性的原理有一個介紹。因此本文在源碼層面,對響應式原理進行梳理,對關鍵步驟進行解析。 描述 ?我們通過一個簡單的 Vue應用 來演示 Vue的響應式屬性: html: {{message}} js: let vm = new Vue({ el: #ap...
摘要:所以我今后打算把每一個內容分成白話版和源碼版。有什么錯誤的地方,感謝大家能夠指出響應式系統我們都知道,只要在實例中聲明過的數據,那么這個數據就是響應式的。什么是響應式,也即是說,數據發生改變的時候,視圖會重新渲染,匹配更新為最新的值。 寫文章不容易,點個贊唄兄弟專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內部詳情,讓我們一起學習吧研究基于 V...
摘要:在讀取訪問器屬性時,就會調用函數,該函數負責返回有效的值在寫入訪問器屬性時,會調用函數并傳入新值,該函數負責決定如何處理數據,但是這兩個函數不一定非要同時存在。 前言 Vue最明顯的特性之一便是它的響應式系統,其數據模型即是普通的 JavaScript 對象。而當你讀取或寫入它們時,視圖便會進行響應操作。文章簡要闡述下其實現原理,如有錯誤,還請不吝指正。個人博客鏈接:hiybm.cn ...
寫文章不容易,點個贊唄兄弟專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內部詳情,讓我們一起學習吧研究基于 Vue版本 【2.5.17】 如果你覺得排版難看,請點擊 下面鏈接 或者 拉到 下面關注公眾號也可以吧 【Vue原理】Props - 源碼版 今天記錄 Props 源碼流程,哎,這東西,就算是研究過了,也真是會隨著時間慢慢忘記的。 幸好我做...
摘要:對象用戶看到的對象用戶看到的是這個對象即是實際使用的對象實際使用的對象復制更新相應的代碼實現對象代理響應式原理前提官網說過,限于現代瀏覽器限制,無法監測通過這種方式添加的屬性,所以,他的響應式是建立在實例化對象的時候,預定義屬性的基礎上的。 1. Vue 對象 1.1 用戶看到的對象 var app = new Vue({ el: #app , /* * 用...
摘要:原型方法通過原型方法方法來掛載實例。當響應式屬性發生變化時,會通知依賴列表中的對象進行更新。此時,對象執行方法,重新渲染節點。在執行過程中,如果需要讀取響應式屬性,則會觸發響應式屬性的。總結響應式屬性的原理 vue實例 初始化 完成以后,接下來就要進行 掛載。 vue實例掛載,即為將vue實例對應的 template模板,渲染成 Dom節點。 原型方法 - $mount ? 通過原...
閱讀 3454·2021-11-22 12:00
閱讀 672·2019-08-29 13:24
閱讀 2905·2019-08-29 11:31
閱讀 2587·2019-08-26 14:00
閱讀 3185·2019-08-26 11:42
閱讀 2476·2019-08-23 18:31
閱讀 799·2019-08-23 18:27
閱讀 2844·2019-08-23 16:58