Vue源碼學(xué)習(xí)defineProperty響應(yīng)式數(shù)據(jù)原理實現(xiàn)
準備工作
接上文數(shù)據(jù)初始化完成之后,就可以對數(shù)據(jù)進行劫持。Vue2中對數(shù)據(jù)進行劫持采用了一個Api叫Object.defineProperty()
在這里需要提供一個方法去觀測data變化,這個方法是一個核心模塊(響應(yīng)式模塊),我們單獨建一個文件夾來存放在/src/observe/index.js
// src/state.js import { observe } from "./observe/index" export function initState(vm){ // 對數(shù)據(jù)需要進行劫持 const opts = vm.$options //獲取所有選項 if (opts.data){ initData(vm) } } function initData(vm){ // 對數(shù)據(jù)進行代理 let data = vm.$options.data // data可以是函數(shù)或者對象,根實例可以是對象,組件data必須是函數(shù) data = typeof data === 'function' ? data.call(vm) : data // 對數(shù)據(jù)進行劫持 Vue2采用的一個api object.defineproperty observe(data) }
// src/observe/index.js export function observe(data){ debugger console.log(data); }
執(zhí)行/dist/index.html,當控制臺出可以輸出{name: 'i東東', age: 18}
說明前面的代碼沒有問題,接下來就可以開始下面的操作了。
第一步 對對象進行劫持
當拿到了data,就可以對data數(shù)據(jù)進行劫持,如果說他不是對象就不用劫持,所以還需要進行一個判斷。
// src/observe/index.js export function observe(data){ // 對這個對象進行劫持 if(typeof data !=='object'|| data == null){ return // 只對對象進行劫持 } }
那么緊接著如何劫持這個對象呢?
如果一個對象被劫持過了,那么就不需要再次被劫持了(要判斷一個對象是否被劫持過,可以增添一個實例,來判斷是否被劫持過),所以在內(nèi)部創(chuàng)造了一個類去觀測數(shù)據(jù),如果數(shù)據(jù)被觀測過那他的實例就是這個類。
// src/observe/index.js class Observer{ constructor(data){ //所有數(shù)據(jù) this.walk(data) // 因為data是一個對象,所以就需要對data進行比遍歷 } walk(data){ // 循環(huán)對象 對屬性依次劫持 Object.keys(data).forEach(key=>defineReactive(data,key,data[key])) //重新定義屬性 } } export function defineReactive(target,key,value){ // 閉包 屬性劫持 Object.defineProperty(target,key,{ get(){ //取值的時候會執(zhí)行g(shù)et return value }, set(newValue){ // 修改的時候執(zhí)行set if(newValue === value) return value = newValue } }) } export function observe(data){ // 對這個對象進行劫持 if(typeof data !=='object'|| data == null){ return // 只對對象進行劫持 } return new Observer(data); // 對這個數(shù)據(jù)進行觀測 }
因為要對每個屬性進行劫持,但是Objece.defineProperty()
只能劫持已經(jīng)存在的屬性,后增加的或者刪除的是不知道的,(Vue2里面會為此單獨寫一些api 比如:setset setdelete),所以需要對data進行遍歷 this.walk()
對屬性依次劫持,重新定義屬性(性能會差,Vue3中proxy就會好很多),就可以調(diào)用defineReactive,因為這個方法可以單獨去使用,所以直接導(dǎo)出。
完成之后執(zhí)行index.html中console.log(vm),會發(fā)現(xiàn)vm上只有用戶的選項,并沒有剛才劫持過的屬性,是因為在state.js中我們只是data傳入了observe函數(shù),所以就考慮,在vm上增加一個屬性,叫_data
,這樣就相當于把_data對象放在了實例vm上,并且又把這個對象進行了觀測,觀測的時候依舊回去循環(huán)這個對象。
// src/state.js function initData(vm){ let data = vm.$options.data data = typeof data === 'function' ? data.call(vm) : data vm._data = data // 新增這一句 observe(data) }
這樣再次輸出,會發(fā)現(xiàn)控制臺輸出了_data,并且給age,name都增加上來get和set方法,現(xiàn)在說明這個事情就成了。
這個時候就可以通過vm._data.name進行取值
// dist/index.html const vm = new Vue({ data(){ return { name:'i東東', age:18 } } }) vm._data.name = 'i東東修改' console.log(vm._data.name); // 用戶設(shè)置值了 // index.js:15 用戶取值了 // index.html:29 i東東修改
第二步 修改取值方法
緊接著就會發(fā)現(xiàn)正常我們?nèi)≈刀际莢m.name,但是上面的訪問還是vm._data.name,所以下面需要將取值的方法進行一下優(yōu)化。需要在state.js中將vm._data用vm代理。
// state.js function proxy(vm,target,key){ Object.defineProperty(vm,key,{ get(){ return vm[target][key] // vm._data.name }, set(newValue){ vm[target][key] = newValue } }) } function initData(vm){ // 對數(shù)據(jù)進行代理 let data = vm.$options.data data = typeof data === 'function' ? data.call(vm) : data vm._data = data observe(data) // 新增 將vm._data用vm代理 for(let key in data){ proxy(vm,'_data',key) } }
這樣在index.html中我們就可以用過vm.name重鋼訪問到數(shù)據(jù),也可以通過vm.name = 'i東東修改'去設(shè)置值,雖然這樣性能是不太好的,但是他用起來會很方便的。所以在這里面相當于代理了兩次第一次把用戶的數(shù)據(jù)進行了屬性劫持,第二次就是proxy當取值和設(shè)置值的時候代理到某個人身上。
第三步 深度屬性劫持
// index.html const vm = new Vue({ data(){ return { name:'i東東', age:18, say:{ hobby:'學(xué)習(xí)' } } } }) console.log(vm);
假如說我再增加一個對象say,輸出vm會發(fā)現(xiàn)hobby并沒有被劫持,原因是因為我們只劫持了name、age、say三個屬性,如果屬性是個對象的話,我們就需要再次劫持。這樣我們只需要在defineReactive()里面再次調(diào)用observe再次建立劫持,形成遞歸這樣就可以完成對對象的深度屬性劫持。
// src/observe/index.js export function defineReactive(target,key,value){ // 閉包 屬性劫持 observe(value) // 新增 對所有的對象都進行屬性接觸 Object.defineProperty(target,key,{ get(){ //取值的時候會執(zhí)行g(shù)et console.log('用戶取值了'); return value }, set(newValue){ // 修改的時候執(zhí)行set console.log('用戶設(shè)置值了'); if(newValue === value) return value = newValue } }) }
以上就是Vue源碼學(xué)習(xí)defineProperty響應(yīng)式數(shù)據(jù)原理實現(xiàn)的詳細內(nèi)容,更多關(guān)于Vue defineProperty響應(yīng)式數(shù)據(jù)的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
關(guān)于SpringBoot與Vue交互跨域問題解決方案
最近在利用springboot+vue整合開發(fā)一個前后端分離的個人博客網(wǎng)站,所以這一篇總結(jié)一下在開發(fā)中遇到的一個問題,關(guān)于解決在使用vue和springboot在開發(fā)前后端分離的項目時,如何解決跨域問題。在這里分別分享兩種方法,分別在前端vue中解決和在后臺springboot中解決。2021-10-10使用Vue3+ts?開發(fā)ProTable源碼教程示例
這篇文章主要為大家介紹了使用Vue3+ts?開發(fā)ProTable源碼示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-07-07