在 Vue
中,咱們可使用 $watch
觀測一個字段,當字段的值發生變化的時候執行指定的觀察者,以下:數組
1 var vm = new Vue({ 2 data: { 3 num:1 4 } 5 }) 6 vm.$watch('num',function() { 7 console.log('num被修改') 8 })
這時候,當咱們去修改 num 數值的時候,就會打印出來 'num被修改'。這個究竟是如何實現,怎麼打印出來的呢?函數
如今咱們先以另外一種方式,講解期中的道理。關鍵一個知識點: Object.definePropert; 不瞭解的先打開這先看下
spa
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
prototype
假設咱們 有下邊的數據code
1 var data = { 2 num: 1 3 }
咱們還有一個叫作 $watch
的函數,同時函數接受兩個參數;第一個參數是要觀測的字段,第二個參數是當該字段的值發生變化後要執行的函數,以下:對象
1 function $watch () {...} 2 $watch('num', () => { 3 console.log('修改了 num') 4 })
下邊經過 Object.defineProperty 實現下邊的功能:blog
1 Object.defineProperty(data, 'num', { 2 set () { 3 console.log('設置了num') 4 }, 5 get () { 6 console.log('讀取了 num') 7 } 8 })
經過 Object.defineProperty 咱們能夠輕鬆知道 num 被設置,和讀取了。但問題是如何讓$watch 方法知道,同時通知第二個參數函數呢?遞歸
有了上邊的想法,咱們就能夠大膽地思考一些事情,好比: 能不能在獲取屬性 num 的時候收集依賴,而後在設置屬性 num
的時候觸發以前收集的依賴呢?ip
1 // dep 數組就是咱們所謂的「筐」 2 const dep = [] 3 Object.defineProperty(data, 'num', { 4 set () { 5 // 當屬性被設置的時候,將「筐」裏的依賴都執行一次 6 dep.forEach(fn => fn()) 7 }, 8 get () { 9 // 當屬性被獲取的時候,把依賴放到「筐」裏 10 dep.push(fn) 11 } 12 })
上邊的 fn 來自哪裏? 又是在何時出發num 屬性的get() 呢?字符串
接下來須要在$watch()上下手:
1 // fn 是全局變量 2 let fn= null 3 function $watch (exp, callback) { 4 // 將 fn 的值設置爲 callback 5 fn = callback 6 // 讀取字段值 exp,觸發 get 函數 7 data[exp] 8 }
經過上邊調用$watch 方法,先給全局變量fn 設置爲回調函數,而後讀取data的屬性,num屬性的get方法中,收集callback, 這樣當num 變化時候能夠通知callback方法;
上邊的方法還有幾個問題須要思考:
1. 實現多個屬性監聽;2. data 某個屬性字段是對象時,3. 肯定屬性值發生變化,纔去出發回調;
要解決上述問題又要怎麼去作呢? 下邊封裝一個方法:
1 function observe(data) { 2 for (let key in data) { 3 const dep = [] 4 let val = data[key] 5 // 若是 val 是對象,遞歸調用 observe 函數將其轉爲訪問器屬性 6 const nativeString = Object.prototype.toString.call(val) 7 if (nativeString === '[object Object]') { 8 observe(val) 9 } 10 Object.defineProperty(data, key, { 11 set:function setter (newVal) { 12 if (newVal === val) return 13 val = newVal 14 dep.forEach(fn => fn()) 15 }, 16 get:function getter () { 17 dep.push(fn) 18 return val 19 } 20 }) 21 } 22 } 23 observe(data)
Vue中$watch方法第一個參數能夠是 data 中的某個屬性,function, 以及data屬性中 對象的屬性 ; 那麼這個watch是如何實現呢? 下邊咱們改變下$watch();
1 function $watch (exp, callback) { 2 fn= fcallback 3 let pathArr, 4 obj = data 5 if (typeof exp === 'function') { 6 exp() 7 return 8 } 9 // 檢查 exp 中是否包含 . 10 if (/\./.test(exp)) { 11 // 將字符串轉爲數組,例:'a.b' => ['a', 'b'] 12 pathArr = exp.split('.') 13 // 使用循環讀取到 data.a.b 14 pathArr.forEach(p => { 15 obj = obj[p] 16 }) 17 return 18 } 19 data[exp] 20 }
先判斷第一個參數 時候爲function ,若是爲function,則直接調用第一個參數;若是爲obj.a 等形式;則進行split分割一層層出發,收集fn;
最後完整版下以下:
1 var fn = null; 2 var data = {names:"xiaoming", age:19,obj: {a:1,b:2,c:{c:1,d:2}}} 3 function observe (data) { 4 for (let key in data) { 5 const dep = [] 6 let val = data[key] 7 // 若是 val 是對象,遞歸調用 observe 函數將其轉爲訪問器屬性 8 const nativeString = Object.prototype.toString.call(val) 9 if (nativeString === '[object Object]') { 10 observe(val) 11 } 12 Object.defineProperty(data, key, { 13 set: setter(newVal) { 14 if (newVal === val) return 15 val = newVal 16 dep.forEach(fn => fn()) 17 }, 18 get: getter() { 19 dep.push(fn) 20 return val 21 } 22 }) 23 } 24 } 25 26 observe(data) 27 28 function $watch (exp, callback) { 29 fn = callback 30 let pathArr, 31 obj = data 32 if (typeof exp === 'function') { 33 exp() 34 return 35 } 36 // 檢查 exp 中是否包含 . 37 if (/\./.test(exp)) { 38 // 將字符串轉爲數組,例:'a.b' => ['a', 'b'] 39 pathArr = exp.split('.') 40 // 使用循環讀取到 data.a.b 41 pathArr.forEach(p => { 42 obj = obj[p] 43 }) 44 return 45 } 46 data[exp] 47 } 48 49 $watch('names',function() { 50 console.log('name change') 51 })
運行:在改變 data.names = '小明';
結果:
固然Vue實現確定不會如此簡單,接下來有空慢慢細講,(*^▽^*)