假设代码是1
2
3<body>
<div id="app">{{sum}}</div>
</body>
1 | const app = new Vue({ |
computed 有函数声明和对象声明,我采用的是函数声明1
2
3
4
5
6
7
8// 对象声明用法
computed:{
sum: {
get:function(){
return this.num+10
}
}
}
实例 Vue
流程
- 先是进入[instance/index] 触发 this._init
- 再进入[instance/init] 触发了 initState
- 接着进入到[instance/state],依次给 props、methods、data、computed、watch 进行 Object.definePrototype() 数据绑定。
- initData、initComputed 都是给里面的 key 进行 Object.defineProperty() 数据订阅劫持(应该是这个名词,网上大家都这么说),但是细节有所不同。并且 data 里的 key 都会有依赖收集 Dep,computed 里的 key 都设定一个监控器 Watcher。
- 再进行完以上步骤后,就会进行实例挂载了
对比 data 与 computed
其中的 Dep、Watcher 是相互作用的,我对它们的理解是:
Dep:dependence 依赖收集,data 就有 Dep,而 computed 没有。它的代码在[observer/dep.js]
Watcher:监控器,需要监控变化。computed 就有 Watcher,而 data 没有。它的代码在[observer/watcher.js]。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// data的主要代码是这个
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
const dep = new Dep()
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}
const getter = property && property.get
const setter = property && property.set
if ((!getter || setter) && arguments.length === 2) {
val = obj[key]
}
let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const 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) {
const value = getter ? getter.call(obj) : val
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
if (getter && !setter) return
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = !shallow && observe(newVal)
dep.notify()
}
})
}
// initComputed 的主要代码是这个:
function initComputed (vm: Component, computed: Object) {
const watchers = vm._computedWatchers = Object.create(null)
const isSSR = isServerRendering()
for (const key in computed) {
const userDef = computed[key]
const getter = typeof userDef === 'function' ? userDef : userDef.get
if (!isSSR) {
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
computedWatcherOptions
)
}
if (!(key in vm)) {
defineComputed(vm, key, userDef)
} else if (process.env.NODE_ENV !== 'production') {
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)
}
}
}
}
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
}
if (process.env.NODE_ENV !== '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)
}
function createComputedGetter (key) {
return function computedGetter () {
const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {
if (watcher.dirty) {
watcher.evaluate()
}
if (Dep.target) {
watcher.depend()
}
return watcher.value
}
}
}看下列表格, computed 多了一个判断条件 watcher.dirty,dirty 是 computed 专属的参数,它的作用可以继续向下看下一步的分析。
注意 dep.depend() 和 watcher.depend() 不一样,前者是 Dep,后者是 Watcher。
Object.defineProperty | data | computed |
---|---|---|
get | function(){ | function(){ |
if (Dep.target) { | if (watcher) { | |
dep.depend() | if (watcher.dirty) { | |
} | watcher.evaluate() | |
} | ||
if (Dep.target) { | ||
watcher.depend() | ||
} | ||
} | ||
set | function(){dep.notify()} | 用户自定义的 |
缓存
先看看 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
173export 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)
// options
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()
: ''
// parse expression for getter
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)
}
}
run () {
if (this.active) {
const value = this.get()
if (
value !== this.value ||
isObject(value) ||
this.deep
) {
const 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 () {
this.value = this.get()
this.dirty = false
}
depend () {
let i = this.deps.length
while (i--) {
this.deps[i].depend()
}
}
teardown () {
if (this.active) {
if (!this.vm._isBeingDestroyed) {
remove(this.vm._watchers, this)
}
let i = this.deps.length
while (i--) {
this.deps[i].removeSub(this)
}
this.active = false
}
}
}computed 的 sum 实例化了一个 Watcher(叫它 Watcher2), new Watcher(vm,getter || noop,noop,computedWatcherOptions),其中 getter 是我们实例中的 function(){return this.num+10}
根据 Watcher 的代码 this.value = this.lazy ? undefined : this.get(),因为 dirty:true,所以它啥都不做,此时 Watcher1.value = undefined
实例挂载
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
30export default class Dep {
static target: ?Watcher;
id: number;
subs: Array<Watcher>;
constructor () {
this.id = uid++
this.subs = []
}
addSub (sub: Watcher) {
this.subs.push(sub)
}
removeSub (sub: Watcher) {
remove(this.subs, sub)
}
depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}
notify () {
const subs = this.subs.slice()
if (!config.async) {
subs.sort((a, b) => a.id - b.id)
}
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}实例挂载时,也会设定一个监控器(叫它 Watcher1)new Watcher(vm, updateComponent, noop, {before () {if (vm._isMounted && !vm._isDestroyed) {callHook(vm, ‘beforeUpdate’)}}}, true),其中 updateComponent 先暂定为虚拟 DOM 的函数,返回空。
根据 Watcher 的代码,因为 dirty:false,所以触发了 Watcher2.get(),其中 value = this.getter.call(vm, vm),this.getter 指向 updateComponent 函数,它会访问 sum 的值,就触发了 sum 的 get 方法。
根据上面表格 computed 的 get 函数,会进行 watcher.dirty 判断。1
2
3if(watcher.dirty){
watcher.evalute()
}因为是刚开始的数据处理,所以 dirty:true,走到了 watcher.evaluate(),根据 Watcher 中 evaluate 的函数定义,它再走到 watcher.get(),其中 value = this.getter.call(vm, vm),它需要获取到 num 的值,就触发了 num 的 get。结果就是 num 的依赖收集器 Dep.subs 存入了 sum 的 Watcher 监控器,sum 的监控器 Watcher.deps 也存入了 num 这个依赖。于是 watcher.value 就获得值了,并且将 dirty: false。
1
2
3if (Dep.target) {
watcher.depend()
}可以自己跟着逻辑走一步,发现 Dep.target 此时指向 Watcher1,但是触发的是 Watcher2 的 depend() 函数,它将自己的 Watcher 里的 deps 的依赖组一个个执行,将此前的 Watcher2 放进依赖收集器里了。
所以最终 num 的依赖收集器 的 subs 有2个 Watcher。依赖更新
假设此时的 num:2,就会触发 num 的 set 方法,开始执行 dep.notify(),注意一下为什么它会用到 sort 函数,因为 subs 里的 Watcher有先后顺序,我们需要先执行 computed 的 Watcher,再执行挂载时的 Watcher。
Watcher2就跑到前面了, Watcher2.run()后只是将 dirty:true 了。
接着执行 Watcher1,因为 sum 脏了,所以它又开始上面实例挂载的步骤了。
这是我画出一个流程图
我研究了好几天了,特别绕,一下 Watcher、一下 Dep。然后我就用 WPS 的流程图把整个流程滑下来,就特别的清晰了。也把 Watcher 与 Dep 的作用都了解清楚了(因为我前几天做了个小小小小 vue,只了解了其中一点点。目前我的 vue 还在继续完善)。