摘要:就是用于把變化放入觀察,并通知其變化更新。這邊數據雙向綁定差不多就結束了。下一章節通過數據綁定原理結合來實現數據驅動更新的。
在Vue中我們經常修改數據,然后視圖就直接修改了,那么這些究竟是怎么實現的呢?
其實Vue使用了E5的語法Object.defineProperty來實現的數據驅動。
那么Object.defineProperty究竟是怎么實現的呢?
我們先來看一下一個簡單的demo
{{test}}
在vue這段小代碼中,this.test === this._data.test其實是等價的。這是為什么呢
其實就是通過Object.defineProperty做的一個小代理實現的。
原理如下:
var obj = { _data: { x: 123, y: 467 } } function proxy (target, sourceKey, key) { Object.defineProperty(target, key, { enumerable: true, configurable: true, get: function() { return this[sourceKey][key] }, set: function(val) { this[sourceKey][key] = val } }) } proxy(obj, "_data", "x") proxy(obj, "_data", "y") console.log(obj.x) // 123 console.log(obj.y) // 467 console.log(obj._data.x) // 123 console.log(obj._data.y) // 467
以上一個demo就是對obj對象的一個代理,通過訪問obj.x直接代理到obj._data.x。 Object.defineProperty具體用法可以自行搜索。
那么其實數據雙向綁定也是根據Object.defineProperty里面的get和set來實現的,通過set的時候去做一些視圖更新的操作。
接下來我們就來看一下Vue源碼吧。
雙向數據綁定,將分為以下3個部分:
1. Observer。這個模塊是用于監聽對象上的所有屬性,即使用Object.defineProperty來實現get和set 2. Watcher。 這個模塊是觀察者,當監聽的數據值被修改時,執行對象的回調函數。 3. Dep。 連接Observe和Watcher的橋梁,每個Observer對應一個Dep,內部維護一個數組,保存與該Observer相關的WatcherObserver
首先來看下oberser。
路徑: src/core/observer/index.js
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 this.dep = new Dep() this.vmCount = 0 // 添加__ob__來標示value有對應的Observer 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/setters walk (obj: Object) { const keys = Object.keys(obj) for (let i = 0; i < keys.length; i++) { defineReactive(obj, keys[i]) } } // 觀察數組的每一項 observeArray (items: Array) { for (let i = 0, l = items.length; i < l; i++) { observe(items[i]) } } }
所以從源碼可以看出數據類型主要分為2種,一種是數組,一種是對象。對應的處理方法分別樹observe和defineReactive
那我們再來看看這2個函數做了些什么呢?
這2個函數也是在這個文件內部
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) // 這邊作出get和set的動態響應的處理 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() } }) }
// 這個方法具體是為對象的key添加get,set方法;如果用戶傳入自己本身傳入get和set方法也會保留其方法。它會為每一個值都創建一個Dep,在get函數中 dep.depend做了2件事,一是向Dep.target的內部添加dep,二是將dep。target添加到dep內部的subs數組中,也就是建立關系。
在set函數中,如果新值和舊值相同則不處理,如果不同,則通知更新。
接下來看observe
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 ( shouldObserve && !isServerRendering() && (Array.isArray(value) || isPlainObject(value)) && Object.isExtensible(value) && !value._isVue ) { ob = new Observer(value) } if (asRootData && ob) { ob.vmCount++ } return ob }
對數組中的每一項進行檢測,該方法用于觀察一個對象,如果不是對象則直接返回,如果是對象則返回該對象Observer對象。
但是這樣緊緊對數組中的每一項的對象進行了觀察,如果數組本身的長度修改那么又如何觸發呢。Vue專門對數組做了特出的處理。
回過頭來看Observer的類中有這么一段,在處理數組的時候,處理了這些代碼
const augment = hasProto ? protoAugment : copyAugment augment(value, arrayMethods, arrayKeys) this.observeArray(value)
我們來細看這些函數,見如下
function protoAugment (target, src: Object, keys: any) { /* eslint-disable no-proto */ target.__proto__ = src /* eslint-enable no-proto */ } function copyAugment (target: Object, src: Object, keys: Array) { for (let i = 0, l = keys.length; i < l; i++) { const key = keys[i] def(target, key, src[key]) } } const arrayProto = Array.prototype export const arrayMethods = Object.create(arrayProto) const methodsToPatch = [ "push", "pop", "shift", "unshift", "splice", "sort", "reverse" ] /** * Intercept mutating methods and emit events */ methodsToPatch.forEach(function (method) { // cache original method const original = arrayProto[method] def(arrayMethods, method, function mutator (...args) { const result = original.apply(this, args) const ob = this.__ob__ let inserted switch (method) { case "push": case "unshift": inserted = args break case "splice": inserted = args.slice(2) break } if (inserted) ob.observeArray(inserted) // notify change ob.dep.notify() return result }) })
我們可以看到protoAugment很簡單,就是執行了一段value._proto_ = arrayMethods
copyAugment中循環把arrayMethods上的arrayKeys方法添加到value上。
arrayMethods又是重寫了數組的操作方法["push","pop","shift","unshift","splice","sort","reverse"]。
通過調用數組這些方法的時候,通知dep.notify。 至此Observer部分已經結束
Dep相對就簡單點。
源碼路徑:src/core/observer/dep.js
export default class Dep { static target: ?Watcher; id: number; subs: Array; constructor () { this.id = uid++ this.subs = [] } // 添加觀察者 addSub (sub: Watcher) { this.subs.push(sub) } // 刪除觀察者 removeSub (sub: Watcher) { remove(this.subs, sub) } // 調用watcher depend () { if (Dep.target) { Dep.target.addDep(this) } } notify () { // stabilize the subscriber list first const subs = this.subs.slice() for (let i = 0, l = subs.length; i < l; i++) { subs[i].update() } } }
內部有個唯一的id標識,還有一個保存watcher的數組subs。
Watcherlet uid = 0 export default class Watcher { constructor ( vm: Component, expOrFn: string | Function, cb: Function, options?: Object ) { this.vm = vm vm._watchers.push(this) ... this.cb = cb this.id = ++uid ... this.expression = process.env.NODE_ENV !== "production" ? expOrFn.toString() : "" if (typeof expOrFn === "function") { this.getter = expOrFn } else { this.getter = parsePath(expOrFn) if (!this.getter) { this.getter = function () {} } } this.value = this.get() } get () { pushTarget(this) let value const vm = this.vm ... value = this.getter.call(vm, vm) ... popTarget() this.cleanupDeps() return value } ... update () { ... queueWatcher(this) } 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) } } } } ... }
Watcher就是用于把變化放入觀察,并通知其變化更新。
queueWatcher就是把變化者放入數組queue,然后通過nextTick去更換新數組queue中的變化。
在生命周期掛載元素時,就會通過創建Watcher,然后來更新創新模塊。
vm._watcher = new Watcher(vm, updateComponent, noop)
這邊數據雙向綁定差不多就結束了。最后再附上一張簡要的流程圖來進一步清晰自己的思路。
下一章節通過數據綁定原理結合jquery來實現數據驅動更新的demo。之所以采用jquery操作dom是因為現在Vue源碼還沒到解析html模板那一步。
所以一步步來。等之后學完模板解析后。再去制作一個MVVM的簡易demo。
如果對您有幫助,請點個贊,謝謝!
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/99085.html
摘要:執行的時候,會綁定上下文對象為組件實例于是中的就能取到組件實例本身,的代碼塊頂層作用域就綁定為了組件實例于是內部變量的訪問,就會首先訪問到組件實例上。其中的獲取,就會先從組件實例上獲取,相當于。 寫文章不容易,點個贊唄兄弟專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內部詳情,讓我們一起學習吧研究基于 Vue版本 【2.5.17】 如果你覺得...
摘要:雙向數據綁定的核心和基礎是其內部真正參與數據雙向綁定流程的主要有和基于和發布者訂閱者模式,最終實現數據的雙向綁定。在這里把雙向數據綁定分為兩個流程收集依賴流程依賴收集會經過以上流程,最終數組中存放列表,數組中存放列表。 Vue雙向數據綁定的核心和基礎api是Object.defineProperty,其內部真正參與數據雙向綁定流程的主要有Obderver、Dep和Watcher,基于d...
摘要:儲存訂閱器因為屬性被監聽,這一步會執行監聽器里的方法這一步我們把也給弄了出來,到這一步我們已經實現了一個簡單的雙向綁定了,我們可以嘗試把兩者結合起來看下效果。總結本文主要是對雙向綁定原理的學習與實現。 當今前端天下以 Angular、React、vue 三足鼎立的局面,你不選擇一個陣營基本上無法立足于前端,甚至是兩個或者三個陣營都要選擇,大勢所趨。 所以我們要時刻保持好奇心,擁抱變化,...
摘要:項目地址和的區別其實和最大的區別就是多了一個虛擬,其他的區別都是很小的。 項目地址 Vue1和Vue2的區別 其實Vue1和Vue2最大的區別就是Vue2多了一個虛擬DOM,其他的區別都是很小的。所以理解了Vue1的源碼,就相當于理解了Vue2,中間差了一個虛擬DOM的Diff算法 文檔 數據雙向綁定 Vue主流程走向 組件 nextTick異步更新 MVVM 先來科普一下MVVM...
閱讀 958·2022-06-21 15:13
閱讀 1848·2021-10-20 13:48
閱讀 1027·2021-09-22 15:47
閱讀 1365·2019-08-30 15:55
閱讀 3112·2019-08-30 15:53
閱讀 519·2019-08-29 12:33
閱讀 711·2019-08-28 18:15
閱讀 3458·2019-08-26 13:58