Vue主要原理最簡實現與邏輯梳理

toBeTheLight發表於2018-04-05

Vue的主要原理中主要用到了定義的這麼幾個函式Dep,Watcher,observer。
我們來使用這幾個函式簡單的實現一下vue建構函式資料繫結和相互依賴部分,梳理一下它們之間的關係。
省略了編譯部分和proxy代理與其他的一些複雜邏輯。

Dep

Dep是依賴類,簡要實現為

class Dep {
  constructor () {
    // 放當時屬性的觀察者
    this.subs = []
  }
}
// target 用來掛載當時的watcher觀察者
Dep.target = null

observer

做屬性劫持,並做點其他事情


function observer (vm, key, val) {
  let dep = new Dep()
  Object.defineProperty(vm, key, {
    /**
     * get主要做兩個事情
     * 1. 收集觀察當前key的wathcer(即依賴當前key的操作)
     * 2. 獲取值
     */
    get () {
      // 這是作用1
      if (Dep.target) {
        dep.subs.push(Dep.target)
      }
      // 這是作用2
      return val
    },
    /**
     * set也是兩個事情
     * 1. 修改目標值
     * 2. 執行依賴當前key的watcher
     */
    set (newVal) {
      // 這是作用1
      val = newVal
      // 這是作用2
      for(cb of dep.subs) {
        cb.call(vm)
      }
    }
  })
}

Watcher

Watcher是觀察者類,用來建立依賴某屬性的操作(如指令,渲染,計算屬性等)

class Watcher {
  /**
   * vm: 例項
   * cb: 依賴某屬性的操作函式
   */
  constructor (vm, cb) {
    // 把當前的操作掛載到Dep上
    Dep.target = cb
    /**
     * 執行操作,兩個作用
     * 1. 進行操作的初始化
     * 2. 觸發屬性的get方法,使當前cb被收集
     */
    cb.call(vm)
    Dep.target = null
  }
}

demo

那麼我們就使用上面定義好的函式寫個例子

<div>
  <p class="text"></p>
<div>
let vm = new Vue({
  // 假設有data
  data: {msg: 1},
  // 有某個v-text操作,我們抽象為vText函式,依賴屬性msg(代表所有依賴其他屬性的操作)
  renderFun: {
    vText () {
      document.querySelector(`.text`).innerText = this.msg
    }
  }
})
// 修改vue例項的值,觀察變化
vm.msg = 333

那麼我們也寫一個vue的簡易建構函式

class Vue {
  constructor (options) {
    let data = options.data
    let renderFun = options.renderFun
    // initData
    Object.keys(data).forEach(key => {
      observer(this, key, data[key])
    })
    // 模擬計算屬性,watcher,指令等依賴屬性的操作
    Object.keys(renderFun).forEach(key => {
      new Watcher(this, renderFun[key])
    })
  }
}

執行過程

完整的程式碼可以看demo部分的兩個連結

  1. 建立vue例項,執行new Vue()
  2. 對data進行初始化,對data中屬性進行屬性劫持

    • 劫持過程中,在閉包內建立對當前屬性的依賴佇列(dep.subs)和值(val)。get進行觀察者watcher的收集和值得獲取;set進行值的更新和依賴佇列中watcher的執行
  3. 對編譯過程中如computedwatcher模板編譯過程中的指令函式進行初始化,我們以renderFun代替
  4. 針對renderFun中的每個功能函式進行new Watcher()工作
  5. vText為例子,在new Wathcer()過程中

    1. vText掛載到全域性通用的Dep.target
    2. 執行vText,其中有讀vm.msg的操作,則觸發msg屬性的get,進入Dep.target判斷,將Dep.targetvText收集進msgsubs依賴佇列中,此時vText執行完畢,頁面innetText被修改
    3. Dep.target置空
  6. 執行vm.msg = 333,則觸發msgset

    1. set先修改msg的值
    2. 再執行msg依賴佇列中的所有watcher的函式,即vText,頁面的innerText被同步更新

總結

總之幾者的關係就是在observerget中將對當前屬性的watcher收集進dep,在observerset中執行收集到的watcher

而vue的真正的執行過程絕不是上面寫的這麼簡單,比如watcher的執行就絕不是簡單的遍歷執行,而且還對observer進行了很大程度的簡化。我們還省略了諸如_proxydefineReactive等出現頻率較高的函式。寫這樣一個最簡實現主要是為了梳理一下主幹,降低閱讀原始碼的難度。

??????????????????????????????????

相關文章