Vueソースの応答式原理
個人ブログアドレス
Objectの変化検出
Vue公式サイトで述べたように、vueは
関数defineReactiveはObjectです.definePropertyのパッケージは、応答式のデータを定義する役割を果たします.
しかし、それだけでは何の役にも立たない.本当に役に立つのは収集依存だ.getterで依存を収集し、setterで依存をトリガーします.
Dep(収集依存)
Watcher(依存)
次に定義したばかりのdefineReactive関数を変更します
この時点では、データの個別のプロパティを検出し、最後にカプセル化することができます.
最後にまとめます.
Watcherをインスタンス化するときにgetメソッドで現在のWathcerインスタンスにDep.targetを割り当て、WatcherインスタンスをDepに追加します.データを設定すると、defineReactiveのsetをトリガーしてDep.notify()を実行し、Depで収集された依存Watcherインスタンスを巡り、Watcherインスタンスのupdateメソッドをトリガーします.
Arrayの変化検出
Objectはgetter/setterで変化を検出できるが,配列はpushのような方法で変化する.これにより,オブジェクトと同様に,遮断器により検出変化を実現することはできない.
遮断器を定義してArrayを上書きする.prototypeは,配列プロトタイプ上のメソッドを用いて配列を操作するたびに,実際にはブロック上のメソッドを実行し,ブロック内でArrayのプロトタイプメソッドを用いる.
次にArrayのプロトタイプを上書きします.
Arrayもgetterで依存を集めていますが、依存しているところが変わりました.Vue.jsはObserverに依存を存在させる:
なぜDepがObserverに存在するのかというと,getterとブロッキングの両方にアクセスしなければならないからである.
ブロッキングはArrayプロトタイプのパッケージであるため、ブロッキングでthis(現在動作中の配列)にアクセスできます.
depはObserverインスタンスに保存されているので、thisでObserverインスタンスにアクセスする必要があります.
ブロッキング:
ここまでは配列の変化だけを検出し、配列要素の変化も検出します.
次に、配列内の新しい要素の変化も検出します.
まとめてみます.
Arrayが変化を追跡する方法はObjectと異なり,ブロックによって配列プロトタイプを上書きする方法で変化を追跡する.
全体を汚さないようにprototypeは、変化を検出する必要がある配列のみに対して、
Observerでは、変化を検出したデータごとにタグデータが 検出されたかどうかは、データによって
したがって,配列の依存をObserverに格納し,配列が変化するまでブロックすると,依存に通知を送信する.
最後に、
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
によって、配列サブ要素および配列新規要素の変化も検出される.