vue中監聽object數據變化的基本原理

# 簡略版+本身的註釋vue

// 判斷一個變量是不是對象
function isObject(obj) {
  return obj.constructor === Object
}
class Observer {
  constructor(value) {
    this.value = value;
    if (!arr.isArray(value)) {
      this.walk(value);
    }
  }
  walk(obj) {
    const keys = Object.keys(obj);
    // 循環將obj中的每個屬性轉換成getter/setter進行變化追蹤
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i], obj[keys[i]]);
    }
  }
}
function defineReactive(data, key, val) {
  if (isObject(val)) {
    new Observer(val); // 進行遞歸調用
  }
  let dep = new Dep();
  Object.defineProperty(data, key, {
    configurable: true,
    enumerable: true,
    get: function () {
      dep.depend();
      return val;
    },
    set: function (newVal) {
      if (val === newVal) return
      // 若是賦值的新值也是一個對象 也須要進行偵測
      if (isObject(newVal)) {
        new Observer(val); // 進行遞歸調用
      }
      val = newVal;
      dep.notify(); // 通知全部的訂閱者,數據要被修改了,作出相應的行爲(也就是執行對應的回調函數)
    }
  })
}
class Dep {
  constructor() {
    this.subs = [] // 這個裏面存放的是Watch實例對象
  }
  addSub(sub) {
    this.subs.push(sub); // 在這個地方收集訂閱者
  }
  removeSub(sub) {
    remove(this.subs, sub);
  }
  depend() {
    if (window.target) {
      this.addSub(window.target); // 在這個地方觸發depend方法,進行收集訂閱者
    }
  }
  notify() {
    const subs = this.subs.slice();
    for (let i = 0; i < subs.length; i++) {
      subs[i].update(); // 在這個地方執行回調函數
    }
  }
}
function remove(arr, item) {
  if (arr.length) {
    const index = arr[item];
    if (index > -1) {
      return arr.splice(index, 1);
    }
  }
}
class Watcher {
  constructor(vm, expOrFn, cb) {
    this.vm = vm;
    this.getter = parsePath(expOrFn);
    this.cb = cb;
    this.value = this.get(); // 獲取expOrFn中的值 在這個內部同時會觸發getter,從而在dep中添加本身
  }
  get() {
    window.target = this; // 將watch實例對象賦值給全局的target變量上
    let value = this.getter.call(this.vm, this.vm); // 該代碼的做用很關鍵
    // 若是expOrFn直接是一個表達式不是一個函數 eg: 'name', 'age' 假設只有一個屬性 不是這種的 'name.a.b'
    // 咱們就能夠直接下面這樣寫,只是爲了測試理解,vue.js源碼處理更加全面
    // let value = this.vm[this.expOrFn] // vm就是要監聽的數據,固然expOrFn要在constructor中掛在到this身上
    window.target = undefined;
    return value;
  }
  update() {
    const oldValue = this.value;
    this.value = this.get();
    this.cb.call(this.vm, this.value, oldValue);
  }
}
// 該方法的做用是將 'name.a.b'這種表達式的值取出來
// 也就是經過該方法返回的是 obj['name']['a']['b]的值
function parsePath(path) {
  const bailRE = /[^\w.$]/;
  const segments = path.split('.')
  // 閉包
  return function (obj) {
    for (let i = 0; i < segments.length; i++) {
      if (!obj) return
      obj = obj[segments[i]] // 取出屬性的值 因此這個地方會執行getter
    }
    return obj; // 返回屬性的值
  }
}
<script>
let btns = document.getElementsByTagName('button');
// 定義數據
let person = {};
// 定義響應式數據的屬性
defineReactive(person, 'name', '喬峯')
// 監聽數據
let w = new Watcher(person, 'name', function (newVal, oldVal) {
  console.log('數據發生變化了')
  console.log(newVal, oldVal);
})
// 1.取值操做
person.name
// 2.改變數據
btns[0].onclick = function () {
  person.name = '小龍女';
}

# 總結c#

1.在實例化一個Watcher對象時,其get方法中會觸發defineReactive中的getter訪問器,在其getter訪問器中會執行dep.depend()方法,dep.depend()方法會調用this.subs.push(sub)方法,從而收集依賴,也就是在subs 中存放的是當前的Watcher實例對象閉包

2.Watch實例對象自身必須有一個update方法ide

3.當數據發生變化時,會觸發defineReactive中的setter訪問器,在其setter訪問器中會調用subs[i].update()方法,其中的每個subs[i]就是watch實例對象,從而執行了update方法,在update方法中執行了this.cb.call(this.vm, this.value, oldValue)回調函數函數

3.Observer 類是用來將傳遞進來的數據遍歷轉換成響應式數據,也就是轉換成getter/setter的形式進行偵測測試

4.在使用的時候,咱們首先須要調用defineReactive方法,來建立一個響應式數據,而後再調用Watcher方法來監聽這個響應式數據的變化,而後就能作到數據驅動,或者叫響應式數據this

______若有不對,請指正_______spa

相關文章
相關標籤/搜索