Vueソースの応答式原理


個人ブログアドレス
Objectの変化検出
Vue公式サイトで述べたように、vueはObject.definePropertyによって対象属性値の変化を検出する.
function defineReactive (obj, key, val) {
    let dep = new Dep()
    Object.defineProperty(obj, key, {
         enumerable: true,
          configurable: true,
          get () {
            return val
          },
          set (newVal) {
            if (val === newVal) return
            	val = newVal
          }
    })
}

関数defineReactiveはObjectです.definePropertyのパッケージは、応答式のデータを定義する役割を果たします.
しかし、それだけでは何の役にも立たない.本当に役に立つのは収集依存だ.getterで依存を収集し、setterで依存をトリガーします.
Dep(収集依存)
//         ,        。
class Dep {
	constructor () {
        //     
    	this.subs = []
  	}
    addSub (sub) {
        this.subs.push(sub)
    }
    
    depend (target) {
        if (Dep.target) {
            //    Dep.target Watcher  
            Dep.target.addDep(this)
        }
    }
    notity () {
        this.subs.forEach(val => {
            val.update()
        })
    }
    Dep.target = null
}

Watcher(依存)
//    Watcher     Dep,       ,   Dep        Watcher Dep    。
class Watcher {
    constructor (vm, expOrFn, cb) {
        // vm: vue  
        // expOrFn:       
        // cb: callback    
        this.vm = vm
        this.cb = cb
        //   this.getter     expOrFn   ,      
        if (typeof expOrFn === 'function') {
            this.getter = expOrFn
        } else {
            // parsePath      keypath   ,        Vue   
            this.getter = parsePath(expOrFn)
        }
        this.value = this.get()
    }
    get () {
        Dep.target = this
        //      this.getter
        let value = this.getter(this.vm, this.vm)
        Dep.target = null
        return value
    }
    addDep (dep) {
        dep.addSub(this)
    }
    //     
    update () {
        const oldValue = this.value
    	this.value = this.get()
    	this.cb.call(this.vm, this.value, oldValue)
    }
}

次に定義したばかりのdefineReactive関数を変更します
function defineReactive (obj, key, val) {
    let dep = new Dep() //   
    Object.defineProperty(obj, key, {
          enumerable: true,
          configurable: true,
          get () {
            //   getter ,    
            dep.addDep()
            return val
          },
          set (newVal) {
            if (val === newVal) return
            	val = newVal
            	//   setter ,  Dep notify,    
             	dep.notity()
          }
    })
}

この時点では、データの個別のプロパティを検出し、最後にカプセル化することができます.
class Observer {
	constructor (value) {
        this.value = value
        //                     
    	if (!Array.isArray(value)) {
            this.walk(value)
        }
    }
    
    walk (value) {
        const keys = Object.keys(value)
        keys.forEach(key => {
            this.defineReactive(value, key, value[key])
        })
    }
}

最後にまとめます.
Watcherをインスタンス化するときにgetメソッドで現在のWathcerインスタンスにDep.targetを割り当て、WatcherインスタンスをDepに追加します.データを設定すると、defineReactiveのsetをトリガーしてDep.notify()を実行し、Depで収集された依存Watcherインスタンスを巡り、Watcherインスタンスのupdateメソッドをトリガーします.
Arrayの変化検出
Objectはgetter/setterで変化を検出できるが,配列はpushのような方法で変化する.これにより,オブジェクトと同様に,遮断器により検出変化を実現することはできない.
遮断器を定義してArrayを上書きする.prototypeは,配列プロトタイプ上のメソッドを用いて配列を操作するたびに,実際にはブロック上のメソッドを実行し,ブロック内でArrayのプロトタイプメソッドを用いる.
const arrayProto = Array.prototype
const arrayMethods = Object.create(arrayProto)

