Vue收集依赖
Young soul2 人气:0定义依赖
定义依赖是什么时候开始的呢?通过源码可以发现在执行_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方法更新视图。
加载全部内容