剖析源码响应式变化原理 (三)
一.数据劫持
在上节中我们已经知道,vue在哪里做了状态的初始化(initState
)
export function initState (vm: Component) {
vm._watchers = []
const opts = vm.$options
if (opts.props) initProps(vm, opts.props)
if (opts.methods) initMethods(vm, opts.methods)
if (opts.data) {
initData(vm)
} else {
observe(vm._data = {}, true /* asRootData */)
}
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
这里又进行了细化和拆分,对不同的属性做了不同的初始化操作,原来我们常用的api都在这里做的初始化~
1.数据的初始化
这里我们先关心数据是如何进行初始化操作的
function initData (vm: Component) {
let data = vm.$options.data
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
// 1.数据不是对象则发生异常
if (!isPlainObject(data)) {
data = {}
process.env.NODE_ENV !== 'production' && warn(
'data functions should return an object:\n' +
'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
vm
)
}
const keys = Object.keys(data)
const props = vm.$options.props
const methods = vm.$options.methods
let i = keys.length
// 2.校验数据是否在method中已经声明过
while (i--) {
const key = keys[i]
if (process.env.NODE_ENV !== 'production') {
if (methods && hasOwn(methods, key)) {
warn(
`Method "${key}" has already been defined as a data property.`,
vm
)
}
}
// 3.校验数据是否在属性中已经声明过
if (props && hasOwn(props, key)) {
process.env.NODE_ENV !== 'production' && warn(
`The data property "${key}" is already declared as a prop. ` +
`Use prop default value instead.`,
vm
)
} else if (!isReserved(key)) {
// 4.将_data代理到实例上
proxy(vm, `_data`, key)
}
}
// 5.观测数据
observe(data, true /* asRootData */)
}
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
这里主要是检测属性是否被重复声明,并对属性进行观测
2.观测数据
export function observe (value: any, asRootData: ?boolean): Observer | void {
// 1.如果不是对象直接return
if (!isObject(value) || value instanceof VNode) {
return
}
let ob: Observer | void
// 2.如果已经观测过则直接返回上次观测的实例
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { //
ob = value.__ob__
} else if (
shouldObserve &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
// 3.如果可以观测就进行观测
ob = new Observer(value)
}
// 4.如果是根数据 vmCount标注为1
if (asRootData && ob) {
ob.vmCount++
}
return ob
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
只观测对象数据类型,已经观测的不在进行观测,不能扩展的属性不进行观测。
export class Observer {
constructor (value: any) {
this.value = value
this.dep = new Dep()
this.vmCount = 0
def(value, '__ob__', this)
// 1.数组的话重写数组原型方法
if (Array.isArray(value)) {
if (hasProto) {
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
// 2.观测数组中是对象类型的数据
this.observeArray(value)
} else {
// 3.对象的话使用defineProperty重新定义属性
this.walk(value)
}
}
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}
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
这里要区分对象和数组,如果是数组不能使用Object.defineProperty会造成性能浪费,所以采用重写可以更改数组本身的方法的方式。
3.对象的观测
对象的观测就是将所有属性使用defineProperty
进行重新定义
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
// 1.如果对象不可配置则直接退出
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}
// 2.获取getter和setter
const getter = property && property.get
const setter = property && property.set
// 3.重新定义set和get方法
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
return value
},
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
if (getter && !setter) return
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
}
})
}
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
对象的属性劫持已经烂大街了,非常简单就是通过
defineProperty
来实现的,如果你还不会那得好好反思一下了。这里提一下:想减少观测可以使用Object.freeze
冻结对象
4.数组的观测
数组的观测就是通过重写原型方法来实现的
export const arrayMethods = Object.create(arrayProto)
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
methodsToPatch.forEach(function (method) {
// cache original method
const original = arrayProto[method]
def(arrayMethods, method, function mutator (...args) {
const result = original.apply(this, args)
const ob = this.__ob__
let inserted
switch (method) {
case 'push':
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
break
}
// 对新增的属性再次进行观测
if (inserted) ob.observeArray(inserted)
return result
})
})
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
这里我们所谓的数据观测就是当数据变化时我们可以知道,像对象更改时可以出发
set
方法,像数组调用push
方法可以触发我们自己写的push
二.依赖收集
这里我们要回想一下vue
的渲染过程是通过渲染watcher
来实现的
let updateComponent = updateComponent = () => {
vm._update(vm._render(), hydrating)
}
new Watcher(vm, updateComponent, noop, {}, true /* isRenderWatcher */)
2
3
4
在我们创建
watcher
时,会对变量进行取值
2.1 对象依赖收集
对于对象而言,取值就会触发get方法,我们可以在defineProperty的get中进行依赖收集,在set中通知watcher
进行更新操作
class Watcher {
constructor (
vm: Component,
expOrFn: string | Function, // updateComponent
cb: Function,
options?: ?Object,
isRenderWatcher?: boolean
) {
this.vm = vm
if (isRenderWatcher) {
vm._watcher = this
}
this.cb = cb
this.id = ++uid // 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 = process.env.NODE_ENV !== 'production'
? expOrFn.toString()
: ''
// 将updateComponent 放到this.getter上
this.getter = expOrFn
this.value = this.lazy
? undefined
: this.get() // 执行get方法
}
get () {
pushTarget(this) // Dep.target = 渲染watcher
let value
const vm = this.vm
try {
value = this.getter.call(vm, vm) // 开始取值 那么在get方法中就可以获取到这个全局变量Dep.target
} 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
}
}
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
渲染watcher,默认会调用get方法也就是我们传入的updateComponent方法,在调用此方法前先将watcher存到全局中,这样再取值时可以获取到这个watcher。
const dep = new Dep()
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
if (Dep.target) { // 如果有watcher 将dep和watcher对应起来
dep.depend()
}
return value
}
set: function reactiveSetter (newVal) {
dep.notify(); // 当属性更新时通知dep中的所有watcher进行更新操作
}
2
3
4
5
6
7
8
9
10
11
2.2 数组的依赖收集
let childOb = !shallow && observe(val)
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
}
// 调用数组方法时进行watcher的更新操作
methodsToPatch.forEach(function (method) {
ob.dep.notify()
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
这里的watcher和dep的关系是多对多的关系,一个属性一个dep,每个dep里存放着多个watcher,同时watcher也会记住对应的dep。
export default class Dep {
constructor () {
this.id = uid++
this.subs = []
}
addSub (sub: Watcher) {
this.subs.push(sub)
}
depend () {
if (Dep.target) {
Dep.target.addDep(this) // 让watcher记住自己
}
}
notify () {
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update() // 让存储的watcher依次调用更新方法
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Watcher {
constructor (
vm: Component,
expOrFn: string | Function, // updateComponent
cb: Function,
options?: ?Object,
isRenderWatcher?: boolean
) {
this.deps = []
this.newDeps = []
this.depIds = new Set()
this.newDepIds = new Set()
this.expression = process.env.NODE_ENV !== 'production'
? expOrFn.toString()
: ''
// 1.将updateComponent 放到this.getter上
this.getter = expOrFn
this.value = this.lazy
? undefined
// 2.执行get方法
: this.get()
}
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)) {
// 3.让dep记录watcher
dep.addSub(this)
}
}
}
update () {
queueWatcher(this)
}
}
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
watcher中会进行虑重操作,实现watcher和dep互相记忆
三.异步更新
为了防止多次更改同一个属性或者多次修改不同属性(他们依赖的watcher相同) 会导致频繁更新渲染
export function queueWatcher (watcher: Watcher) {
const id = watcher.id
// 1.判断watcher是否已经存放了
if (has[id] == null) {
has[id] = true
// 2.将watcher存放到队列中
queue.push(watcher)
// queue the flush
if (!waiting) {
waiting = true
nextTick(flushSchedulerQueue) // 在下一队列中清空queue
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
对相同watcher进行过滤操作,当同步的更改状态完毕时再去更新watcher