const methodsToPatch = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]
methodsToPatch.forEach(function (method) {
  //       
  const original = arrayProto[method]
  Object.defineProperty(arrayMethods, method, {
      enumerable: false,
      configurable: true,
      writable: true,
      value: function mutator (...args) {
          return original.apply(this, args)
      }
  })

次にArrayのプロトタイプを上書きします.
//      __proto__,      __proto__,               value 。
const hasProto = "__proto__" in {}
const arrayKeys = Object.getOwnPropertyNames(arrayMethods)
class Observer {
	constructor (value) {
        this.value = value
    	if (!Array.isArray(value)) {
            this.walk(value)
        } else {
        	const augment = hasProto ? protoAugment : copyAugment
        	augment(value, arrayMethods, arraykeys)
        }
    }
    
    walk (value) {
        const keys = Object.keys(value)
        keys.forEach(key => {
            this.defineReactive(value, key, value[key])
        })
    }
}

function protoAugment (target, src: Object) {
  target.__proto__ = src
}

function copyAugment (target: Object, src: Object, keys: Array) {
  for (let i = 0, l = keys.length; i < l; i++) {
    const key = keys[i]
    def(target, key, src[key])
  }
}

Arrayもgetterで依存を集めていますが、依存しているところが変わりました.Vue.jsはObserverに依存を存在させる:
class Observer {
	constructor (value) {
        this.value = value
        this.dep = new Dep //   Dep
    	if (!Array.isArray(value)) {
            this.walk(value)
        } else {
        	const augment = hasProto ? protoAugment : copyAugment
        	augment(value, arrayMethods, arraykeys)
        }
    }
    
    walk (value) {
        const keys = Object.keys(value)
        keys.forEach(key => {
            this.defineReactive(value, key, value[key])
        })
    }
}

なぜDepがObserverに存在するのかというと,getterとブロッキングの両方にアクセスしなければならないからである.
function defineReactive (data, key, val) {
	let childOb = observer(val) //   
    let dep = new Dep() 
    Object.defineProperty(obj, key, {
          enumerable: true,
          configurable: true,
          get () {
            dep.addDep()
            if (childOb) {
                //          
                childOb.dep.depend()
            }
            return val
          },
          set (newVal) {
            if (val === newVal) return
            	val = newVal
             	dep.notity()
          }
    })
    
}
//   value        ,   __ob__  ,          Observer  
//          ,     Observer  
function observer (value, asRootData) {
    if (!isObject(value)) {
        return
    }
    let ob
    if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observe) {
        ob = value.__ob__
    } else {
        ob = new Observer(value)
    }
    return ob
}

ブロッキングはArrayプロトタイプのパッケージであるため、ブロッキングでthis(現在動作中の配列)にアクセスできます.
depはObserverインスタンスに保存されているので、thisでObserverインスタンスにアクセスする必要があります.
function def (obj, key, val, enumerable) {
    Object.defineProperty(obj, key, {
    	value: val,
        enumerable: !!enumerable,
        writable: true,
        configerable: true
    })
}
class Observer {
	constructor (value) {
        this.value = value
        this.dep = new Dep
        //  value            __ob__,     Observer  
        //           __ob__    Observer  ,       Observer depp
        // __ob__       Observer  ,             
        def(value, '__ob__', this) //   
    	if (!Array.isArray(value)) {
            this.walk(value)
        } else {
        	const augment = hasProto ? protoAugment : copyAugment
        	augment(value, arrayMethods, arraykeys)
        }
    }
    
    ...
}

ブロッキング:
methodsToPatch.forEach(function (method) {
  const original = arrayProto[method]
  def(arrayMethods, method, function mutator (...args) {
      const result = original.apply(this, args)
      const ob = this.__ob__ //   
      ob.dep.notify() //           
      return resullt
  })
})

ここまでは配列の変化だけを検出し、配列要素の変化も検出します.
class Observer {
	constructor (value) {
        this.value = value
        this.dep = new Dep
        def(value, '__ob__', this) 
    	if (!Array.isArray(value)) {
            this.walk(value)
        } else {
        	const augment = hasProto ? protoAugment : copyAugment
        	augment(value, arrayMethods, arraykeys)
            //          
        	this.observeArray(value) //   
        }
    }
    
    observeArray (items) {
    	items.forEach(item => {
    		observe(item)
    	})
    }
    
    ...
}

次に、配列内の新しい要素の変化も検出します.
methodsToPatch.forEach(function (method) {
  const original = arrayProto[method]
  def(arrayMethods, method, function mutator (...args) {
      const result = original.apply(this, args)
      const ob = this.__ob__ 
      //     
      let inserted
      switch (method) {
              case 'push'
              case 'unshift'
              	inserted = args
              	breaak
              case 'splice'
              	inserted = args.slice(2)
                break
      }
      if (inserted) ob.observeArray(inserted)
      //     
      ob.dep.notify() 
      return resullt
  })
})

まとめてみます.
Arrayが変化を追跡する方法はObjectと異なり,ブロックによって配列プロトタイプを上書きする方法で変化を追跡する.
全体を汚さないようにprototypeは、変化を検出する必要がある配列のみに対して、__proto__をサポートしていないブラウザに対しては、ブロックを配列自体に直接配置します.
Observerでは、変化を検出したデータごとに__ob__のプロパティが追加され、this(Observer )__ob__に保存します.主に2つの役割があります.
  • タグデータが
  • 検出されたかどうか
  • は、データによって__ob__を取得し、さらにObserverインスタンスを取得することができる.

  • したがって,配列の依存をObserverに格納し,配列が変化するまでブロックすると,依存に通知を送信する.
    最後に、observeArrayによって、配列サブ要素および配列新規要素の変化も検出される.