深刻Vue響應式原理

深刻Vue.js響應式原理

1、建立一個Vue應用

new Vue({
  data() {
    return {
      name: 'yjh',
    };
  },
  router,
  store,
  render: h => h(App),
}).$mount('#app');

 

2、實例化一個Vue應用到底發生了什麼?

  1. this._init()
  2. callHook(vm, 'beforeCreate')
  3. observe(vm._data)
vm._data = vm.$options.data()

proxy(vm, _data, key) 代理到vm上訪問html

function proxy(vm, _data, key)() {
  Object.defineProperty(target, key, {
    get() {
      return vm._data.key
    },
    set(val) {
      vm._data.key = val
    }
  })
}
  1. callHook(vm, 'created')
  2. mountComponent(vm.$mount執行後執行mountComponent)
  3. callHook(vm, 'beforeMount')
  4. new Watcher(vm, updateComponent)
const updateComponent = () => {
  // 建立虛擬dom
  const vnode = vm._render()

  // 建立虛擬dom的過程等同於以下代碼行
  // const vnode = vm.$options.render.call(vm, vm.$createElement)

  // 更新$el
  vm._update(vnode)
}
  1. callHook(vm, 'mount')

在以上發生的行爲當中,第3步與第7步二者相輔相成;也是咱們最須要關心的,弄清楚這二者,vue響應式原理就基本掌握了vue

3、如何追蹤數據變化

咱們都知道 數據發生變化視圖也隨之更新,那麼首先咱們得知道如何監聽數據的變化node

class Observer {
  constructor(value) {
    this.value = value
    this.walk(value)
  }

  walk(obj) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i])
    }
  }
}
function defineReactive(obj, key) {
  Object.defineProperty(obj, key, {
    get() {
      // 數據被訪問
      return obj.key
    },
    set(val) {
      if (val === obj.key) {
        return
      }
      // 數據更新了
      obj.key = val
    }
  })
}

4、定義一個發佈訂閱的Dep類

當咱們在建立虛擬dom的過程當中,也就是執行vm.$createElement方法,可能會在多個地方使用到同一個數據字段(如:vm.name),即多個訂閱者訂閱了name的更新,所以在Vue中定義了一個發佈訂閱的Dep類react

class Dep {
  constructor() {
    this.subs = []
  }

  addSub(sub) {
    this.subs.push(sub)
  }

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

  notify() {
    this.subs.forEach(sub => sub.update())
  }

  removeSub(sub) {
    const i = this.subs.findIndex(sub)
    if (i > -1) {
      this.subs.splice(i, 1)
    }
  }
}

 

5、數據訂閱者

訂閱數據更新的究竟是誰,咱們先看看以下場景git

<!-- 場景1 -->
<div>名字:{{ userInfo.name }},全名:{{ fullName }}</div>
export default {
  data() {
    return {
      userInfo: {
        name: 'junhua',
      },
    }
  },
  mounted() {
    // 場景2
    this.$watch('name', (newVal, val) => {
      // ...
    })
  },
  // 場景2
  watch: {
    name(newVal, val) {
      // ...
    }
  },
  computed() {
    // 場景3
    fullName() {
      return `yang${this.userInfo.name}`
    }
  }
}

 

從上面示例代碼看,訂閱數據更新的場景有:github

  1. 模版插值 :new Watcher(vm, updateComponent)數據發生變化,更新組件
  2. vm.$watch : 監聽單個數據作一些邏輯操做
  3. computed使用場景:計算屬性

所以數據訂閱者包含一個參數expOrFn([Function|String]),數據更新後須要執行的callback,以下:app

class Watcher {
  constructor(vm, expOrFn, cb) {
    this.vm = vm
      if (typeof expOrFn === 'function') {
      this.getter = expOrFn
    } else {
      this.getter = parsePath(expOrFn)
    }
    this.cb = cb || () => {}
    this.value = this.get()
  }

  get() {
    Dep.target = this
    const value = this.getter.call(this.vm, this.vm)
    Dep.target = undefined
    return value
  }

  update() {
    const val = this.value
    const newVal = this.get()
    this.cb.call(this.vm, newVal, val)
  }
}

 

6、最終的觀察者Observer

class Observer {
  constructor(value) {
    this.value = value
    this.walk(value)
  }

  walk(obj) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i],)
    }
  }
}

function defineReactive(obj, key) {
  const dep = new Dep()
  Object.defineProperty(obj, key, {
    get() {
      // 依賴收集,收集訂閱者Watcher實例
      dep.depend()
      // 數據被訪問
      return obj.key
    },
    set(val) {
      if (val === obj.key) {
        return
      }
      // 數據更新了
      obj.key = val
      // 通知訂閱者Watcher實例更新
      dep.notify()
    }
  })
}

 

7、總結

咱們再來回顧下實例化Vue應用的最重要的兩點dom

observe(vm._data)
// vm.$mount()
const componentUpdateWatcher = new Watcher(vm, updateComponent)

updateComponent在更新渲染組件時,會訪問1或多個數據模版插值,當訪問數據時,將經過getter攔截器把componentUpdateWatcher做爲訂閱者添加到多個依賴中,每當其中一個數據有更新,將執行setter函數,對應的依賴將會通知訂閱者componentUpdateWatcher執行update,即執行updateComponent;至此Vue數據響應式目的已達到,再來看官網的這張圖片就很好理解了函數

 

 

 

github地址   文章來源:博客園-楊君華,轉載請註明出處:楊君華this

相關文章
相關標籤/搜索