Vue双方向データバインドの原理ソースコード解析
Vueトレースデータ変化
一般的なjsオブジェクトをdataオプションとしてVueインスタンスに転送し、Vueはオブジェクトのすべてのプロパティを遍歴し、
各コンポーネントインスタンスは
注意事項
Vueが応答式に変換されるには、dataオブジェクト上に属性が存在する必要があります.
すでに作成するインスタンスに対して、Vueはルートレベルの応答式属性を動的に追加することを許さず、Vueを用いることができる.set()を追加
vueの双方向データバインドは、データハイジャック+パブリッシュサブスクリプションによって実現されるデータハイジャックの実装vuejsでは、受信した
observeメソッドは、次の段階で実行されます. initMixin()->initState()->initData()で実行し、vueインスタンスのdata属性を処理する initMixin()->initState()->initProps()->validateProp()で実行し、propsのプロパティを処理する
Observerインスタンス
以上はObserverの主なソースコードであり、入力されたオブジェクトを処理し、既存の_ob__属性、説明はすでに処理をブロックしたことを説明して、直接__に戻りますob__属性の値を指定します.そうでなければ,入力されたオブジェクトに対して配列かオブジェクトかを別々に処理する.最終的には、defineReactive$$1()メソッドを使用してオブジェクト属性のgetメソッドとsetメソッドを書き換えます.
Watcher
Watcherはどこで実例化されたのでしょうか stateMinxin -> vue.protorype.$watchメソッドにおける initMixin->initData->initComputed initMixin -> vue.prototype.$mount->mountComponentで 前者は主にvueインスタンスで構成されたwatchとcomputed処理であり、データの変化を同期し、最後にビューとデータをバインドする.
Vuejsでは、グローバルに実行される方法は、主に
ここでinitRenderメソッドでは、vueインスタンスの
vm._updateでは、主にビューを更新し、仮想DOMを介して
以上、vue初期化フェーズでは、new Observer()インスタンスを介してdefineReactive$$1メソッドを呼び出してオブジェクトのプロパティをブロックし、getメソッドとsetメソッドを書き換えます.setメソッドで値が変更されると、サブスクライバリストのwatcherのupdateメソッドが実行され、更新されます.domをマウントすると、生成されたDOMツリーオブジェクトnew Watcherがサブスクライバリストに追加され、ビューが変化すると着信_が実行されます.updateの_renderメソッド更新ビュー
一般的なjsオブジェクトをdataオプションとしてVueインスタンスに転送し、Vueはオブジェクトのすべてのプロパティを遍歴し、
Object.defineProperty
を使用してgetter/setter
に変換します.各コンポーネントインスタンスは
watcher
インスタンスに対応し、コンポーネントレンダリング中に関連するデータ属性が依存として記録されます.その後、依存項目のsetter
がトリガーされると、watcherに通知され、関連するコンポーネントが再レンダリングされる.注意事項
Vueが応答式に変換されるには、dataオブジェクト上に属性が存在する必要があります.
すでに作成するインスタンスに対して、Vueはルートレベルの応答式属性を動的に追加することを許さず、Vueを用いることができる.set()を追加
vueの双方向データバインドは、データハイジャック+パブリッシュサブスクリプションによって実現される
observe(value, asRootData)
がハイジャックされたかどうかを検出し、直接返された場合、そうでない場合、value
のインスタンスがvalueでインスタンス化され、Observerメソッドでは、Observer
によってオブジェクト属性がObject.defineProperty
に変換され、これにより、データが変化したときに一連の操作が行われる.observeメソッドは、次の段階で実行されます.
function observe (value, asRootData) {
if (!isObject(value) || value instanceof VNode) {
return
}
var ob;
//‘__ob__’ , Observer 。
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__;
} else if (
...
) {
ob = new Observer(value); // Observer
}
if (asRootData && ob) {
ob.vmCount++;
}
return ob
}
Observerインスタンス
var Observer = function Observer (value) {
this.value = value;
this.dep = new Dep(); //1 ,
this.vmCount = 0;
def(value, '__ob__', this); // Object.defineProperty value __ob__
if (Array.isArray(value)) { //
if (hasProto) { // var hasProto = '__proto__' in {};
protoAugment(value, arrayMethods);
} else {
copyAugment(value, arrayMethods, arrayKeys);
}
this.observeArray(value);
} else {
this.walk(value);
}
};
//Observer :
Observer.prototype.walk = function walk (obj) {
var keys = Object.keys(obj);
for (var i = 0; i < keys.length; i++) {
defineReactive$$1(obj, keys[i]); // obj keys[i] get set
}
};
Observer.prototype.observeArray = function observeArray (items) {
for (var i = 0, l = items.length; i < l; i++) {
observe(items[i]); // , observe
}
};
//1
var Dep = function Dep () {
this.id = uid++;
this.subs = []; // watcher
};
Dep.prototype.addSub = function addSub (sub) {
this.subs.push(sub);
};
Dep.prototype.removeSub = function removeSub (sub) {
remove(this.subs, sub); //
};
Dep.prototype.depend = function depend () {
if (Dep.target) {
Dep.target.addDep(this); // ,watcher , watcher addDep
}
};
Dep.prototype.notify = function notify () {
...
for (var i = 0, l = subs.length; i < l; i++) {
subs[i].update(); //watcher , watcher update
}
};
以上はObserverの主なソースコードであり、入力されたオブジェクトを処理し、既存の_ob__属性、説明はすでに処理をブロックしたことを説明して、直接__に戻りますob__属性の値を指定します.そうでなければ,入力されたオブジェクトに対して配列かオブジェクトかを別々に処理する.最終的には、defineReactive$$1()メソッドを使用してオブジェクト属性のgetメソッドとsetメソッドを書き換えます.
function defineReactive$$1 (
obj,
key,
val,
customSetter,
shallow
) {
var dep = new Dep(); //
var property = Object.getOwnPropertyDescriptor(obj, key);
// ,
if (property && property.configurable === false) {
return
}
// get set
var getter = property && property.get;
var setter = property && property.set;
if ((!getter || setter) && arguments.length === 2) {
val = obj[key];
}
var childOb = !shallow && observe(val);
// Object.defineProperty() , get set
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
var value = getter ? getter.call(obj) : val;
if (Dep.target) {
dep.depend(); // depend() , watcher addDep()
if (childOb) {
childOb.dep.depend();
if (Array.isArray(value)) {
dependArray(value);
}
}
}
return value
},
set: function reactiveSetter (newVal) {
var value = getter ? getter.call(obj) : val;
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) { // ,
return
}
/* eslint-enable no-self-compare */
if (customSetter) { // set ,
customSetter();
}
if (getter && !setter) { return }
if (setter) { // setter
setter.call(obj, newVal);
} else {
val = newVal;
}
childOb = !shallow && observe(newVal);
dep.notify(); // , ,dep.notify() watcher update ,
}
});
}
Watcher
var Watcher = function Watcher (
vm,
expOrFn,
cb,
options,
isRenderWatcher
) {
this.vm = vm;
if (isRenderWatcher) {
vm._watcher = this;
}
vm._watchers.push(this);
// options
...
this.cb = cb;
this.id = ++uid$2; // uid for batching
this.active = true;
this.dirty = this.lazy; // for lazy watchers
this.deps = [];
this.newDeps = [];
this.depIds = new _Set();
this.newDepIds = new _Set();
this.expression = expOrFn.toString();
// parse expression for getter
if (typeof expOrFn === 'function') {
this.getter = expOrFn;
} else {
this.getter = parsePath(expOrFn);
if (!this.getter) {
this.getter = noop;
warn(
"Failed watching path: \"" + expOrFn + "\" " +
'Watcher only accepts simple dot-delimited paths. ' +
'For full control, use a function instead.',
vm
);
}
}
this.value = this.lazy
? undefined
: this.get(); // get ,
};
//
Watcher.prototype.get = function get () {
pushTarget(this); // Watcher Dep.target
var value;
var vm = this.vm;
try {
value = this.getter.call(vm, vm);
} catch (e) {
...
} finally {
// "touch" every property so they are all tracked as
// dependencies for deep watching
if (this.deep) {
traverse(value);
}
popTarget();
this.cleanupDeps();
}
return value
};
Watcher.prototype.addDep = function addDep (dep) {
var id = dep.id;
if (!this.newDepIds.has(id)) {
this.newDepIds.add(id);
this.newDeps.push(dep);
if (!this.depIds.has(id)) {
dep.addSub(this); // Dep subs
}
}
};
Watcher.prototype.cleanupDeps = function cleanupDeps () {
//
var i = this.deps.length;
while (i--) {
var dep = this.deps[i];
if (!this.newDepIds.has(dep.id)) {
dep.removeSub(this);
}
}
var tmp = this.depIds;
this.depIds = this.newDepIds;
this.newDepIds = tmp;
this.newDepIds.clear();
tmp = this.deps;
this.deps = this.newDeps;
this.newDeps = tmp;
this.newDeps.length = 0;
};
// ,Watcher update , ,
Watcher.prototype.update = function update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true;
} else if (this.sync) { // ,
this.run();
} else { //
queueWatcher(this);
}
};
//watcher ,
Watcher.prototype.run = function run () {
if (this.active) {
var value = this.get();
if (
value !== this.value ||
isObject(value) ||
this.deep
) {
//
var oldValue = this.value;
//
this.value = value;
if (this.user) {
try { //
this.cb.call(this.vm, value, oldValue);
} catch (e) {
handleError(e, this.vm, ("callback for watcher \"" + (this.expression) + "\""));
}
} else { //
this.cb.call(this.vm, value, oldValue);
}
}
}
};
Watcherはどこで実例化されたのでしょうか
Vuejsでは、グローバルに実行される方法は、主に
getter/setter
であり、プレフィックスに基づいて、それぞれ処理のどの部分であるかを基本的に理解することができる.initMixin(Vue), stateMixin(Vue), eventsMixin(Vue), lifecycleMixin(Vue), renderMixin(Vue), initGlobalAPI(Vue)
では、主に初期化の作業、例えば初期化、initMixin(Vue)
などが行われ、最後にinitProxy, initLifecycle, initEvents, initRender, initInjection, initState, initProvide
が実行され、ビューのマウントが行われるここでinitRenderメソッドでは、vueインスタンスの
vm.$mount
およびdefineReactive$$1
属性を$attrs
によってブロック処理する$listeners
メソッドではmountComponent(this,el,hydrating)によってマウントおよびビュー更新メソッドが指定されています.function mountComponent (
vm,
el,
hydrating
) {
vm.$el = el;
if (!vm.$options.render) {
vm.$options.render = createEmptyVNode; //
{
/* istanbul ignore if */
if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||
vm.$options.el || el) {
warn(
'You are using the runtime-only build of Vue where the template ' +
'compiler is not available. Either pre-compile the templates into ' +
'render functions, or use the compiler-included build.',
vm
);
} else {
warn(
'Failed to mount component: template or render function not defined.',
vm
);
}
}
}
callHook(vm, 'beforeMount');
var updateComponent;
/* istanbul ignore if */
if (config.performance && mark) {
updateComponent = function () {
var name = vm._name;
var id = vm._uid;
var startTag = "vue-perf-start:" + id;
var endTag = "vue-perf-end:" + id;
mark(startTag);
var vnode = vm._render();
mark(endTag);
measure(("vue " + name + " render"), startTag, endTag);
mark(startTag);
vm._update(vnode, hydrating);
mark(endTag);
measure(("vue " + name + " patch"), startTag, endTag);
};
} else {
updateComponent = function () {
vm._update(vm._render(), hydrating); //
};
}
// Watcher, , beforeUpdate
new Watcher(vm, updateComponent, noop, {
before: function before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate');
}
}
}, true /* isRenderWatcher */);
hydrating = false;
// vue mounted()
if (vm.$vnode == null) {
vm._isMounted = true;
callHook(vm, 'mounted');
}
return vm
}
vm._updateでは、主にビューを更新し、仮想DOMを介して
vm.$mount
メソッドは、initLifecycleで定義され、実行中にVue.prototype._update
メソッドが呼び出される.vm.__patch__
メソッドではvm.__patch__
が呼び出され、patchはpatch
の戻り値として割り当てられたグローバルメソッドであり、このメソッドがVirtual DOMの実装方法である.以上、vue初期化フェーズでは、new Observer()インスタンスを介してdefineReactive$$1メソッドを呼び出してオブジェクトのプロパティをブロックし、getメソッドとsetメソッドを書き換えます.setメソッドで値が変更されると、サブスクライバリストのwatcherのupdateメソッドが実行され、更新されます.domをマウントすると、生成されたDOMツリーオブジェクトnew Watcherがサブスクライバリストに追加され、ビューが変化すると着信_が実行されます.updateの_renderメソッド更新ビュー