目录
- 定义依赖
- 收集依赖
- 触发依赖
- 总结
定义依赖
定义依赖是什么时候开始的呢?通过源码可以发现在执行_init函数的时候会执行initState(vm)方法:
function initState(vm) {
...
if (opts.data) {
initData(vm);
}
else {
var ob = observe((vm._data = {}));
ob && ob.vmCount++;
}
...
}
先触发initData方法:
function initData(vm) {
var data = vm.$options.data;
data = vm._data = isFunction(data) ? getData(data, vm) : data || {};
...
var keys = Object.keys(data);
...
var i = keys.length;
while (i--) {
var key = keys[i];
{
if (methods && hasOwn(methods, key)) {
warn$2("Method \"".concat(key, "\" has already been defined as a data property."), vm);
}
}
if (props && hasOwn(props, key)) {
warn$2("The data property \"".concat(key, "\" is already declared as a prop. ") +
"Use prop default value instead.", vm);
}
else if (!isReserved(key)) {
proxy(vm, "_data", key);
}
}
// observe data
var ob = observe(data);
ob && ob.vmCount++;
}
首先会获取data数据,然后执行proxy(vm, “_data”, key):
var sharedPropertyDefinition = {
enumerable: true,
configurable: true,
get: noop,
set: noop
};
function proxy(target, sourceKey, key) {
sharedPropertyDefinition.get = function proxyGetter() {
return this[sourceKey][key];
};
sharedPropertyDefinition.set = function proxySetter(val) {
this[sourceKey][key] = val;
};
Object.defineProperty(target, key, sharedPropertyDefinition);
}
在vm实例中添加了_data对象并将data的数据给了_data。随后执行observe(data):
function observe(value, shallow, ssrMockReactivity) {
...
else if (shouldObserve &&
(ssrMockReactivity || !isServerRendering()) &&
(isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value.__v_skip /* ReactiveFlags.SKIP */) {
ob = new Observer(value, shallow, ssrMockReactivity);
}
return ob;
}
主要执行 new Observer(value, shallow, ssrMockReactivity)方法:
function Observer(value, shallow, mock) {
if (shallow === void 0) { shallow = false; }
if (mock === void 0) { mock = false; }
this.value = value;
this.shallow = shallow;
this.mock = mock;
// this.value = value
this.dep = mock ? mockDep : new Dep();
this.vmCount = 0;
def(value, '__ob__', this);
if (isArray(value)) {
...
}
else {
/**
* Walk through all properties and convert them into
* getter/setters. This method should only be called when
* value type is Object.
*/
var keys = Object.keys(value);
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
defineReactive(value, key, NO_INIITIAL_VALUE, undefined, shallow, mock);
}
}
}
主要执行defineReactive:
function defineReactive(obj, key, val, customSetter, shallow, mock) {
var dep = new Dep();
var property = Object.getOwnPropertyDescriptor(obj, key);
if (property && property.configurable === false) {
return;
}
// cater for pre-defined getter/setters
var getter = property && property.get;
var setter = property && property.set;
if ((!getter || setter) &&
(val === NO_INIITIAL_VALUE || arguments.length === 2)) {
val = obj[key];
}
var childOb = !shallow && observe(val, false, mock);
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
var value = getter ? getter.call(obj) : val;
if (Dep.target) {
{
dep.depend({
target: obj,
type: "get" /* TrackOpTypes.GET */,
key: key
});
}
if (childOb) {
childOb.dep.depend();
if (isArray(value)) {
dependArray(value);
}
}
}
return isRef(value) && !shallow ? value.value : value;
},
set: function reactiveSetter(newVal) {
var value = getter ? getter.call(obj) : val;
if (!hasChanged(value, newVal)) {
return;
}
if (customSetter) {
customSetter();
}
if (setter) {
setter.call(obj, newVal);
}
else if (getter) {
// #7981: for accessor properties without setter
return;
}
else if (!shallow && isRef(value) && !isRef(newVal)) {
value.value = newVal;
return;
}
else {
val = newVal;
}
childOb = !shallow && observe(newVal, false, mock);
{
dep.notify({
type: "set" /* TriggerOpTypes.SET */,
target: obj,
key: key,
newValue: newVal,
oldValue: value
});
}
}
});
return dep;
}
可以看出新增了一个依赖对象Dep,表示是该数据被哪些组件所依赖,并定义了data下数据的get和set方法。
收集依赖
vue是怎么收集依赖的呢?当组件渲染的时候会执行下面的渲染函数:
var render = function render() {
var _vm = this,
_c = _vm._self._c
return _c("div", [
_vm._v("\n " + _vm._s(_vm.num) + "\n " + _vm._s(_vm.a) + "\n "),
_c("button", { on: { click: _vm.addModule } }, [_vm._v("新增")]),
])
}
原内容如下:
<template>
<div>
{{num}}
{{a}}
<button @click="addModule">新增</button>
</div>
</template>
<script>
export default {
name: "TestWebpackTest",
mounted() {
console.log(this);
},
data() {
return {
num: 1,
a:2
};
},
computed:{
getNum(){
return this.num+Math.random()
},
getA(){
return this.a+Math.random()
}
},
methods: {
addModule() {
this.num++;
}
}
};
</script>
<style lang="scss">
div {
.test {
width: 10px;
height: 15px;
background-color: blue;
}
}
</style>
在渲染组件模版的时候会取获取数据,此时会触发data中定义数据的getter方法,此时为当前挂载组件实例化watcher的时候会设置Dep.target。
Dep.prototype.depend = function (info) {
if (Dep.target) {
Dep.target.addDep(this);
if (info && Dep.target.onTrack) {
Dep.target.onTrack(__assign({ effect: Dep.target }, info));
}
}
};
通知当前依赖的组件去添加依赖,当前依赖的组件会将该依赖添加进入newDeps。相反依赖也可能被多个组件使用,所以在该依赖也有多个组件。
Watcher.prototype.addDep = function (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);
}
}
};
其实本质就是建立组件和变量之间的依赖关系,一个组件可以有多个依赖,一个依赖可以被多个组件使用。
触发依赖
当数据发生变化时会触发数据的gettter方法:
dep.notify({
type: "set" /* TriggerOpTypes.SET */,
target: obj,
key: key,
newValue: newVal,
oldValue: value
});
调用当前依赖的notify方法去通知组件更新:
Dep.prototype.notify = function (info) {
// stabilize the subscriber list first
var subs = this.subs.slice();
...
for (var i = 0, l = subs.length; i < l; i++) {
if (info) {
var sub = subs[i];
sub.onTrigger &&
sub.onTrigger(__assign({ effect: subs[i] }, info));
}
subs[i].update();
}
};
该方法就是获取当前依赖下的组件并调用该组件的update方法:
Watcher.prototype.update = function () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true;
}
else if (this.sync) {
this.run();
}
else {
queueWatcher(this);
}
};
下面的内容我在另外一篇文章中讲过:vue2.7.10在数据发生变化后是如何更新页面的。
总结
- 定义依赖是在实例化组件的时候执行的此时的Dep.target指向当前vm实例,在initData方法中会遍历data数据并设置get和set方法,每个数据都有一个dep(依赖),表示每个数据都会被多个组件所依赖。
- 收集依赖是在执行render方法的时候。该方法触发数据的get方法,建立数据的dep(依赖)和watcher之间的联系。
- 触发依赖是在数据发生改变的时候执行。此时会触发数据的set方法,取出当前数据的依赖者(watcher)并循环调用依赖者的update方法更新视图。