【Vue 2.0】核心源碼解讀 -- 不按期更新

介紹

關於 Vue.js 的原理一直以來都是一個話題。通過幾天的源碼學習和資料介紹,我將一些我的理解的經驗給寫下來,但願可以與你們共勉。javascript

附上GITHUB源碼地址, 若是有任何不解 能夠在 文章下面提出或者寫下issue, 方便你們回答和學習, 有興趣能夠Star.
最後附上 LIVE DEMOvue

簡單圖解 Vue.js 內置對象

vue雙向綁定原理

構造實例對象

應用建立時須要使用的構造函數對象, data 爲咱們的數據模型java

const vm = new Vue({
  data: {
      foo: 'hello world'
  }
});

數據雙向綁定

被觀察者 observe

被觀察對象,data屬性裏的值的變化,get時檢查是否有新的觀察員須要加入觀察員集合, set時通知觀察員集合裏的觀察員更新視圖,被觀察者有一個觀察員的集合對象。react

觀察員集合對象 Dep

一個觀察員的收集器, depend()負責將當前的 Dep.target 觀察員加入觀察員集合, data 中的每一項數據都會有對應的閉包dep對象, 數據對象會有一個內置的dep對象,用來通知嵌套的數據對象變化的狀況。git

觀察員 watcher

由模板解析指令建立的觀察員, 負責模板中的更新視圖操做。保留舊的數據,以及設置鉤子函數 update(), 等待被觀察者數據通知更新,對比新的value與舊數據, 從而更新視圖。github

數據代理 proxyData

咱們的關注點在與建立後的vm, 其 options.data, 被掛載至vm._data, 同時被代理至 vm 上, 以致於 vm._data.foo 等價於 vm.foo, 代理函數代碼以下:緩存

const noop = function () {}
const sharedPropertyDefinition = {
  enumerable: true,
  configurable: true,
  get: noop,
  set: noop
}

function proxy (target, sourceKey, key) {
  sharedPropertyDefinition.get = function proxyGetter () {
    return this[sourceKey][key]
  }
  sharedPropertyDefinition.set = function proxySetter (val) {
    this[sourceKey][key] = val
  }
  Object.defineProperty(target, key, sharedPropertyDefinition)
}

// initState 時執行 initData
function initData () {
    
    const keys = Object.keys(data)
    let i = keys.length
    while (i--) {
      const key = keys[i]
      // key不以 $ 或 _開頭
      if (!isReserved(key)) {
        proxy(vm, `_data`, key)
      }
    }
    // do something
}

數據劫持 defineProperty

/**
 * Define a reactive property on an Object.
 */
function defineReactive(obj, key, val) {

  // 觀察員集合
  const dep = new Dep();
  
  // data 內屬性描述 
  const property = Object.getOwnPropertyDescriptor(obj, key);

  // 屬性不可再次修改
  if (property && property.configurable === false) {
    return;
  }

  // 屬性預約義 getter/setters
  const getter = property && property.get;
  const setter = property && property.set;

  // 若是val爲對象, 獲取val的 被觀察數據對象 __ob__
  let childOb = observe(val);
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter() {
      // 被觀察數據被使用時, 獲取被觀察員最新的數據
      const value = getter ? getter.call(obj) : val
      
      // 觀察員在new時或使用 get()時, 注入給被觀察員對象集合
      if (Dep.target) {
        // 將當前的 watcher 傳遞給 觀察員
        dep.depend();
        if (childOb) {
          // 將當前的 watcher 傳遞給子對象的 觀察員
          childOb.dep.depend();
        }
      }
      return val;
    },
    set: function reactiveSetter(newVal) {
      const value = getter ? getter.call(obj) : val;
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return;
      }
      if (setter) {
        setter.call(obj, newVal);
      } else {
        val = newVal;
      }
      // 新value設置被觀察者對象 __ob__
      childOb = observe(newVal);
      
      // 通知數據對象依賴的觀察員, 更新 update()
      dep.notify();
    }
  });
}

計算屬性介紹

初始化執行過程

計算屬性圖形介紹

計算屬性的定義和使用閉包

var vm = new Vue({
    data: {
        firstname: 'li',
        lastname: 'yanlong'
    },
    computed: {
        fullname () {
            return this.firstname + this.lastname;
        }
    }
});
console.log(vm.fullname);

核心代碼解讀

const computedWatcherOptions = {lazy: true};
function initComputed (vm, computedOptions) {
    // 建立計算屬性對應的觀察員對象
    // 獲取計算屬性時收集 內置數據對象的 dep
    const watchers = vm._computedWatchers = Object.create(null)
    
    for (const key in computed) {
        const userDef = computed[key]
        const getter = typeof userDef === 'function' ? userDef : userDef.get
        // create internal watcher for the computed property.
        watchers[key] = new Watcher(
          vm,
          getter || noop,
          noop,
          computedWatcherOptions
        );
        if (!(key in vm)) {
          defineComputed(vm, key, userDef)
        } 
    }
}

function defineComputed (target, key, userDef) {
    // 若是不爲服務端渲染,則使用緩存value
    const shouldCache = !isServerRendering()
    
    // sharedPropertyDefinition 共享屬性配置
    if (typeof userDef === 'function') {
      sharedPropertyDefinition.get = shouldCache
        ? createComputedGetter(key)
        : userDef
      sharedPropertyDefinition.set = noop
    } else {
      sharedPropertyDefinition.get = userDef.get
        ? shouldCache && userDef.cache !== false
          ? createComputedGetter(key)
          : userDef.get
        : noop
      sharedPropertyDefinition.set = userDef.set
        ? userDef.set
        : noop
    }
    // 給 vm對象定義計算屬性 
    Object.defineProperty(target, key, sharedPropertyDefinition)
}

// 建立
function createComputedGetter (key) {
  return function computedGetter () {
    const watcher = this._computedWatchers && this._computedWatchers[key]
    if (watcher) {
      // 計算屬性的 watcher 有數據更新過, 從新計算
      if (watcher.dirty) {
        watcher.evaluate()
      }
      
      // 視圖指令 使用了計算屬性
      // 將計算屬性的watcher依賴傳遞給視圖指令的 watcher
      if (Dep.target) {
        // 源碼地址
        // https://github.com/vuejs/vue/blob/master/src/core/observer/watcher.js#L210
        watcher.depend()
      }
      return watcher.value
    }
  }
}

計算屬性知識點介紹

1. 計算屬性的 watcher 對象
計算屬性函數在讀取它自己的value時, 使用一個watcher觀察員進行代理. 經過對原始數據的劫持, 將watcher 觀察員添加到原始數據的dep依賴集合中. mvvm

2. deps的數據對象發生更新
舉例,若是firstname 或者 lastname 任意一個更新,那麼就會設置計算屬性的watcher.dirty = true, 而當其它指令或者函數使用,計算屬性會從新計算值,若是是視圖指令,還會從新該指令的watcher的數據對象依賴。函數

Watcher 觀察員種類

目前瞭解狀況來看, 主要分三類

  1. 視圖指令的 watcher
  2. 計算屬性的 watcher
  3. 用戶自定義的 watcher
相關文章
相關標籤/搜索