Vue源碼------------- 數據響應系統的基本思路

    在 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/definePropertyprototype

     假設咱們 有下邊的數據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實現確定不會如此簡單,接下來有空慢慢細講,(*^▽^*)

相關文章
相關標籤/搜索