vue computed的依赖收集机制源码分析
Dep类:data的所有属性都创建了一个dep实例收集被观察属性的watcher实例
var uid = 0; /** * A dep is an observable that can have multiple * directives subscribing to it. */ // 依赖收集 var Dep = function Dep () { this.id = uid++; // 唯一id this.subs = []; }; // 注入依赖的watch实例 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.update)所有watch属性更新 Dep.prototype.notify = function notify () { // stabilize the subscriber list first var subs = this.subs.slice(); for (var i = 0, l = subs.length; i < l; i++) { subs[i].update(); } }; // the current target watcher being evaluated. // this is globally unique because there could be only one // watcher being evaluated at any time. Dep.target = null; var targetStack = []; // 切换当前watch中的实例与入栈 function pushTarget (_target) { if (Dep.target) { targetStack.push(Dep.target); } Dep.target = _target; } // 切换当前watch中的实例与出栈 function popTarget () { Dep.target = targetStack.pop(); }
Watcher类:观察组件实例的某个属性,值发生变化执行传入的回调方法(cb),computed也借助了Watcher实现依赖项的收集
/** * A watcher parses an expression, collects dependencies, * and fires callback when the expression value changes. * This is used for both the $watch() api and directives. */ // watch实例 var Watcher = function Watcher ( vm, // 组件本身 expOrFn, // 读取观察属性 cb, options, isRenderWatcher ) { // 当前组件实例 this.vm = vm; if (isRenderWatcher) { vm._watcher = this; } vm._watchers.push(this); // options if (options) { this.deep = !!options.deep; // 是否深度观察 this.user = !!options.user; this.lazy = !!options.lazy; this.sync = !!options.sync; // 是否同步 } else { this.deep = this.user = this.lazy = this.sync = false; } // 回调方法 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 = function () {}; "development" !== 'production' && 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(); }; /** * Evaluate the getter, and re-collect dependencies. */ Watcher.prototype.get = function get () { // 调整正在watcher中的实例 pushTarget(this); var value; var vm = this.vm; try { // 该方法的执行过程中所有使用get操作的属性会被收集为依赖项 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(); // 清除旧的依赖列表,更新依赖列表 this.cleanupDeps(); } return value }; /** * Add a dependency to this directive. */ // 添加依赖项 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); } } }; /** * Clean up for dependency collection. */ // 更新依赖列表 Watcher.prototype.cleanupDeps = function cleanupDeps () { var this$1 = this; var i = this.deps.length; while (i--) { var dep = this$1.deps[i]; if (!this$1.newDepIds.has(dep.id)) { dep.removeSub(this$1); } } 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; }; /** * Subscriber interface. * Will be called when a dependency changes. */ // 更新试图 Watcher.prototype.update = function update () { /* istanbul ignore else */ if (this.lazy) { this.dirty = true; } else if (this.sync) { // 同步执行 this.run(); } else { // 异步执行 队列阻塞实现nextTick queueWatcher(this); } }; /** * Scheduler job interface. * Will be called by the scheduler. */ // 执行watcher回调 Watcher.prototype.run = function run () { if (this.active) { var value = this.get(); if ( value !== this.value || // Deep watchers and watchers on Object/Arrays should fire even // when the value is the same, because the value may // have mutated. isObject(value) || this.deep ) { // set new value 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); } } } }; /** * Evaluate the value of the watcher. * This only gets called for lazy watchers. */ Watcher.prototype.evaluate = function evaluate () { this.value = this.get(); this.dirty = false; }; /** * Depend on all deps collected by this watcher. */ // 注入依赖 Watcher.prototype.depend = function depend () { var this$1 = this; var i = this.deps.length; while (i--) { this$1.deps[i].depend(); } }; /** * Remove self from all dependencies' subscriber list. */ Watcher.prototype.teardown = function teardown () { var this$1 = this; if (this.active) { // remove self from vm's watcher list // this is a somewhat expensive operation so we skip it // if the vm is being destroyed. if (!this.vm._isBeingDestroyed) { remove(this.vm._watchers, this); } var i = this.deps.length; while (i--) { this$1.deps[i].removeSub(this$1); } this.active = false; } }; /* */
Observer类:对data的所有属性进行递归,监听每一个属性的get、set操作。get操作用于收集观察实例的依赖项(即依赖属性),set操作用于更新数据与相应的视图
var observerState = { shouldConvert: true }; /** * Observer class that are attached to each observed * object. Once attached, the observer converts target * object's property keys into getter/setters that * collect dependencies and dispatches updates. */ var Observer = function Observer (value) { this.value = value; this.dep = new Dep(); this.vmCount = 0; // 设置ob原型 def(value, '__ob__', this); if (Array.isArray(value)) { var augment = hasProto ? protoAugment : copyAugment; augment(value, arrayMethods, arrayKeys); // 设置原型 // 遍历数组观察每一个属性 this.observeArray(value); } else { // 观察单个属性 this.walk(value); } }; /** * Walk through each property and convert them into * getter/setters. This method should only be called when * value type is Object. */ // 遍历对象观察每一个属性 Observer.prototype.walk = function walk (obj) { var keys = Object.keys(obj); for (var i = 0; i < keys.length; i++) { defineReactive(obj, keys[i], obj[keys[i]]); } }; /** * Observe a list of Array items. */ // 观察数组的每一项值 Observer.prototype.observeArray = function observeArray (items) { for (var i = 0, l = items.length; i < l; i++) { observe(items[i]); } }; /** * Attempt to create an observer instance for a value, * returns the new observer if successfully observed, * or the existing observer if the value already has one. */ 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 ( observerState.shouldConvert && !isServerRendering() && (Array.isArray(value) || isPlainObject(value)) && Object.isExtensible(value) && !value._isVue ) { ob = new Observer(value); } if (asRootData && ob) { ob.vmCount++; } return ob } /** * Define a reactive property on an Object. */ // 观察数据每个属性的变化(数据修改触发页面渲染等) function defineReactive ( obj, key, val, customSetter, shallow ) { // 实例化依赖收集实例 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; var childOb = !shallow && observe(val); Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { var value = getter ? getter.call(obj) : val; // 如果有正在观察的实例 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); // 执行依赖的watcher实例队列 dep.notify(); } }); } /** * Set a property on an object. Adds the new property and * triggers change notification if the property doesn't * already exist. */ // 设置属性时触发页面渲染 function set (target, key, val) { if (Array.isArray(target) && isValidArrayIndex(key)) { target.length = Math.max(target.length, key); target.splice(key, 1, val); return val } if (key in target && !(key in Object.prototype)) { target[key] = val; return val } var ob = (target).__ob__; if (target._isVue || (ob && ob.vmCount)) { "development" !== 'production' && warn( 'Avoid adding reactive properties to a Vue instance or its root $data ' + 'at runtime - declare it upfront in the data option.' ); return val } if (!ob) { target[key] = val; return val } defineReactive(ob.value, key, val); ob.dep.notify(); return val } /** * Delete a property and trigger change if necessary. */ // 删除属性时触发页面渲染 function del (target, key) { if (Array.isArray(target) && isValidArrayIndex(key)) { target.splice(key, 1); return } var ob = (target).__ob__; if (target._isVue || (ob && ob.vmCount)) { "development" !== 'production' && warn( 'Avoid deleting properties on a Vue instance or its root $data ' + '- just set it to null.' ); return } if (!hasOwn(target, key)) { return } delete target[key]; if (!ob) { return } ob.dep.notify(); } /** * Collect dependencies on array elements when the array is touched, since * we cannot intercept array element access like property getters. */ // 递归数组监听属性 function dependArray (value) { for (var e = (void 0), i = 0, l = value.length; i < l; i++) { e = value[i]; e && e.__ob__ && e.__ob__.dep.depend(); if (Array.isArray(e)) { dependArray(e); } }
computed计算属性:初始化计算属性,观察计算属性,收集计算属性的依赖并把计算属性写入组件实例本身
/** * Perform no operation. * Stubbing args to make Flow happy without leaving useless transpiled code * with ...rest (https://flow.org/blog/2017/05/07/Strict-Function-Call-Arity/) */ function noop (a, b, c) {} /* */ 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); } var computedWatcherOptions = { lazy: true }; // 初始化计算属性 function initComputed (vm, computed) { // $flow-disable-line var watchers = vm._computedWatchers = Object.create(null); // computed properties are just getters during SSR var isSSR = isServerRendering(); // 是不是服务端渲染 for (var key in computed) { var userDef = computed[key]; // 获取计算属性的回调方法,赋值给getter var getter = typeof userDef === 'function' ? userDef : userDef.get; if ("development" !== 'production' && getter == null) { warn( ("Getter is missing for computed property \"" + key + "\"."), vm ); } if (!isSSR) { // 监听组件的computed属性 // create internal watcher for the computed property. watchers[key] = new Watcher( vm, getter || noop, noop, computedWatcherOptions ); } // component-defined computed properties are already defined on the // component prototype. We only need to define computed properties defined // at instantiation here. // 如果key并不属于组件实例本身,定义计算属性 if (!(key in vm)) { defineComputed(vm, key, userDef); } else { if (key in vm.$data) { warn(("The computed property \"" + key + "\" is already defined in data."), vm); } else if (vm.$options.props && key in vm.$options.props) { warn(("The computed property \"" + key + "\" is already defined as a prop."), vm); } } } } // 创建计算属性至组件本身 function defineComputed ( target, key, userDef ) { // 不是服务端渲染 var shouldCache = !isServerRendering(); if (typeof userDef === 'function') { sharedPropertyDefinition.get = shouldCache ? createComputedGetter(key) : userDef; sharedPropertyDefinition.set = noop; } else { sharedPropertyDefinition.get = userDef.get ? shouldCache && userDef.cache !== false ? createComputedGetter(key) : userDef.get : noop; sharedPropertyDefinition.set = userDef.set ? userDef.set : noop; } if ("development" !== 'production' && sharedPropertyDefinition.set === noop) { sharedPropertyDefinition.set = function () { warn( ("Computed property \"" + key + "\" was assigned to but it has no setter."), this ); }; } // 监听组件本身的计算属性 Object.defineProperty(target, key, sharedPropertyDefinition); } // 生成计算属性的getter方法 function createComputedGetter (key) { return function computedGetter () { var watcher = this._computedWatchers && this._computedWatchers[key]; if (watcher) { if (watcher.dirty) { watcher.evaluate(); }
// 注入依赖 if (Dep.target) { watcher.depend(); } return watcher.value } } }
挂载组件:观察整个组件实例,收集依赖,当依赖发生变化重新调用render函数生成新的vnode,再通过diff算法将newVnode与oldVnode对比,更新变化的部分
// 挂载组件 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 ("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); }; } // we set this to vm._watcher inside the watcher's constructor // since the watcher's initial patch may call $forceUpdate (e.g. inside child // component's mounted hook), which relies on vm._watcher being already defined // 观察组件实例,收集依赖项,当依赖项发生变化就重新使用render函数生成vnode new Watcher(vm, updateComponent, noop, null, true /* isRenderWatcher */); hydrating = false; // manually mounted instance, call mounted on self // mounted is called for render-created child components in its inserted hook if (vm.$vnode == null) { vm._isMounted = true; callHook(vm, 'mounted'); } return vm }