摘要:響應式原理之之前簡單介紹了和類的代碼和作用,現在來介紹一下類和。對于數組,響應式的實現稍有不同。不存在時,說明不是響應式數據,直接更新。如果對象是響應式的,確保刪除能觸發更新視圖。
Vue響應式原理之Observer
之前簡單介紹了Dep和Watcher類的代碼和作用,現在來介紹一下Observer類和set/get。在Vue實例后再添加響應式數據時需要借助Vue.set/vm.$set方法,這兩個方法內部實際上調用了set方法。而Observer所做的就是將修改反映到視圖中。
Observerexport 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 def(value, "__ob__", this) if (Array.isArray(value)) { const augment = hasProto ? protoAugment : copyAugment augment(value, arrayMethods, arrayKeys) this.observeArray(value) } else { this.walk(value) } } 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]) } } }
Observer有三個屬性。value是響應式數據的值;dep是Dep實例,這個Dep實例用于Vue.set/vm.$set中通知依賴更新;vmCount表示把這個數據當成根data對象的實例數量,大于0時是實例化傳入的根data對象。
構造函數接受一個值,表示要觀察的值,這樣,在Observer實例中引用了響應式數據,并將響應式數據的__ob__屬性指向自身。如果被觀察值是除數組以外的類型,會調用walk方法,令每個屬性都是響應式。對于基本類型的值,Object.keys會返回一個空數組,所以在walk內,defineReactive只在對象的屬性上執行。如果是被觀察值是數組,那么會在每個元素上調用工廠函數observe,使其響應式。
對于數組,響應式的實現稍有不同。回顧一下在教程數組更新檢測里的說明,變異方法會觸發視圖更新。其具體實現就在這里。arrayMethods是一個對象,保存了Vue重寫的數組方法,具體重寫方式下面再說,現在只需知道這些重寫的數組方法除了保持原數組方法的功能外,還能通知依賴數據已更新。augment的用途是令value能夠調用在arrayMethods中的方法,實現的方式有兩種。第一種是通過原型鏈實現,在value.__proto__添加這些方法,優先選擇這種實現。部分瀏覽器不支持__proto__,則直接在value上添加這些方法。
最后執行observeArray方法,遍歷value,在每個元素上執行observe方法。
數組變異方法的實現執行變異方法會觸發視圖功能,所以變異方法要實現的功能,除了包括原來數組方法的功能外,還要有通知依賴數據更新的功能。代碼保存在/src/core/observer/array.js。
const arrayProto = Array.prototype export const arrayMethods = Object.create(arrayProto) const methodsToPatch = [ "push", "pop", "shift", "unshift", "splice", "sort", "reverse" ] methodsToPatch.forEach(function (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 }) })
模塊內,使用arrayProto保存數組原型,arrayMethods的原型是arrayProto,用來保存變異后的方法,methodsToPatch是保存變異方法名的數組。
遍歷methodsToPatch,根據方法名來獲取在arrayProto上的數組變異方法,然后在arrayMethods實現同名方法。
在該同名方法內,首先執行緩存的數組方法original,執行上下文是this,這些方法最終會添加到響應式數組或其原型上,所以被調用時this是數組本身。ob指向this.__ob__,使用inserted指向被插入的元素,調用ob.observeArray觀察新增的數組元素。最后執行ob.dep.notify(),通知依賴更新。
observe工廠函數,獲取value上__ob__屬性指向的Observer實例,如果需要該屬性且未定義時,根據數據創建一個Observer實例,在實例化時會在value上添加__ob__屬性。參數二表示傳入的value是否是根data對象。只有根數據對象的__ob__.vmCount大于0。
isObject判斷value是不是Object類型,實現如obj !== null && typeof obj === "object"。
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 }
此處可以看出,value與Observer實例ob之間是雙向引用。value.__ob__指向ob,ob.value指向value。
Vue.set在Vue實例化以后,如果想為其添加新的響應式屬性,對于對象,直接使用字面量賦值是沒有效果的。由響應式數據的實現可以想到,這種直接賦值的方式,并沒有為該屬性自定義getter/setter,在獲取屬性時不會收集依賴,在更新屬性時不會觸發更新。如果想要為已存在的響應式數據添加新屬性,可以使用Vue.set/vm.$set方法,但要注意,不能在data上添加新屬性。
Vue.set/vm.$set內部都是在/src/code/observer/index.js定義的set的函數。
set函數接受三個參數,參數一target表示要新增屬性的對象,參數二key表示新增的屬性名或索引,參數三val表示新增屬性的初始值。
export function set (target: Array| Object, key: any, val: any): any { if (process.env.NODE_ENV !== "production" && !Array.isArray(target) && !isObject(target) ) { warn(`Cannot set reactive property on non-object/array value: ${target}`) } if (Array.isArray(target) && isValidArrayIndex(key)) { target.length = Math.max(target.length, key) target.splice(key, 1, val) return val } if (key in target && !(key in Object.prototype)) { target[key] = val return val } const ob = (target: any).__ob__ if (target._isVue || (ob && ob.vmCount)) { process.env.NODE_ENV !== "production" && warn( "Avoid adding reactive properties to a Vue instance or its root $data " + "at runtime - declare it upfront in the data option." ) return val } // 不存在ob 說明不是響應式數據 if (!ob) { target[key] = val return val } // 為target添加新屬性 defineReactive(ob.value, key, val) // ob.dep實際是target.__ob__.dep ob.dep.notify() return val }
函數內部首先判斷target類型,非數組或非對象的目標數據是無法添加響應式數據的。
如果是數組,且key是有效的數組索引,更新數組長度,然后調用變異方法splice,更新對應的值并觸發視圖更新。如果是對象,且屬性key在target的原型鏈上且不在Object.prototype上(即不是Object原型上定義的屬性或方法),直接在target上添加或更新key。
令ob指向target.__ob__,如果target是Vue實例或是根data對象(ob.vmCount > 0),則無法新增數據,直接返回。
接著處理能為target添加屬性的情況。不存在ob時,說明不是響應式數據,直接更新target。否則,執行defineReactive函數為ob.value新增響應式屬性,ob.value實際指向target,添加之后調用ob.dep.notify()通知觀察者重新求值,ob是Observer實例。
總結一下,set的內部邏輯:
當target是數組時,更新長度,調用變異方法splice插入新元素即可。
當target是對象時:
key在除Object.prototype外的原型鏈上時,直接賦值
key在原型鏈上搜索不到時,需要新增屬性。如果target無__ob__屬性,說明不是響應式數據,直接賦值。否則調用defineReactive(ob.value, key, val)觀察新數據,同時觸發依賴。
Vue.delete刪除對象的屬性。如果對象是響應式的,確保刪除能觸發更新視圖。
Vue.delete實際指向del。del接受兩個參數,參數一target表示要刪除屬性的對象,參數二key表示要刪除的屬性名。
如果target是數組且key對于的索引在target中存在,使用變異方法splice方法直接刪除。
如果target是Vue實例或是根data對象則返回,不允許在其上刪除屬性。key不是實例自身屬性時也返回,不允許刪除。如果是自身屬性則使用delete刪除,接著判斷是否有__ob__屬性,如果有,說明是響應式數據,執行__ob__.dep.notify通知視圖更新。
export function del (target: Array小結| Object, key: any) { if (process.env.NODE_ENV !== "production" && !Array.isArray(target) && !isObject(target) ) { warn(`Cannot delete reactive property on non-object/array value: ${target}`) } // 數組 直接刪除元素 if (Array.isArray(target) && isValidArrayIndex(key)) { target.splice(key, 1) return } const ob = (target: any).__ob__ if (target._isVue || (ob && ob.vmCount)) { process.env.NODE_ENV !== "production" && warn( "Avoid deleting properties on a Vue instance or its root $data " + "- just set it to null." ) return } // 屬性不在target上 if (!hasOwn(target, key)) { return } delete target[key] // 不是響應式數據 if (!ob) { return } ob.dep.notify() }
關于Observer類和set/get的源碼已經做了簡單的分析,細心的讀者可能會有一個問題:target.__ob__.dep是什么時候收集依賴的。答案就在defineReactive的源碼中,其收集操作同樣在響應式數據的getter中執行。
至于defineReactive的源碼解析,在后面的文章再做分析。
參考鏈接Vue技術內幕|揭開數據響應系統的面紗
Vue源碼
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/99435.html
摘要:響應式原理之不論如何,最終響應式數據都要通過來實現,實際要借助新增的。在函數內,首先實例化一個實例,會在稍后添加為響應式數據自定義的中發揮作用。只有數組和對象才可能是響應式,才能返回實例。參考鏈接技術內幕揭開數據響應系統的面紗源碼 Vue響應式原理之defineReactive defineReactive 不論如何,最終響應式數據都要通過defineReactive來實現,實際要借助...
摘要:響應式原理為了探究這一切的原因,我再次點開了的官網。在官網很下面的位置,找到了關于響應式原理的說明。因此,新添加到數組中的對象中的屬性,就成了非響應式的屬性了,改變它自然不會讓組件重新渲染。響應式屬性的對象,有這個對象就代表是響應式的。 ??最近在用Vue開發一個后臺管理的demo,有一個非常常規的需求。然而這個常規的需求中,包含了大量的知識點。有一個產品表格,用來顯示不同產品的信息。...
摘要:淺析響應式原理一的特點之一是響應式,視圖隨著數據的更新而更新,在視圖中修改數據后實例中的數據也會同步更新。對于每個響應式數據,會有兩個實例,第一個是在中的閉包遍歷,用途顯而易見。接收一個回調函數,會在重新求值且值更新后執行。 淺析Vue響應式原理(一) Vue的特點之一是響應式,視圖隨著數據的更新而更新,在視圖中修改數據后Vue實例中的數據也會同步更新。內部借助依賴(下文中的Dep類)...
摘要:哪吒別人的看法都是狗屁,你是誰只有你自己說了才算,這是爹教我的道理。哪吒去他個鳥命我命由我,不由天是魔是仙,我自己決定哪吒白白搭上一條人命,你傻不傻敖丙不傻誰和你做朋友太乙真人人是否能夠改變命運,我不曉得。我只曉得,不認命是哪吒的命。 showImg(https://segmentfault.com/img/bVbwiGL?w=900&h=378); 出處 查看github最新的Vue...
閱讀 955·2023-04-25 23:54
閱讀 3036·2021-11-08 13:21
閱讀 3759·2021-09-27 13:35
閱讀 3381·2021-07-26 23:41
閱讀 1043·2019-08-30 15:52
閱讀 3431·2019-08-30 11:27
閱讀 2088·2019-08-29 18:37
閱讀 528·2019-08-29 17:24