Vue2.0 核心之響應式流程

Vue2.0 核心之響應式流程

看了部分Vue源碼分析或運行機制的文章,接收到這些信息:javascript

  • 使用Object.defineProperty實現響應監聽,
  • 使用DepWacther實現依賴收集追蹤
  • 使用Virtual Dom、高效diff算法實現最小化更新

對整個流程仍是沒有理解,或者寫了流程,也是做者本身實現的一套,不是Vue的源碼。爲了熟悉Vue真正的流程花了點時間看了Vue的源碼,經過debugger(瀏覽器版本)本身梳理了一遍。vue

如下代碼來源自Vue,只作了刪減,保留核心。java

new Vue(options)的執行流程,debugger的主體流程以下:react

  1. beforeCreate Hook
  2. initSate()git

    • initData() -- oberve
  3. created Hook
  4. vm.$mount(vm.$options.el)github

    1. 根據template解析出render function
    2. 聲明 update function算法

      updateComponent = function () {
            vm._update(vm._render(), hydrating);
        };
    3. new Watcher(vm, updateComponent, noop,{before...}
    4. mounted Hook

不考慮updateComponentHooks剩下:promise

  1. initData() -- oberve
  2. new Watcher(vm, updateComponent, noop,{before...}

這兩個步驟實現了依賴的收集與通知、以及初始化,我在github上的項目learnVue截取了Vue核心代碼進行流程學習,如下是我簡化後的代碼:瀏覽器

import {Observer} from './observer';
    import  Watcher  from "./watcher";
    let noop =function(){};
    let vm={
        _watchers:[],
        data :{
            name:'jack',
            age:12
        }
        // render:new Function("with(this){return _c('div',{attrs:{\"id\":\"app\"}},[_v(_s(name))])}")
    }
    new Observer(vm.data);

    new Watcher(vm,function(){
        //update function 
        var name=vm.data.name;
        var age=vm.data.age;
        console.log(name,age)
    },noop);

下面代碼,控制檯會正常輸出lili,10:app

setTimeout(()=>{
    vm.data.name='lucy';
    vm.data.age=10;
    vm.data.name='lili';
},1000)

依賴收集

什麼優化都不作Object.defineProperty 定義set便可實現同步。後果是任一屬性更新都會同步dom,形成性能浪費。Vue使用訂閱/觀察模式作了第一步優化。

打開vue項目下observer文件夾,能夠看到如下幾個文件:

  • index.js(observer) //植入響應式鉤子
  • dep.js //依賴管理
  • watcher.js //觀察者,同步UI

index.js 核心代碼:

const dep = new Dep()
    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: function reactiveGetter() {
            const value = val
            if (Dep.target) {
                dep.depend()
            }
            return value
        }
    })

調用dep.js 中的方法

depend () {
    if (Dep.target) {
      Dep.target.addDep(this)
    }
  }

Dep.target 又是什麼呢?這就是new Watcher的做用了。
watcher.js:

constructor(
        vm,
        expOrFn,
        cb
    ) {
        this.vm = vm
        this.getter = expOrFn
        this.value = this.get() //初始化
    }
    get() {
        pushTarget(this) //推送 Dep.target
        let value
        const vm = this.vm
        value = this.getter.call(vm, vm) //觸發 get鉤子

        popTarget() //還原 Dep.target
        this.cleanupDeps()
        return value
    }

Watcher.get()函數中進行依賴收集,畫了一下運行流程(綠色和藍色線條):

經過這個流程咱們能夠看到,Watcher 就是負責把內存的值同步到UI的。

異步更新

同步更新會致使大量的重繪,從而致使UI性能問題。Vue採用異步更新策略作了第二步優化,把一個批次的修改一次更新給UI。來看下index.js中set方法。

set: function reactiveSetter(newVal) {
            const value = val
            /* eslint-disable no-self-compare */
            if (newVal === value || (newVal !== newVal && value !== value)) {
                return
            }
            val = newVal
        dep.notify()
    }

dep.js

notify () {
    // stabilize the subscriber list first
    const subs = this.subs.slice()
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update() //Watcher.update
    }
  }

watcher.js

/**
     * Subscriber interface.
     * Will be called when a dependency changes.
     */
    update() {
        /* istanbul ignore else */
        queueWatcher(this)
    }

queueWatcherWatcher給了queueWatcher //scheduler.js 調度中心,異步的實現,優先promise,降級setTimeout

export const nextTick = (function () {
    const callbacks = []
    let pending = false
    let timerFunc
  
    function nextTickHandler () {
      pending = false
      const copies = callbacks.slice(0)
      callbacks.length = 0
      for (let i = 0; i < copies.length; i++) {
        copies[i]()
      }
    }
  
    // the nextTick behavior leverages the microtask queue, which can be accessed
    // via either native Promise.then or MutationObserver.
    // MutationObserver has wider support, however it is seriously bugged in
    // UIWebView in iOS >= 9.3.3 when triggered in touch event handlers. It
    // completely stops working after triggering a few times... so, if native
    // Promise is available, we will use it:
    /* istanbul ignore if */
    if (typeof Promise !== 'undefined') {
      var p = Promise.resolve()
      var logError = err => { console.error(err) }
      timerFunc = () => {
        p.then(nextTickHandler).catch(logError)
      }
    }else {
      // fallback to setTimeout
      /* istanbul ignore next */
      timerFunc = () => {
        setTimeout(nextTickHandler, 0)
      }
    }
  
    return function queueNextTick (cb, ctx) {
      let _resolve
      callbacks.push(() => {
        if (cb) cb.call(ctx)
        if (_resolve) _resolve(ctx)
      })
      if (!pending) {
        pending = true
        timerFunc()
      }
      if (!cb && typeof Promise !== 'undefined') {
        return new Promise(resolve => {
          _resolve = resolve
        })
      }
    }
  })()

流程圖,黃色線條:

未完待續

暫時只研究了內存 => UIUI => 內存 之後再研究。框架的彎彎繞繞但沒有一行冗餘代碼,Vue賊6

相關文章
相關標籤/搜索