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 }