非庖丁瞎解牛系列~ =。=
在日常項目開發的時候,我們將js對象傳給vue實例中的data選項,來作為其更新視圖的基礎,事實上是vue將會遍歷它的屬性,用Object.defineProperty 設置它們的 get/set,從而讓 data 的屬性能夠響應數據變化:
Object.defineProperty(obj, name, { // 獲取值的時候先置入vm的_data屬性對象中 get() { // 賦值的時候顯示的特性 }, set() { // 值變化的時候可以做點什么 } })
接下來可以利用其實現一個最簡單的watcher.既然要綁定數據執行回調函數,data屬性和callback屬性是少不了的,我們定義一個vm對象(vue中vm對象作為根實例,是全局的):
/** * @param {Object} _data 用于存放data值 * @param {Object} $data data原始數據對象,當前值 * @param {Object} callback 回調函數 */ var vm = { _data: {}, $data: {}, callback: {} }
在設置值的時候,如果檢測到當前值與存儲在_data中的對應值發生變化,則將值更新,并執行回調函數,利用Object.definedProperty方法中的get() & set() 我們很快就可以實現這個功能~
vm.$watch = (obj, func) => { // 回調函數 vm.callback[ obj ] = func // 設置data Object.defineProperty(vm.$data, obj, { // 獲取值的時候先置入vm的_data屬性對象中 get() { return vm._data[ obj ] }, set(val) { // 比較原值,不相等則賦值,執行回調 if (val !== vm._data[ obj ]) { vm._data[ obj ] = val const cb = vm.callback[ obj ] cb.call(vm) } } }) } vm.$watch("va", () => {console.log("已經成功被監聽啦")}) vm.$data.va = 1
雖然初步實現了這個小功能,那么問題來了,obj對象如果只是一個簡單的值為值類型的變量,那以上代碼完全可以滿足;但是如果obj是一個具有一層甚至多層樹結構對象變量,我們就只能監聽到最外層也就是obj本身的變化,內部屬性變化無法被監聽(沒有設置給對應屬性設置set和get),因為對象自身內部屬性層數未知,理論上可以無限層(一般不會這么做),所以此處還是用遞歸解決吧~
咱們先將Object.defineProperty函數剝離,一是解耦,二是方便我們遞歸~
var defineReactive = (obj, key) => { Object.defineProperty(obj, key, { get() { return vm._data[key] }, set(newVal) { if (vm._data[key] === newVal) { return } vm._data[key] = newVal const cb = vm.callback[ obj ] cb.call(vm) } }) }
咦,說好的遞歸呢,不著急,上面只是抽離了加get和set功能的函數,
現在我們加入遞歸~
var Observer = (obj) => { // 遍歷,讓對象中的每個屬性可以加上get set Object.keys(obj).forEach((key) =>{ defineReactive(obj, key) }) }
這里僅僅只是遍歷,要達到遞歸,則需要在defineReactive的時候再加上判斷,判斷這個屬性是否為object類型,如果是,則執行Observer自身~我們改寫下defineReactive函數
// 判斷是否為object類型,是就繼續執行自身 var observe = (value) => { // 判斷是否為object類型,是就繼續執行Observer if (!value || typeof value !== "object") { return } return new Observer(value) } // 將observe方法置入defineReactive中Object.defineProperty的set中,形成遞歸 var defineReactive = (obj, key) => { // 判斷val是否為對象,如果對象有很多層屬性,則這邊的代碼會不斷調用自身(因為observe又執行了Observer,而Observer執行defineReactive),一直到最后一層,從最后一層開始執行下列代碼,層層返回(可以理解為洋蔥模型),直到最前面一層,給所有屬性加上get/set var childObj = observe(vm._data[key]) Object.defineProperty(obj, key, { get() { return vm._data[key] }, set(newVal) { // 如果設置的值完全相等則什么也不做 if (vm._data[key] === newVal) { return } // 不相等則賦值 vm._data[key] = newVal // 執行回調 const cb = vm.callback[ key ] cb.call(vm) // 如果set進來的值為復雜類型,再遞歸它,加上set/get childObj = observe(val) } }) }
現在我們來整理下,把我們剛開始實現的功能雛形進行進化
var vm = { _data: {}, $data: {}, callback: {}} var defineReactive = (obj, key) => { // 一開始的時候是不設值的,所以,要在外面做一套observe // 判斷val是否為對象,如果對象有很多層屬性,則這邊的代碼會不斷調用自身(因為observe又執行了Observer,而Observer執行defineReactive),一直到最后一層,從最后一層開始執行下列代碼,層層返回(可以理解為洋蔥模型),直到最前面一層,給所有屬性加上get/set var childObj = observe(vm._data[key]) Object.defineProperty(obj, key, { get() { return vm._data[key] }, set(newVal) { if (vm._data[key] === newVal) { return } // 如果值有變化的話,做一些操作 vm._data[key] = newVal // 執行回調 const cb = vm.callback[ key ] cb.call(vm) // 如果set進來的值為復雜類型,再遞歸它,加上set/get childObj = observe(newVal) } }) } var Observer = (obj) => { Object.keys(obj).forEach((key) =>{ defineReactive(obj, key) }) } var observe = (value) => { // 判斷是否為object類型,是就繼續執行Observer if (!value || typeof value !== "object") { return } Observer(value) } vm.$watch = (name, func) => { // 回調函數 vm.callback[name] = func // 設置data defineReactive(vm.$data, name) } // 綁定a,a若變化則執行回調方法 var va = {a:{c: "c"}, b:{c: "c"}} vm._data[va] = {a:{c: "c"}, b:{c: "c"}} vm.$watch("va", () => {console.log("已經成功被監聽啦")}) vm.$data.va = 1
在谷歌瀏覽器的console中粘貼以上代碼,然后回車發現,結果不出所料,va本身被監聽了,可以,我們試試va的內部屬性有沒有被監聽,改下vm.$data.va = 1為vm.$data.va.a = 1,結果發現報錯了
什么鬼?
我們又仔細檢查了代碼,WTF,原來我們在遞歸的時候,Object.defineProperty中的回調函數cb的key參數一直在發生變化,我們希望的是里面的屬性變化的時候執行的是我們事先定義好的回調函數~那么我們來改下方法,將一開始定義好的回調作為參數傳進去,確保每一層遞歸set的回調都是我們事先設置好的~
var vm = { _data: {}, $data: {}, callback: {}} var defineReactive = (obj, key, cb) => { // 一開始的時候是不設值的,所以,要在外面做一套observe var childObj = observe(vm._data[key], cb) Object.defineProperty(obj, key, { get() { return vm._data[key] }, set(newVal) { if (vm._data[key] === newVal) { return } // 如果值有變化的話,做一些操作 vm._data[key] = newVal // 執行回調 cb() // 如果set進來的值為復雜類型,再遞歸它,加上set/get childObj = observe(newVal) } }) } var Observer = (obj, cb) => { Object.keys(obj).forEach((key) =>{ defineReactive(obj, key, cb) }) } var observe = (value, cb) => { // 判斷是否為object類型,是就繼續執行Observer if (!value || typeof value !== "object") { return } Observer(value, cb) } vm.$watch = (name, func) => { // 回調函數 vm.callback[name] = func // 設置data defineReactive(vm.$data, name, func) } // 綁定a,a若變化則執行回調方法 var va = {a:{c: "c"}, b:{c: "c"}} vm._data.va = {a:{c: "c"}, b:{c: "c"}} vm.$watch("va", () => {console.log("又成功被監聽啦")}) vm.$data.va.a = 1
再執行一次以上代碼,發現內部的a屬性也被監聽到了,而且屬性值變化的時候執行了我們事先定義好的回調函數~嘻嘻嘻~
雖然實現了$watch的基本功能,但是和vue的源碼還是有一定的距離,特別是一些扁平化和模塊化的思想需要涉及到一些設計模式,其實我們在看源碼的時候,常常是逆著作者的思維走的,功能從簡單到復雜往往涉及到代碼的模塊化和解耦,使得代碼非常地分散,讀起來晦澀難懂,自己動手,從小功能代碼塊實現,然后結合源碼,對比思路,慢慢豐富,也不失為一種學習源碼的方式~
ps: 如果各位讀者看到本文的error或者由更好的優化建議,隨時聯系~
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/81965.html
摘要:所以,我們是不是應該寫一個消息訂閱器呢這樣的話,一觸發方法,我們就發一個通知出來,然后,訂閱這個消息的,就會怎樣。。。截止到現在,在我們只考慮最簡單情況下。。關于的新文章行代碼,理解和分析的響應式架構 本文能幫你做什么?。。好奇vue雙向綁定的同學,可以部分緩解好奇心還可以幫你了解如何實現$watch 前情回顧 我之前寫了一篇沒什么干貨的文章。。并且刨了一個大坑。。今天。。打算來填一天...
摘要:至此監聽器和訂閱者功能基本完成,后面再加上指令解析器的功能系列文章的目錄雙向綁定的實現原理系列一雙向綁定的實現原理系列二設計模式雙向綁定的實現原理系列三監聽器和訂閱者雙向綁定的實現原理系列四補充指令解析器 監聽器Observer和訂閱者Watcher 實現簡單版Vue的過程,主要實現{{}}、v-model和事件指令的功能 主要分為三個部分 github源碼 1.數據監聽器Obser...
摘要:至此監聽器和訂閱者功能基本完成,后面再加上指令解析器的功能系列文章的目錄雙向綁定的實現原理系列一雙向綁定的實現原理系列二設計模式雙向綁定的實現原理系列三監聽器和訂閱者雙向綁定的實現原理系列四補充指令解析器 監聽器Observer和訂閱者Watcher 實現簡單版Vue的過程,主要實現{{}}、v-model和事件指令的功能 主要分為三個部分 github源碼 1.數據監聽器Obser...
摘要:總結最后我們依照下圖參考深入淺出,再來回顧下整個過程在后,會調用函數進行初始化,也就是過程,在這個過程通過轉換成了的形式,來對數據追蹤變化,當被設置的對象被讀取的時候會執行函數,而在當被賦值的時候會執行函數。 前言 Vue 最獨特的特性之一,是其非侵入性的響應式系統。數據模型僅僅是普通的 JavaScript 對象。而當你修改它們時,視圖會進行更新。這使得狀態管理非常簡單直接,不過理解...
摘要:接下來,我們就一起深入了解的數據響應式原理,搞清楚響應式的實現機制。回調函數只是打印出新的得到的新的值,由執行后生成。及異步更新相信讀過前文,你應該對響應式原理有基本的認識。 前言 Vue.js 的核心包括一套響應式系統。 響應式,是指當數據改變后,Vue 會通知到使用該數據的代碼。例如,視圖渲染中使用了數據,數據改變后,視圖也會自動更新。 舉個簡單的例子,對于模板: {{ name ...
摘要:對于的動態數據綁定,經過反復地看源碼和博客講解,總算能夠理解它的實現了,心累分享一下學習成果,同時也算是做個記錄。 對于vue.js的動態數據綁定,經過反復地看源碼和博客講解,總算能夠理解它的實現了,心累~ 分享一下學習成果,同時也算是做個記錄。完整代碼GitHub地址:https://github.com/hanrenguang/Dynamic-data-binding。也可以到倉庫...
閱讀 1660·2021-11-16 11:41
閱讀 2458·2021-11-08 13:14
閱讀 3106·2019-08-29 17:16
閱讀 3079·2019-08-29 16:30
閱讀 1843·2019-08-29 13:51
閱讀 356·2019-08-23 18:38
閱讀 3223·2019-08-23 17:14
閱讀 630·2019-08-23 15:09