Vue原始碼解讀之Dep,Observer和Watcher
阿新 • • 發佈:2018-12-19
在解讀Dep,Observer和Watcher之前,首先我去了解了一下Vue的資料雙向繫結,即MVVM,學習於:https://blog.csdn.net/u013321...
以及關於Observer和watcher的學習來自於:https://www.jb51.net/article/...
整體過程
Vue例項化一個物件的具體過程如下:
- 新建立一個例項後,Vue呼叫compile將el轉換成vnode。
- 呼叫initState, 建立props, data的鉤子以及其物件成員的Observer(新增getter和setter)。
- 執行mount掛載操作,在掛載時建立一個直接對應render的Watcher,並且編譯模板生成render函式,執行vm._update來更新DOM。
-
每當有資料改變,都將通知相應的Watcher執行回撥函式,更新檢視。
- 當給這個物件的某個屬性賦值時,就會觸發set方法。
- set函式呼叫,觸發Dep的notify()向對應的Watcher通知變化。
- Watcher呼叫update方法。
在這個過程中:
- Observer是用來給資料新增Dep依賴。
- Dep是data每個物件包括子物件都擁有一個該物件, 當所繫結的資料有變更時, 通過dep.notify()通知Watcher。
- Compile是HTML指令解析器,對每個元素節點的指令進行掃描和解析,根據指令模板替換資料,以及繫結相應的更新函式。
- Watcher是連線Observer和Compile的橋樑,Compile解析指令時會建立一個對應的Watcher並繫結update方法 , 新增到Dep物件上。
接下來我們來分析一下物件具體的程式碼實現。
Observer
var Observer = function Observer (value) { this.value = value; this.dep = new Dep(); this.vmCount = 0; // 給value新增__ob__屬性,值就是本Observer物件,value.__ob__ = this; // Vue.$data 中每個物件都 __ob__ 屬性,包括 Vue.$data物件本身 def(value, '__ob__', this); //判斷是否為陣列,不是的話呼叫walk()新增getter和setter //如果是陣列,呼叫observeArray()遍歷陣列,為陣列內每個物件新增getter和setter if (Array.isArray(value)) { var augment = hasProto ? protoAugment : copyAugment; augment(value, arrayMethods, arrayKeys); this.observeArray(value); } else { this.walk(value); } };
walk和defineReactive
// 遍歷每個屬性並將它們轉換為getter/setter。只有當值型別為物件時才呼叫此方法。
Observer.prototype.walk = function walk (obj) {
var keys = Object.keys(obj);
for (var i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i]);
}
};
function defineReactive (
obj,
key,
val,
customSetter,
shallow
) {
var dep = new Dep();
var property = Object.getOwnPropertyDescriptor(obj, key);
if (property && property.configurable === false) {
return
}
//獲取已經實現的 getter /setter 方法
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(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
var value = getter ? getter.call(obj) : val;
//Dep.target 全域性變數指向的就是當前正在解析指令的Complie生成的 Watcher
// 會執行到 dep.addSub(Dep.target), 將 Watcher 新增到 Dep 物件的 Watcher 列表中
if (Dep.target) {
dep.depend();
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 ("development" !== 'production' && customSetter) {
customSetter();
}
if (setter) {
setter.call(obj, newVal);
} else {
val = newVal;
}
childOb = !shallow && observe(newVal);
dep.notify();//如果資料被重新賦值了, 呼叫 Dep 的 notify 方法, 通知所有的 Watcher
}
});
}
observeArray和observe
Observer.prototype.observeArray = function observeArray (items) {
for (var i = 0, l = items.length; i < l; i++) {
// 如果是陣列繼續執行 observe 方法, 其中會繼續新建 Observer 物件, 直到窮舉完畢執行 walk 方法
observe(items[i]);
}
};
function observe (value, asRootData) {
if (!isObject(value) || value instanceof VNode) {
return
}
var ob;
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__;
} else if (
shouldObserve &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
ob = new Observer(value);
}
if (asRootData && ob) {
ob.vmCount++;
}
return ob
}
Dep
// Dep是訂閱者Watcher對應的資料依賴
var Dep = function Dep () {
//每個Dep都有唯一的ID
this.id = uid++;
//subs用於存放依賴
this.subs = [];
};
//向subs陣列新增依賴
Dep.prototype.addSub = function addSub (sub) {
this.subs.push(sub);
};
//移除依賴
Dep.prototype.removeSub = function removeSub (sub) {
remove(this.subs, sub);
};
//設定某個Watcher的依賴
//這裡添加了Dep.target是否存在的判斷,目的是判斷是不是Watcher的建構函式呼叫
//也就是說判斷他是Watcher的this.get呼叫的,而不是普通呼叫
Dep.prototype.depend = function depend () {
if (Dep.target) {
Dep.target.addDep(this);
}
};
Dep.prototype.notify = function notify () {
var subs = this.subs.slice();
//通知所有繫結 Watcher。呼叫watcher的update()
for (var i = 0, l = subs.length; i < l; i++) {
subs[i].update();
}
};
Watcher
在initMixin()初始化完成Vue例項所有的配置之後,在最後根據el是否存在,呼叫$mount()實現掛載。
if (vm.$options.el) {
vm.$mount(vm.$options.el);
}
$mount
//這是供外部使用的公共的方法
Vue.prototype.$mount = function (
el,
hydrating
) {
el = el && inBrowser ? query(el) : undefined;
return mountComponent(this, el, hydrating)
};
mountComponent
function mountComponent (
vm,
el,
hydrating
) {
vm.$el = el;
//這個if判斷目的在檢測vm.$options.render
if (!vm.$options.render) {
vm.$options.render = createEmptyVNode;
{
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');
// 定義updateComponent,將作為Watcher物件的引數傳入。
var updateComponent;
/* istanbul ignore if */
if ("development" !== 'production' && 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);
};
}
new Watcher(vm, updateComponent, noop, {
before: function before () {
if (vm._isMounted) {
callHook(vm, 'beforeUpdate');
}
}
}, true /* isRenderWatcher */);
hydrating = false;
// 呼叫鉤子
if (vm.$vnode == null) {
vm._isMounted = true;
callHook(vm, 'mounted');
}
return vm
}
watcher
mountComponent在構造新的Watcher物件傳了當前vue例項、updateComponent函式、空函式這三個引數。
var Watcher = function Watcher (
vm,
expOrFn,
cb,
options,
isRenderWatcher
) {
this.vm = vm;
if (isRenderWatcher) {
vm._watcher = this;
}
// 當前Watcher新增到vue例項上
vm._watchers.push(this);
// 引數配置,options預設false
if (options) {
this.deep = !!options.deep;
this.user = !!options.user;
this.computed = !!options.computed;
this.sync = !!options.sync;
this.before = options.before;
} else {
this.deep = this.user = this.computed = this.sync = false;
}
this.cb = cb;
this.id = ++uid$1; // uid for batching
this.active = true;
this.dirty = this.computed; //用於計算屬性
this.deps = [];
this.newDeps = [];
//內容不可重複的陣列物件
this.depIds = new _Set();
this.newDepIds = new _Set();
this.expression = expOrFn.toString();
//將watcher物件的getter設為updateComponent方法
if (typeof expOrFn === 'function') {
this.getter = expOrFn;
} else {
this.getter = parsePath(expOrFn);
if (!this.getter) {
this.getter = function () {};
"development" !== 'production' && warn(
"Failed watching path: \"" + expOrFn + "\" " +
'Watcher only accepts simple dot-delimited paths. ' +
'For full control, use a function instead.',
vm
);
}
}
//如果是計算屬性,就建立Dep資料依賴,否則通過get獲取value
if (this.computed) {
this.value = undefined;
this.dep = new Dep();
} else {
this.value = this.get();
}
};
};
get
Watcher.prototype.get = function get () {
pushTarget(this);//將Dep的target新增到targetStack,同時Dep的target賦值為當前watcher物件
var value;
var vm = this.vm;
try {
// 呼叫updateComponent方法
value = this.getter.call(vm, vm);
} catch (e) {
if (this.user) {
handleError(e, vm, ("getter for watcher \"" + (this.expression) + "\""));
} else {
throw e
}
} finally {
// "touch" every property so they are all tracked as
// dependencies for deep watching
if (this.deep) {
traverse(value);
}
popTarget();//update執行完成後,又將Dep.target從targetStack彈出。
this.cleanupDeps();
}
return value
};
//這是全域性唯一的,因為任何時候都可能只有一個watcher正在評估。
Dep.target = null;
var targetStack = [];
function pushTarget (_target) {
if (Dep.target) { targetStack.push(Dep.target); }
Dep.target = _target;
}
function popTarget () {
Dep.target = targetStack.pop();
}
Watcher的get方法實際上就是呼叫了updateComponent方法,updateComponent就是
updateComponent = function() {
vm._update(vm._render(), hydrating);
};
呼叫這個函式會接著呼叫_update函式更新dom,這個是掛載到vue原型的方法,而_render方法重新渲染了vnode。
Vue.prototype._render = function () {
var vm = this;
var ref = vm.$options;
var render = ref.render;
var _parentVnode = ref._parentVnode;
// reset _rendered flag on slots for duplicate slot check
{
for (var key in vm.$slots) {
// $flow-disable-line
vm.$slots[key]._rendered = false;
}
}
if (_parentVnode) {
vm.$scopedSlots = _parentVnode.data.scopedSlots || emptyObject;
}
// set parent vnode. this allows render functions to have access
// to the data on the placeholder node.
vm.$vnode = _parentVnode;
// render self
var vnode;
try {
vnode = render.call(vm._renderProxy, vm.$createElement);
} catch (e) {
handleError(e, vm, "render");
// return error render result,
// or previous vnode to prevent render error causing blank component
/* istanbul ignore else */
{
if (vm.$options.renderError) {
try {
vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e);
} catch (e) {
handleError(e, vm, "renderError");
vnode = vm._vnode;
}
} else {
vnode = vm._vnode;
}
}
}
// return empty vnode in case the render function errored out
if (!(vnode instanceof VNode)) {
if ("development" !== 'production' && Array.isArray(vnode)) {
warn(
'Multiple root nodes returned from render function. Render function ' +
'should return a single root node.',
vm
);
}
vnode = createEmptyVNode();
}
// set parent
vnode.parent = _parentVnode;
return vnode
};
update
//當依賴項改變時呼叫。前面有提到。
Watcher.prototype.update = function update () {
var this$1 = this;
/* istanbul ignore else */
//是否計算屬性
if (this.computed) {
if (this.dep.subs.length === 0) {
this.dirty = true;
} else {
this.getAndInvoke(function () {
this$1.dep.notify();
});
}
//是否快取
} else if (this.sync) {
//呼叫run方法執行回撥函式
this.run();
} else {
queueWatcher(this);
}
};
Watcher.prototype.run = function run () {
if (this.active) {
//這裡的cb就是指watcher的回撥函式
this.getAndInvoke(this.cb);
}
};
Watcher.prototype.getAndInvoke = function getAndInvoke (cb) {
var value = this.get();
if (value !== this.value ||isObject(value) ||this.deep) {
//設定新的值
var oldValue = this.value;
this.value = value;
this.dirty = false;
if (this.user) {
try {
cb.call(this.vm, value, oldValue);
} catch (e) {
handleError(e, this.vm, ("callback for watcher \"" + (this.expression) + "\""));
}
} else {
//執行回撥函式
cb.call(this.vm, value, oldValue);
}
}
};
後記
關於Vue資料雙向繫結的文章很多,檢視越多資料越覺得自己只是淺薄,要更努力才行啊。
學習到比較系統思路的來自於:https://segmentfault.com/a/11...
十分感謝