當面試官問你Vue響應式原理,你能夠這麼回答他

看過vue官方文檔的同窗,對這張圖應該已然至關熟悉了。javascript

vue的響應式是如何實現的?vue

聽過太多回答,經過Object.defineProperty,但是再詳細的問時,對方渾然不知。java

先擼爲敬

const Observer = function(data) {
  // 循環修改成每一個屬性添加get set
  for (let key in data) {
    defineReactive(data, key);
  }
}

const defineReactive = function(obj, key) {
  // 局部變量dep,用於get set內部調用
  const dep = new Dep();
  // 獲取當前值
  let val = obj[key];
  Object.defineProperty(obj, key, {
    // 設置當前描述屬性爲可被循環
    enumerable: true,
    // 設置當前描述屬性可被修改
    configurable: true,
    get() {
      console.log('in get');
      // 調用依賴收集器中的addSub,用於收集當前屬性與Watcher中的依賴關係
      dep.depend();
      return val;
    },
    set(newVal) {
      if (newVal === val) {
        return;
      }
      val = newVal;
      // 當值發生變動時,通知依賴收集器,更新每一個須要更新的Watcher,
      // 這裏每一個須要更新經過什麼判定?dep.subs
      dep.notify();
    }
  });
}

const observe = function(data) {
  return new Observer(data);
}

const Vue = function(options) {
  const self = this;
  // 將data賦值給this._data,源碼這部分用的Proxy因此咱們用最簡單的方式臨時實現
  if (options && typeof options.data === 'function') {
    this._data = options.data.apply(this);
  }
  // 掛載函數
  this.mount = function() {
    new Watcher(self, self.render);
  }
  // 渲染函數
  this.render = function() {
    with(self) {
      _data.text;
    }
  }
  // 監聽this._data
  observe(this._data);  
}

const Watcher = function(vm, fn) {
  const self = this;
  this.vm = vm;
  // 將當前Dep.target指向本身
  Dep.target = this;
  // 向Dep方法添加當前Wathcer
  this.addDep = function(dep) {
    dep.addSub(self);
  }
  // 更新方法,用於觸發vm._render
  this.update = function() {
    console.log('in watcher update');
    fn();
  }
  // 這裏會首次調用vm._render,從而觸發text的get
  // 從而將當前的Wathcer與Dep關聯起來
  this.value = fn();
  // 這裏清空了Dep.target,爲了防止notify觸發時,不停的綁定Watcher與Dep,
  // 形成代碼死循環
  Dep.target = null;
}

const Dep = function() {
  const self = this;
  // 收集目標
  this.target = null;
  // 存儲收集器中須要通知的Watcher
  this.subs = [];
  // 當有目標時,綁定Dep與Wathcer的關係
  this.depend = function() {
    if (Dep.target) {
      // 這裏其實能夠直接寫self.addSub(Dep.target),
      // 沒有這麼寫由於想還原源碼的過程。
      Dep.target.addDep(self);
    }
  }
  // 爲當前收集器添加Watcher
  this.addSub = function(watcher) {
    self.subs.push(watcher);
  }
  // 通知收集器中所的全部Wathcer,調用其update方法
  this.notify = function() {
    for (let i = 0; i < self.subs.length; i += 1) {
      self.subs[i].update();
    }
  }
}

const vue = new Vue({
  data() {
    return {
      text: 'hello world'
    };
  }
})

vue.mount(); // in get
vue._data.text = '123'; // in watcher update /n in get
複製代碼

這裏咱們用不到100行的代碼,實現了一個簡易的vue響應式。固然,這裏若是不考慮期間的過程,我相信,40行代碼以內能夠搞定。可是我這裏不想省略,爲何呢?我怕你把其中的過程自動忽略掉,怕別人問你相關東西的時候,明明本身看過了,卻被懟的啞口無言。總之,我是爲了你好,多喝熱水。markdown

Dep的做用是什麼?

依賴收集器,這不是官方的名字蛤,我本身起的,爲了好記。app

用兩個例子來看看依賴收集器的做用吧。函數

  • 例子1,毫無心義的渲染是否是不必?post

    const vm = new Vue({
        data() {
            return {
                text: 'hello world',
                text2: 'hey',
            }
        }
    })
    複製代碼

    vm.text2的值發生變化時,會再次調用render,而template中卻沒有使用text2,因此這裏處理render是否是毫無心義?測試

    針對這個例子還記得咱們上面模擬實現的沒,在Vuerender函數中,咱們調用了本次渲染相關的值,因此,與渲染無關的值,並不會觸發get,也就不會在依賴收集器中添加到監聽(addSub方法不會觸發),即便調用set賦值,notify中的subs也是空的。OK,繼續迴歸demo,來一小波測試去印證下我說的吧。this

    const vue = new Vue({
      data() {
        return {
          text: 'hello world',
          text2: 'hey'
        };
      }
    })
    
    vue.mount(); // in get
    vue._data.text = '456'; // in watcher update /n in get
    vue._data.text2 = '123'; // nothing
    複製代碼
  • 例子2,多個Vue實例引用同一個data時,通知誰?是否是應該倆都通知?spa

    let commonData = {
      text: 'hello world'
    };
    
    const vm1 = new Vue({
      data() {
        return commonData;
      }
    })
    
    const vm2 = new Vue({
      data() {
        return commonData;
      }
    })
    
    vm1.mount(); // in get
    vm2.mount(); // in get
    commonData.text = 'hey' // 輸出了兩次 in watcher update /n in get
    複製代碼

但願經過這兩個例子,你已經大概清楚了Dep的做用,有沒有原來就那麼回事的感受?有就對了。總結一下吧(如下依賴收集器實爲Dep):

  • vuedata初始化爲一個Observer並對對象中的每一個值,重寫了其中的getsetdata中的每一個key,都有一個獨立的依賴收集器。
  • get中,向依賴收集器添加了監聽
  • 在mount時,實例了一個Watcher,將收集器的目標指向了當前Watcher
  • data值發生變動時,觸發set,觸發了依賴收集器中的全部監聽的更新,來觸發Watcher.update

若是看完還以爲不夠過癮,能夠看看筆者的其餘文章

手拉手帶你過一遍vue部分源碼

vue對template作了什麼

相關文章
相關標籤/搜索