目录
- 定义依赖
- 收集依赖
- 触发依赖
- 总结
定义依赖
定义依赖是什么时候开始的呢?通过源码可以发现在执行_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方法更新视图。