国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 開發 > JS > 正文

簡單實現Vue的observer和watcher

2024-05-06 16:33:59
字體:
來源:轉載
供稿:網友

非庖丁瞎解牛系列~ =。=

在日常項目開發的時候,我們將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的源碼還是有一定的距離,特別是一些扁平化和模塊化的思想需要涉及到一些設計模式,其實我們在看源碼的時候,常常是逆著作者的思維走的,功能從簡單到復雜往往涉及到代碼的模塊化和解耦,使得代碼非常地分散,讀起來晦澀難懂,自己動手,從小功能代碼塊實現,然后結合源碼,對比思路,慢慢豐富,也不失為一種學習源碼的方式~

下一篇將會結合源碼來淺談下vue的watcher和observer

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持VeVb武林網。


注:相關教程知識閱讀請移步到JavaScript/Ajax教程頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 齐河县| 昭通市| 九江市| 鲜城| 陕西省| 宣化县| 娱乐| 三门峡市| 柘荣县| 沙田区| 广宗县| 亳州市| 会同县| 湖北省| 水城县| 临西县| 资兴市| 长乐市| 临洮县| 沈丘县| 德令哈市| 长宁县| 象山县| 内江市| 马公市| 东山县| 海丰县| 温宿县| 西城区| 东光县| 上饶县| 无为县| 中山市| 绥中县| 巴南区| 三门峡市| 山丹县| 安福县| 裕民县| 大同县| 卫辉市|