vue - 响应式原理梳理(一)
描述
?我們通過(guò)一個(gè)簡(jiǎn)單的 Vue應(yīng)用 來(lái)演示 Vue的響應(yīng)式屬性:
html:<div id="app">{{message}}</div>js:let vm = new Vue({el: '#app',data: {message: '123'}})?在應(yīng)用中,message 屬性即為 響應(yīng)式屬性。
?我們通過(guò) vm.message, vm.$data.message, 可訪問(wèn) 響應(yīng)式屬性 message。
?當(dāng)我們通過(guò)修改 vm.message(vm.message = '456'), 修改后的數(shù)據(jù)會(huì) 更新到UI界面中。
問(wèn)題
- 為什么修改 vm.message, 即可觸發(fā) UI更新;
- vm.message 和 data.message 的關(guān)聯(lián)關(guān)系;
官方介紹
? vue的官網(wǎng)文檔,對(duì)響應(yīng)式屬性的原理有一個(gè)介紹。
把一個(gè)普通的 JavaScript 對(duì)象傳給 Vue 實(shí)例的 data 選項(xiàng),Vue 將遍歷此對(duì)象所有的屬性,并使用 Object.defineProperty 把這些屬性全部轉(zhuǎn)為 getter/setter。每個(gè)組件實(shí)例都有相應(yīng)的 watcher 實(shí)例對(duì)象,它會(huì)在組件渲染的過(guò)程中把屬性記錄為依賴,之后當(dāng)依賴項(xiàng)的 setter 被調(diào)用時(shí),會(huì)通知 watcher 重新計(jì)算,從而致使它關(guān)聯(lián)的組件得以更新。
?官方文檔
? 以上介紹,只是對(duì)響應(yīng)式原理進(jìn)行了簡(jiǎn)單描述,并沒(méi)有深入細(xì)節(jié)。因此本文在源碼層面,對(duì)響應(yīng)式原理進(jìn)行梳理,對(duì)關(guān)鍵步驟進(jìn)行解析。
? 響應(yīng)式原理涉及到的關(guān)鍵步驟如下:
- 構(gòu)建vue實(shí)例;
- vue實(shí)例data屬性初始化,構(gòu)建響應(yīng)式屬性;
- 將vue實(shí)例對(duì)應(yīng)的template編譯為render函數(shù);
- 構(gòu)建vue實(shí)例的watcher對(duì)象;
- 執(zhí)行render函數(shù),構(gòu)建VNode節(jié)點(diǎn)樹,同時(shí)建立響應(yīng)式屬性和watcher對(duì)象的依賴關(guān)系;
- 將VNode節(jié)點(diǎn)渲染為dom節(jié)點(diǎn)樹;
- 修改響應(yīng)式屬性,觸發(fā)watcher的更新,重新執(zhí)行render函數(shù),生成新的VNode節(jié)點(diǎn)樹;
- 對(duì)比新舊Vnode,重新渲染dom節(jié)點(diǎn)樹;
構(gòu)造函數(shù) - Vue
?Vue.js 給我們提供了一個(gè) 全局構(gòu)造函數(shù) Vue。
?通過(guò) new Vue(options) 生成一個(gè) vue實(shí)例,從而可以構(gòu)建一個(gè) Vue應(yīng)用。
?其中,options 為構(gòu)造vue實(shí)例的配置項(xiàng),即為 { 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')}// 根據(jù)options, 初始化vue實(shí)例this._init(options)}export default Vue;? vue實(shí)例 構(gòu)造完畢之后,執(zhí)行實(shí)例私有方法 _init(), 開始初始化。
? 在一個(gè) vue應(yīng)用 中,存在兩種類型的 vue實(shí)例 :根vue實(shí)例 和 組件vue實(shí)例。
? 根vue實(shí)例,由構(gòu)造函數(shù) Vue 生成。
? 組件vue實(shí)例,由組件構(gòu)造函數(shù) VueComponent 生成,組件構(gòu)造函數(shù) 繼承 自構(gòu)造函數(shù) Vue。
// 全局方法extend, 會(huì)返回一個(gè)組件構(gòu)造函數(shù)。Vue.extend = function(options) {...// 組件構(gòu)造函數(shù),用于創(chuàng)建組件var Sub = function VueComponent(options) {this._init(options);};// 子類的prototype繼承自Vue的prototype// 相當(dāng)于Sub實(shí)例可以使用Vue實(shí)例的方法Sub.prototype = Object.create(Vue.prototype);...return Sub;}? 通過(guò)一個(gè) 根vue實(shí)例 和多個(gè) 組件vue實(shí)例,構(gòu)成了整個(gè) Vue應(yīng)用。
Vue.prototype._init
? 在_init方法中,vue實(shí)例會(huì)執(zhí)行一系列初始化操作。
? 在初始化過(guò)程中, 我們通過(guò)全局方法 initState 來(lái)初始化vue實(shí)例的 data、props、methods、computed、watch 屬性。
Vue.prototype._init = function(options) {var vm = this;... // 其他初始化過(guò)程, 包括建立子vue實(shí)例和父vue實(shí)例的對(duì)應(yīng)關(guān)系、給vue實(shí)例添加自定義事件、執(zhí)行beforeCreated回調(diào)函數(shù)等// 初始化props屬性、data屬性、methods屬性、computed屬性、watch屬性initState(vm);... // 其他初始化過(guò)程,比如執(zhí)行created回調(diào)函數(shù)// vue實(shí)例初始化完成以后,掛載vue實(shí)例,將模板渲染成htmlif(vm.$options.el) {vm.$mount(vm.$options.el);}};function initState (vm: Component) {vm._watchers = [];// new Vue(options) 中的 optionsconst opts = vm.$options; // 將props配置項(xiàng)中屬性轉(zhuǎn)化為vue實(shí)例的響應(yīng)式屬性if (opts.props) initProps(vm, opts.props); // 將 methods配置項(xiàng)中的方法添加到 vue實(shí)例對(duì)象中if (opts.methods) initMethods(vm, opts.methods);// 將data配置項(xiàng)中的屬性轉(zhuǎn)化為vue實(shí)例的響應(yīng)式屬性if (opts.data) {initData(vm)} else {observe(vm._data = {}, true /* asRootData */)}...}? 其中,initData 方法會(huì)將 data配置項(xiàng) 中的屬性全部轉(zhuǎn)化為 vue實(shí)例 的 響應(yīng)式屬性。
initData
initData 方法的主要過(guò)程:
- 根據(jù)data配置項(xiàng),創(chuàng)建vue實(shí)例的私有屬性: _data。
- 通過(guò) observe 方法,將 _data 對(duì)象中的屬性轉(zhuǎn)化為 響應(yīng)式屬性。
- 通過(guò)全局方法proxy, 建立 vue實(shí)例 和 _data 的關(guān)聯(lián)關(guān)系。
observe
? 全局方法 observe 的作用是用來(lái)觀察一個(gè)對(duì)象,將_data對(duì)象的屬性全部轉(zhuǎn)化為 響應(yīng)式屬性。
// 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是數(shù)組,對(duì)數(shù)組每一個(gè)元素執(zhí)行observe方法this.observeArray(value);} else {// 如果value是對(duì)象, 遍歷對(duì)象的每一個(gè)屬性, 將屬性轉(zhuǎn)化為響應(yīng)式屬性this.walk(value);}};// 如果要觀察的對(duì)象時(shí)數(shù)組, 遍歷數(shù)組,然后調(diào)用observe方法將對(duì)象的屬性轉(zhuǎn)化為響應(yīng)式屬性O(shè)bserver.prototype.observeArray = function observeArray(items) {for(var i = 0, l = items.length; i < l; i++) {observe(items[i]);}};// 遍歷obj的屬性,將obj對(duì)象的屬性轉(zhuǎn)化為響應(yīng)式屬性O(shè)bserver.prototype.walk = function walk(obj) {var keys = Object.keys(obj);for(var i = 0; i < keys.length; i++) {// 給obj的每一個(gè)屬性都賦予getter/setter方法。// 這樣一旦屬性被訪問(wèn)或者更新,這樣我們就可以追蹤到這些變化defineReactive(obj, keys[i], obj[keys[i]]);}};defineReactive
? 通過(guò) defineProperty 方法, 提供屬性的 getter/setter 方法。
? 讀取 屬性時(shí),觸發(fā) getter,將與響應(yīng)式屬性相關(guān)的vue實(shí)例保存起來(lái)。
? 修改 屬性時(shí),觸發(fā) setter,更新與響應(yīng)式屬性相關(guān)的vue實(shí)例。
function defineReactive(obj, key, val, customSetter, shallow) {// 每一個(gè)響應(yīng)式屬性都會(huì)有一個(gè) Dep對(duì)象實(shí)例, 該對(duì)象實(shí)例會(huì)存儲(chǔ)訂閱它的Watcher對(duì)象實(shí)例var dep = new Dep();// 獲取對(duì)象屬性key的描述對(duì)象var property = Object.getOwnPropertyDescriptor(obj, key);// 如果屬性是不可配置的,則直接返回if(property && property.configurable === false) {return}// 屬性原來(lái)的getter/settervar getter = property && property.get;var setter = property && property.set;// 如果屬性值是一個(gè)對(duì)象,遞歸觀察屬性值,var childOb = !shallow && observe(val);// 重新定義對(duì)象obj的屬性keyObject.defineProperty(obj, key, {enumerable : true,configurable : true,get : function reactiveGetter() {// 當(dāng)obj的某個(gè)屬性被訪問(wèn)的時(shí)候,就會(huì)調(diào)用getter方法。var value = getter ? getter.call(obj) : val;// 當(dāng)Dep.target不為空時(shí),調(diào)用dep.depend 和 childOb.dep.depend方法做依賴收集if(Dep.target) {// 通過(guò)dep對(duì)象, 收集依賴關(guān)系dep.depend();if(childOb) {childOb.dep.depend();}// 如果訪問(wèn)的是一個(gè)數(shù)組, 則會(huì)遍歷這個(gè)數(shù)組, 收集數(shù)組元素的依賴if(Array.isArray(value)) {dependArray(value);}}return value},set : function reactiveSetter(newVal) {// 當(dāng)改變obj的屬性是,就會(huì)調(diào)用setter方法。這是就會(huì)調(diào)用dep.notify方法進(jìn)行通知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);// 當(dāng)響應(yīng)式屬性發(fā)生修改時(shí),通過(guò)dep對(duì)象通知依賴的vue實(shí)例進(jìn)行更新dep.notify();}});}? 響應(yīng)式屬性, 通過(guò)一個(gè) dep 對(duì)象, 收集依賴響應(yīng)式屬性的vue實(shí)例,在屬性改變時(shí) 通知vue實(shí)例更新。
? 一個(gè) 響應(yīng)式屬性, 對(duì)應(yīng)一個(gè) dep 對(duì)象。
Dep
? 在觀察者設(shè)計(jì)模式中,有兩種角色:Subject 和 Observer。
? Subject 會(huì)維護(hù)一個(gè) Observer的依賴列表。當(dāng) Subject 發(fā)生變化時(shí),會(huì)通知 Observer 更新。
? 在vue中,響應(yīng)式屬性作為Subject, vue實(shí)例作為Observer, 響應(yīng)式屬性的更新會(huì)通知vue實(shí)例更新。
? 響應(yīng)式屬性通過(guò) dep 對(duì)象來(lái)收集 依賴關(guān)系 。一個(gè)響應(yīng)式屬性,對(duì)應(yīng)一個(gè)dep對(duì)象。
var Dep = function Dep() {// dep對(duì)象的idthis.id = uid++;// 數(shù)組,用來(lái)存儲(chǔ)依賴響應(yīng)式屬性的Observerthis.subs = [];};// 將Observer添加到dep對(duì)象的依賴列表中Dep.prototype.addSub = function addSub(sub) {// Dep對(duì)象實(shí)例添加訂閱它的Watcherthis.subs.push(sub);};// 將Observer從dep對(duì)象的依賴列表中刪除Dep.prototype.removeSub = function removeSub(sub) {// Dep對(duì)象實(shí)例移除訂閱它的Watcherremove(this.subs, sub);};// 收集依賴關(guān)系Dep.prototype.depend = function depend() {// 把當(dāng)前Dep對(duì)象實(shí)例添加到當(dāng)前正在計(jì)算的Watcher的依賴中if(Dep.target) {Dep.target.addDep(this);}};// 通知Observer更新Dep.prototype.notify = function notify() {// stabilize the subscriber list firstvar subs = this.subs.slice();// 遍歷所有的訂閱Watcher,然后調(diào)用他們的update方法for(var i = 0, l = subs.length; i < l; i++) {subs[i].update();}};proxy
? 通過(guò) defineProperty 方法, 給vue實(shí)例對(duì)象添加屬性,提供屬性的 getter/setter 方法。
? 讀取vue實(shí)例的屬性( data配置項(xiàng)中的同名屬性 ), 觸發(fā) getter,讀取 _data 的同名屬性。
? 修改vue實(shí)例的屬性( data配置項(xiàng)中的同名屬性 ), 觸發(fā) 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);}? 通過(guò) proxy 方法,vue實(shí)例 可代理私有屬性 _data, 即通過(guò) vue實(shí)例 可以訪問(wèn)/修改 響應(yīng)式屬性。
總結(jié)
? 結(jié)合源碼理解, 響應(yīng)式屬性 的原理為:
總結(jié)
以上是生活随笔為你收集整理的vue - 响应式原理梳理(一)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: logback的简单使用
- 下一篇: Nginx + Node + Vue 部