Watcher 那么现在万事俱备,会在哪里触发getter
中的依赖收集 ? $mount
的时候
1 2 3 4 5 6 7 8 Vue .prototype .$mount = function ( el?: string | Element, hydrating?: boolean ): Component { el = el && inBrowser ? query (el) : undefined return mountComponent (this , el, hydrating) }
$mount
实际上是执行mountComponent
函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 export function mountComponent ( vm : Component , el : ?Element , hydrating?: boolean ): Component { vm.$el = el if (!vm.$options .render ) { vm.$options .render = createEmptyVNode } callHook (vm, 'beforeMount' ) let updateComponent = ( ) => { vm._update (vm._render (), hydrating) } new Watcher (vm, updateComponent, noop, { before () { if (vm._isMounted && !vm._isDestroyed ) { callHook (vm, 'beforeUpdate' ) } } }, true ) hydrating = false if (vm.$vnode == null ) { vm._isMounted = true callHook (vm, 'mounted' ) } return vm }
现在通过computed
深入看下 Watcher
,我们知道计算属性一般是依赖于我们data
中的值做的惰性更新,在之前的代码中,我们知道是通过initComputed
去初始化计算属性的,接着往下看
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 function initComputed (vm : Component , computed : Object ) { const watchers = vm._computedWatchers = Object .create (null ) for (const key in computed) { const userDef = computed[key] const getter = typeof userDef === 'function' ? userDef : userDef.get watchers[key] = new Watcher ( vm, getter || noop, noop, computedWatcherOptions ) if (!(key in vm)) { defineComputed (vm, key, userDef) } } } export function defineComputed ( target : any, key : string, userDef : Object | Function ) { const shouldCache = !isServerRendering () if (typeof userDef === 'function' ) { sharedPropertyDefinition.get = shouldCache ? createComputedGetter (key) : createGetterInvoker (userDef) sharedPropertyDefinition.set = noop } else { sharedPropertyDefinition.get = userDef.get ? shouldCache && userDef.cache !== false ? createComputedGetter (key) : createGetterInvoker (userDef.get ) : noop sharedPropertyDefinition.set = userDef.set || noop } Object .defineProperty (target, key, sharedPropertyDefinition) }
接下来就要去看Watcher
到底干了些什么啦
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 import { warn, remove, isObject, parsePath, _Set as Set , handleError, invokeWithErrorHandling, noop } from '../util/index' import { traverse } from './traverse' import { queueWatcher } from './scheduler' import Dep , { pushTarget, popTarget } from './dep' import type { SimpleSet } from '../util/index' let uid = 0 export default class Watcher { vm : Component ; expression : string; cb : Function ; id : number; deep : boolean; user : boolean; lazy : boolean; sync : boolean; dirty : boolean; active : boolean; deps : Array <Dep >; newDeps : Array <Dep >; depIds : SimpleSet ; newDepIds : SimpleSet ; before : ?Function ; getter : Function ; value : any; constructor ( vm : Component , expOrFn : string | Function , cb : Function , options?: ?Object , isRenderWatcher?: boolean ) { this .vm = vm if (isRenderWatcher) { vm._watcher = this } vm._watchers .push (this ) if (options) { this .deep = !!options.deep this .user = !!options.user this .lazy = !!options.lazy this .sync = !!options.sync this .before = options.before } else { this .deep = this .user = this .lazy = this .sync = false } this .cb = cb this .id = ++uid this .active = true this .dirty = this .lazy this .deps = [] this .newDeps = [] this .depIds = new Set () this .newDepIds = new Set () this .expression = process.env .NODE_ENV !== 'production' ? expOrFn.toString () : '' if (typeof expOrFn === 'function' ) { this .getter = expOrFn } else { this .getter = parsePath (expOrFn) if (!this .getter ) { this .getter = noop process.env .NODE_ENV !== '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 () } get () { pushTarget (this ) let value const vm = this .vm try { value = this .getter .call (vm, vm) } catch (e) { if (this .user ) { handleError (e, vm, `getter for watcher "${this .expression} "` ) } else { throw e } } finally { if (this .deep ) { traverse (value) } popTarget () this .cleanupDeps () } return value } addDep (dep : Dep ) { const id = dep.id if (!this .newDepIds .has (id)) { this .newDepIds .add (id) this .newDeps .push (dep) if (!this .depIds .has (id)) { dep.addSub (this ) } } } cleanupDeps () { let i = this .deps .length while (i--) { const dep = this .deps [i] if (!this .newDepIds .has (dep.id )) { dep.removeSub (this ) } } let 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 } update () { if (this .lazy ) { this .dirty = true } else if (this .sync ) { this .run () } else { queueWatcher (this ) } } ... }
配合着图再走一遍流程