摘要:問題為什么修改即可觸發更新和的關聯關系官方介紹的官網文檔,對響應式屬性的原理有一個介紹。因此本文在源碼層面,對響應式原理進行梳理,對關鍵步驟進行解析。
描述
?我們通過一個簡單的 Vue應用 來演示 Vue的響應式屬性:
html:{{message}}js: let vm = new Vue({ el: "#app", data: { message: "123" } })
?在應用中,message 屬性即為 響應式屬性。
?我們通過 vm.message, vm.$data.message, 可訪問 響應式屬性 message。
?當我們通過修改 vm.message(vm.message = "456"), 修改后的數據會 更新到UI界面中。
問題為什么修改 vm.message, 即可觸發 UI更新;
vm.message 和 data.message 的關聯關系;
官方介紹? vue的官網文檔,對響應式屬性的原理有一個介紹。
把一個普通的 JavaScript 對象傳給 Vue 實例的 data 選項,Vue 將遍歷此對象所有的屬性,并使用 Object.defineProperty 把這些屬性全部轉為 getter/setter。每個組件實例都有相應的 watcher 實例對象,它會在組件渲染的過程中把屬性記錄為依賴,之后當依賴項的 setter 被調用時,會通知 watcher 重新計算,從而致使它關聯的組件得以更新。
?官方文檔
? 以上介紹,只是對響應式原理進行了簡單描述,并沒有深入細節。因此本文在源碼層面,對響應式原理進行梳理,對關鍵步驟進行解析。
? 響應式原理涉及到的關鍵步驟如下:
構建vue實例;
vue實例data屬性初始化,構建響應式屬性;
將vue實例對應的template編譯為render函數;
構建vue實例的watcher對象;
執行render函數,構建VNode節點樹,同時建立響應式屬性和watcher對象的依賴關系;
將VNode節點渲染為dom節點樹;
修改響應式屬性,觸發watcher的更新,重新執行render函數,生成新的VNode節點樹;
對比新舊Vnode,重新渲染dom節點樹;
構造函數 - Vue?Vue.js 給我們提供了一個 全局構造函數 Vue。
?通過 new Vue(options) 生成一個 vue實例,從而可以構建一個 Vue應用。
?其中,options 為構造vue實例的配置項,即為 { data, methods, computed, filter ... }
/* options: { data: {...}, methods: {...}, computed: {...}, watch: {...} ... } */ 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") } // 根據options, 初始化vue實例 this._init(options) } export default Vue;
? vue實例 構造完畢之后,執行實例私有方法 _init(), 開始初始化。
? 在一個 vue應用 中,存在兩種類型的 vue實例 :根vue實例 和 組件vue實例。
? 根vue實例,由構造函數 Vue 生成。
? 組件vue實例,由組件構造函數 VueComponent 生成,組件構造函數 繼承 自構造函數 Vue。
// 全局方法extend, 會返回一個組件構造函數。 Vue.extend = function(options) { ... // 組件構造函數,用于創建組件 var Sub = function VueComponent(options) { this._init(options); }; // 子類的prototype繼承自Vue的prototype // 相當于Sub實例可以使用Vue實例的方法 Sub.prototype = Object.create(Vue.prototype); ... return Sub; }
? 通過一個 根vue實例 和多個 組件vue實例,構成了整個 Vue應用。
Vue.prototype._init? 在_init方法中,vue實例會執行一系列初始化操作。
? 在初始化過程中, 我們通過全局方法 initState 來初始化vue實例的 data、props、methods、computed、watch 屬性。
Vue.prototype._init = function(options) { var vm = this; ... // 其他初始化過程, 包括建立子vue實例和父vue實例的對應關系、給vue實例添加自定義事件、執行beforeCreated回調函數等 // 初始化props屬性、data屬性、methods屬性、computed屬性、watch屬性 initState(vm); ... // 其他初始化過程,比如執行created回調函數 // vue實例初始化完成以后,掛載vue實例,將模板渲染成html if(vm.$options.el) { vm.$mount(vm.$options.el); } }; function initState (vm: Component) { vm._watchers = []; // new Vue(options) 中的 options const opts = vm.$options; // 將props配置項中屬性轉化為vue實例的響應式屬性 if (opts.props) initProps(vm, opts.props); // 將 methods配置項中的方法添加到 vue實例對象中 if (opts.methods) initMethods(vm, opts.methods); // 將data配置項中的屬性轉化為vue實例的響應式屬性 if (opts.data) { initData(vm) } else { observe(vm._data = {}, true /* asRootData */) } ... }
? 其中,initData 方法會將 data配置項 中的屬性全部轉化為 vue實例 的 響應式屬性。
initDatainitData 方法的主要過程:
根據data配置項,創建vue實例的私有屬性: _data。
通過 observe 方法,將 _data 對象中的屬性轉化為 響應式屬性。
通過全局方法proxy, 建立 vue實例 和 _data 的關聯關系。
function initData(vm) { // 獲取data配置項對象 var data = vm.$options.data; // 組件實例的data配置項是一個函數 data = vm._data = typeof data === "function"? getData(data, vm): data || {}; // 獲取data配置項的屬性值 var keys = Object.keys(data); // 獲取props配置項的屬性值 var props = vm.$options.props; // 獲取methods配置項的屬性值; var methods = vm.$options.methods; var i = keys.length; while(i--) { var key = keys[i]; { // methods配置項和data配置項中的屬性不能同名 if(methods && hasOwn(methods, key)) { warn( ("method "" + key + "" has already been defined as a data property."), vm ); } } // props配置項和data配置項中的屬性不能同名 if(props && hasOwn(props, key)) { "development" !== "production" && warn( "The data property "" + key + "" is already declared as a prop. " + "Use prop default value instead.", vm ); } else if(!isReserved(key)) { // 如果屬性不是$,_ 開頭(vue的保留屬性) // 建立 vue實例 和 _data 的關聯關系性 proxy(vm, "_data", key); } } // 觀察data對象, 將對象屬性全部轉化為響應式屬性 observe(data, true /* asRootData */); }observe
? 全局方法 observe 的作用是用來觀察一個對象,將_data對象的屬性全部轉化為 響應式屬性。
// observe(_data, true) function observe(value, asRootData) { if(!isObject(value)) { return } var ob; ... // ob = new Observer(value); ... return ob; } var Observer = function Observer(value) { ... if(Array.isArray(value)) { // 如果value是數組,對數組每一個元素執行observe方法 this.observeArray(value); } else { // 如果value是對象, 遍歷對象的每一個屬性, 將屬性轉化為響應式屬性 this.walk(value); } }; // 如果要觀察的對象時數組, 遍歷數組,然后調用observe方法將對象的屬性轉化為響應式屬性 Observer.prototype.observeArray = function observeArray(items) { for(var i = 0, l = items.length; i < l; i++) { observe(items[i]); } }; // 遍歷obj的屬性,將obj對象的屬性轉化為響應式屬性 Observer.prototype.walk = function walk(obj) { var keys = Object.keys(obj); for(var i = 0; i < keys.length; i++) { // 給obj的每一個屬性都賦予getter/setter方法。 // 這樣一旦屬性被訪問或者更新,這樣我們就可以追蹤到這些變化 defineReactive(obj, keys[i], obj[keys[i]]); } };defineReactive
? 通過 defineProperty 方法, 提供屬性的 getter/setter 方法。
? 讀取 屬性時,觸發 getter,將與響應式屬性相關的vue實例保存起來。
? 修改 屬性時,觸發 setter,更新與響應式屬性相關的vue實例。
function defineReactive(obj, key, val, customSetter, shallow) { // 每一個響應式屬性都會有一個 Dep對象實例, 該對象實例會存儲訂閱它的Watcher對象實例 var dep = new Dep(); // 獲取對象屬性key的描述對象 var property = Object.getOwnPropertyDescriptor(obj, key); // 如果屬性是不可配置的,則直接返回 if(property && property.configurable === false) { return } // 屬性原來的getter/setter var getter = property && property.get; var setter = property && property.set; // 如果屬性值是一個對象,遞歸觀察屬性值, var childOb = !shallow && observe(val); // 重新定義對象obj的屬性key Object.defineProperty(obj, key, { enumerable : true, configurable : true, get : function reactiveGetter() { // 當obj的某個屬性被訪問的時候,就會調用getter方法。 var value = getter ? getter.call(obj) : val; // 當Dep.target不為空時,調用dep.depend 和 childOb.dep.depend方法做依賴收集 if(Dep.target) { // 通過dep對象, 收集依賴關系 dep.depend(); if(childOb) { childOb.dep.depend(); } // 如果訪問的是一個數組, 則會遍歷這個數組, 收集數組元素的依賴 if(Array.isArray(value)) { dependArray(value); } } return value }, set : function reactiveSetter(newVal) { // 當改變obj的屬性是,就會調用setter方法。這是就會調用dep.notify方法進行通知 var 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("development" !== "production" && customSetter) { customSetter(); } if(setter) { setter.call(obj, newVal); } else { val = newVal; } childOb = !shallow && observe(newVal); // 當響應式屬性發生修改時,通過dep對象通知依賴的vue實例進行更新 dep.notify(); } }); }
? 響應式屬性, 通過一個 dep 對象, 收集依賴響應式屬性的vue實例,在屬性改變時 通知vue實例更新。
? 一個 響應式屬性, 對應一個 dep 對象。
Dep? 在觀察者設計模式中,有兩種角色:Subject 和 Observer。
? Subject 會維護一個 Observer的依賴列表。當 Subject 發生變化時,會通知 Observer 更新。
? 在vue中,響應式屬性作為Subject, vue實例作為Observer, 響應式屬性的更新會通知vue實例更新。
? 響應式屬性通過 dep 對象來收集 依賴關系 。一個響應式屬性,對應一個dep對象。
var Dep = function Dep() { // dep對象的id this.id = uid++; // 數組,用來存儲依賴響應式屬性的Observer this.subs = []; }; // 將Observer添加到dep對象的依賴列表中 Dep.prototype.addSub = function addSub(sub) { // Dep對象實例添加訂閱它的Watcher this.subs.push(sub); }; // 將Observer從dep對象的依賴列表中刪除 Dep.prototype.removeSub = function removeSub(sub) { // Dep對象實例移除訂閱它的Watcher remove(this.subs, sub); }; // 收集依賴關系 Dep.prototype.depend = function depend() { // 把當前Dep對象實例添加到當前正在計算的Watcher的依賴中 if(Dep.target) { Dep.target.addDep(this); } }; // 通知Observer更新 Dep.prototype.notify = function notify() { // stabilize the subscriber list first var subs = this.subs.slice(); // 遍歷所有的訂閱Watcher,然后調用他們的update方法 for(var i = 0, l = subs.length; i < l; i++) { subs[i].update(); } };proxy
? 通過 defineProperty 方法, 給vue實例對象添加屬性,提供屬性的 getter/setter 方法。
? 讀取vue實例的屬性( data配置項中的同名屬性 ), 觸發 getter,讀取 _data 的同名屬性。
? 修改vue實例的屬性( data配置項中的同名屬性 ), 觸發 setter,修改 _data 的同名屬性。
// proxy(vm, _data, "message") function proxy(target, sourceKey, key) { sharedPropertyDefinition.get = function proxyGetter() { return this[sourceKey][key] }; sharedPropertyDefinition.set = function proxySetter(val) { this[sourceKey][key] = val; }; Object.defineProperty(target, key, sharedPropertyDefinition); }
? 通過 proxy 方法,vue實例 可代理私有屬性 _data, 即通過 vue實例 可以訪問/修改 響應式屬性。
總結? 結合源碼理解, 響應式屬性 的原理為:
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/101374.html
摘要:原型方法通過原型方法方法來掛載實例。當響應式屬性發生變化時,會通知依賴列表中的對象進行更新。此時,對象執行方法,重新渲染節點。在執行過程中,如果需要讀取響應式屬性,則會觸發響應式屬性的。總結響應式屬性的原理 vue實例 初始化 完成以后,接下來就要進行 掛載。 vue實例掛載,即為將vue實例對應的 template模板,渲染成 Dom節點。 原型方法 - $mount ? 通過原...
摘要:引言上篇文章介紹原型,這篇文章接著講繼承,嘔心瀝血之作,大哥們點個贊呀明確一點并不是真正的面向對象語言,沒有真正的類,所以我們也沒有類繼承實現繼承有且僅有兩種方式,和原型鏈在介紹繼承前我們先介紹下其他概念函數的三種角色一個函數,有三種角色。 showImg(https://segmentfault.com/img/bVbo4hv?w=1800&h=1000); 引言 上篇文章介紹原型,...
摘要:由于是需要兼容的后臺系統,該項目并不能使用到等技術,因此我在上的經驗大都是使用原生的編寫的,可以看見一個組件分為兩部分視圖部分,和數據部分。 在公司里幫項目組里開發后臺系統的前端項目也有一段時間了。 vue這種數據驅動,組件化的框架和react很像,從一開始的快速上手基本的開發,到后來開始自定義組件,對element UI的組件二次封裝以滿足項目需求,期間也是踩了不少坑。由于將來很長一...
摘要:接下來,我們就一起深入了解的數據響應式原理,搞清楚響應式的實現機制。回調函數只是打印出新的得到的新的值,由執行后生成。及異步更新相信讀過前文,你應該對響應式原理有基本的認識。 前言 Vue.js 的核心包括一套響應式系統。 響應式,是指當數據改變后,Vue 會通知到使用該數據的代碼。例如,視圖渲染中使用了數據,數據改變后,視圖也會自動更新。 舉個簡單的例子,對于模板: {{ name ...
摘要:雖然計算屬性在大多數情況下更合適,但有時也需要一個自定義的偵聽器。當某個屬性發生變化,觸發攔截函數,然后調用自身消息訂閱器的方法,遍歷當前中保存著所有訂閱者的數組,并逐個調用的方法,完成響應更新。 雖然目前的技術棧已由Vue轉到了React,但從之前使用Vue開發的多個項目實際經歷來看還是非常愉悅的,Vue文檔清晰規范,api設計簡潔高效,對前端開發人員友好,上手快,甚至個人認為在很多...
閱讀 1676·2021-11-19 09:40
閱讀 2933·2021-09-24 10:27
閱讀 3220·2021-09-02 15:15
閱讀 1881·2019-08-30 15:54
閱讀 1205·2019-08-30 15:54
閱讀 1373·2019-08-30 13:12
閱讀 636·2019-08-28 18:05
閱讀 2801·2019-08-27 10:53