在vue原理中,最重要的部分就是如何實現數據的觀測,依賴的收集,視圖的更新。本文講的就是Observer, Dep, Watcher這三個的簡單實現。
pub(publish)表示發佈者,sub(subscribe)表示訂閱者, cb(callback)表示回調函數
若是你以爲這篇講的對你有所幫助,請幫我點個starvue
Observer的做用簡單來講就是讓object對象的屬性都用Object.defineProperty()來進行定義,這樣當獲取object的屬性,或者修改屬性的時候,就可以觸發get,set達到數據的觀測的效果。git
class Observer { constructor(value) { this.value = value this.walk(this.value) } walk (value) { // 遞歸遍歷value的屬性 Object.keys(value).forEach((key) = > { defineReactive(value, key, value[key]) }) } } function defineReactive(obj, key ,val) { let childOb = observe(val) Obeject.defineProperty(obj, key, { enumerable: true, configurable: true, get() { console.log('') return val }, set(newVal) { console.log('') val = newVal childOb = observe(val) } }) } function observe (value) { if (typeof value === 'object' && !Array.isArray(value)) { value = new Observer(value) } }
defineReactive的做用就是給對象的屬性進行簡單的數據觀測,一旦值獲取或者設置就會觸發一些行爲.由於一個對象的屬性可能仍是對象,因此在這裏咱們添加observe函數來遍歷值,讓一個對象的屬性的屬性仍是能夠進行觀測的,簡單呢來講的意思就是讓全部屬性均可以進行忽略。固然在實際狀況中,咱們還須要考慮數組的狀況,但都大同小異。
這樣作代碼彷佛有點醜,咱們在設置屬性觸發set會發生console.log()函數,有沒有一種更加智能的方式來實現通知變化呢。這裏咱們就須要用消息訂閱器來進行實現,這樣作咱們就不須要經過觀察console.log()輸出的值來看進行的狀況,咱們只須要在set方法裏邊加一個通知,一旦值發生變化,就通知外邊值發生了改變github
Dep的做用就是用來收集屬性值的變化,一旦set方法觸發的時候,就更新視圖。那就準備一個數組來進行收集吧!
下面是Dep的實現:數組
class Dep { constructor() { this.subs = [] } addSub (sub) { this.subs.push(sub) } notify () { const subs = this.subs.slice() subs.forEach((sub) => { sub.update() // 視圖更新 }) } }
上面就是Dep的簡單實現,addSub的做用是增長訂閱者,由於有不少訂閱者,咱們須要用一個數組將它進行存儲,notify()函數的做用就是當set發生的時候,進行通知,update()這個函數待會在watcher中會講到。實現了Dep咱們是否是該更改了set()函數了呢,下面是defineReactive()修改後的代碼函數
function defineReactive(obj, key ,val) { let dep = new Dep() // 畢竟要使用Dep的方法 let childOb = observe(val) Obeject.defineProperty(obj, key, { enumerable: true, configurable: true, get() { return val }, set(newVal) { val = newVal childOb = observe(val) dep.notify() // 由於數據改變了,咱們就通知Dep } }) }
一旦觸發set,就調用dep.notify(),notify的做用就是針對訂閱者遍歷進行更新。this
watcher的做用,就是當狀態發生改變的時候,更新視圖,咱們能夠假設code
class Watcher { constructor (vm, cb, expOrFn) { this.vm = vm // 這表示一個Vue的實例 this.cb = cb // 這裏須要考慮expOrFn是字符串或者函數的狀況 // 這裏作一個簡化,只考慮函數的狀況 this.getter = expOrFn this.value = this.get() } get () { Dep.target = this const vm = this.vm value = this.getter.call(this.vm, vm) Dep.target = null return value } update () { this.run() } run () { const value = this.get() if (value !== this.value) { const oldValue = this.value this.value = value this.cb.call(this.vm, value, oldValue) } } }
Watcher的簡單實現就完成了,在Dep()構造函數中,咱們使用了sub.update()這行代碼,而update函數是Watcher裏邊的方法,說明每個sub都是Wathcer的實例,問題是咱們應該如何經過addSub()這個方法,將Watcher加入到subs這個數組中盡心存儲呢,答案仍是在defineReactive()裏邊進行修改server
function defineReactive(obj, key ,val) { let dep = new Dep() // 畢竟要使用Dep的方法 let childOb = observe(val) Obeject.defineProperty(obj, key, { enumerable: true, configurable: true, get() { if(Dep.target) { dep.addSub(Dep.target) } return val }, set(newVal) { val = newVal childOb = observe(val) dep.notify() // 由於數據改變了,咱們就通知Dep } }) }
這樣是否是就實現了往Dep裏邊加Watcher了,vue源碼中比這個複雜的多,各類參數,看着頭大。本文的宗旨就是經過簡化讓你瞭解內部原理,若是須要更深刻了解就須要閱讀源碼了。對象