# 首次加载(初始化-数据劫持)
前言
initState
将依次初始化 options 上的 props、methods、data、computed、watch,后面的函数中可以拿到前面初始化后的数据,因为已经放到 this 上了,此处重点就是将data
中的数据通过Object.defineProperty
进行数据劫持,将其变为响应式数据
# 1.initState
- /src/vue/src/core/instance/state.ts
function initData(vm: Component) {
let data: any = vm.$options.data
data = vm._data = isFunction(data) ? getData(data, vm) : data || {}
if (!isPlainObject(data)) {
data = {}
__DEV__ &&
warn(
'data functions should return an object:\n' +
'https://v2.vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
vm
)
}
// proxy data on instance
const keys = Object.keys(data)
const props = vm.$options.props
const methods = vm.$options.methods
let i = keys.length
while (i--) {
const key = keys[i]
if (__DEV__) {
if (methods && hasOwn(methods, key)) {
warn(`Method "${key}" has already been defined as a data property.`, vm)
}
}
if (props && hasOwn(props, key)) {
__DEV__ &&
warn(
`The data property "${key}" is already declared as a prop. ` +
`Use prop default value instead.`,
vm
)
} else if (!isReserved(key)) {
proxy(vm, `_data`, key)
}
}
// observe data
const ob = observe(data)
ob && ob.vmCount++
}
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
# 2.initProps
- /src/vue/src/core/instance/state.ts
function initProps(vm: Component, propsOptions: Object) {
const propsData = vm.$options.propsData || {}
const props = (vm._props = shallowReactive({}))
// cache prop keys so that future props updates can iterate using Array
// instead of dynamic object key enumeration.
const keys: string[] = (vm.$options._propKeys = [])
const isRoot = !vm.$parent
// root instance props should be converted
if (!isRoot) {
toggleObserving(false)
}
for (const key in propsOptions) {
keys.push(key)
const value = validateProp(key, propsOptions, propsData, vm)
/* istanbul ignore else */
if (__DEV__) {
const hyphenatedKey = hyphenate(key)
if (
isReservedAttribute(hyphenatedKey) ||
config.isReservedAttr(hyphenatedKey)
) {
warn(
`"${hyphenatedKey}" is a reserved attribute and cannot be used as component prop.`,
vm
)
}
defineReactive(props, key, value, () => {
if (!isRoot && !isUpdatingChildComponent) {
warn(
`Avoid mutating a prop directly since the value will be ` +
`overwritten whenever the parent component re-renders. ` +
`Instead, use a data or computed property based on the prop's ` +
`value. Prop being mutated: "${key}"`,
vm
)
}
})
} else {
defineReactive(props, key, value)
}
// static props are already proxied on the component's prototype
// during Vue.extend(). We only need to proxy props defined at
// instantiation here.
if (!(key in vm)) {
proxy(vm, `_props`, key)
}
}
toggleObserving(true)
}
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
- 通过
validateProp
对props
进行校验 - 通过
proxy
将props
代理到_props
上
# 3.initMethods
- /src/vue/src/core/instance/state.ts
function initMethods(vm: Component, methods: Object) {
const props = vm.$options.props
for (const key in methods) {
if (__DEV__) {
if (typeof methods[key] !== 'function') {
warn(
`Method "${key}" has type "${typeof methods[
key
]}" in the component definition. ` +
`Did you reference the function correctly?`,
vm
)
}
if (props && hasOwn(props, key)) {
warn(`Method "${key}" has already been defined as a prop.`, vm)
}
if (key in vm && isReserved(key)) {
warn(
`Method "${key}" conflicts with an existing Vue instance method. ` +
`Avoid defining component methods that start with _ or $.`
)
}
}
vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 4.initData
- /src/vue/src/core/instance/state.ts
function initData(vm: Component) {
let data: any = vm.$options.data
data = vm._data = isFunction(data) ? getData(data, vm) : data || {}
if (!isPlainObject(data)) {
data = {}
__DEV__ &&
warn(
'data functions should return an object:\n' +
'https://v2.vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
vm
)
}
// proxy data on instance
const keys = Object.keys(data)
const props = vm.$options.props
const methods = vm.$options.methods
let i = keys.length
while (i--) {
const key = keys[i]
if (__DEV__) {
if (methods && hasOwn(methods, key)) {
warn(`Method "${key}" has already been defined as a data property.`, vm)
}
}
if (props && hasOwn(props, key)) {
__DEV__ &&
warn(
`The data property "${key}" is already declared as a prop. ` +
`Use prop default value instead.`,
vm
)
} else if (!isReserved(key)) {
proxy(vm, `_data`, key)
}
}
// observe data
const ob = observe(data)
ob && ob.vmCount++
}
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
# 5.observe
提示
观测过的数据会打上__ob__
标识,判断数据是对象还是数组,分别走不同的逻辑将数据变为响应式数据,
- /src/vue/src/core/observer/index.ts
export function observe(
value: any,
shallow?: boolean,
ssrMockReactivity?: boolean
): Observer | void {
if (value && hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
return value.__ob__
}
if (
shouldObserve &&
(ssrMockReactivity || !isServerRendering()) &&
(isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value.__v_skip /* ReactiveFlags.SKIP */ &&
!isRef(value) &&
!(value instanceof VNode)
) {
return new Observer(value, shallow, ssrMockReactivity)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Observer 类:实例化的时会实例化一个侦听器,标记观测数据,判断数据是对象还是数据分别执行对应的函数
/src/vue/src/core/observer/index.ts
export class Observer {
dep: Dep
vmCount: number // number of vms that have this object as root $data
constructor(public value: any, public shallow = false, public mock = false) {
// this.value = value
this.dep = mock ? mockDep : new Dep()
this.vmCount = 0
def(value, '__ob__', this)
if (isArray(value)) {
if (!mock) {
if (hasProto) {
/* eslint-disable no-proto */
;(value as any).__proto__ = arrayMethods
/* eslint-enable no-proto */
} else {
for (let i = 0, l = arrayKeys.length; i < l; i++) {
const key = arrayKeys[i]
def(value, key, arrayMethods[key])
}
}
}
if (!shallow) {
this.observeArray(value)
}
} else {
/**
* Walk through all properties and convert them into
* getter/setters. This method should only be called when
* value type is Object.
*/
const keys = Object.keys(value)
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
defineReactive(value, key, NO_INIITIAL_VALUE, undefined, shallow, mock)
}
}
}
/**
* Observe a list of Array items.
*/
observeArray(value: any[]) {
for (let i = 0, l = value.length; i < l; i++) {
observe(value[i], false, this.mock)
}
}
}
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
- /src/vue/src/core/observer/index.ts
export function defineReactive(
obj: object,
key: string,
val?: any,
customSetter?: Function | null,
shallow?: boolean,
mock?: boolean
) {
const dep = new Dep()
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
const getter = property && property.get
const setter = property && property.set
if (
(!getter || setter) &&
(val === NO_INIITIAL_VALUE || arguments.length === 2)
) {
val = obj[key]
}
let childOb = !shallow && observe(val, false, mock)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
const value = getter ? getter.call(obj) : val
if (Dep.target) {
if (__DEV__) {
dep.depend({
target: obj,
type: TrackOpTypes.GET,
key
})
} else {
dep.depend()
}
if (childOb) {
childOb.dep.depend()
if (isArray(value)) {
dependArray(value)
}
}
}
return isRef(value) && !shallow ? value.value : value
},
set: function reactiveSetter(newVal) {
const value = getter ? getter.call(obj) : val
if (!hasChanged(value, newVal)) {
return
}
if (__DEV__ && customSetter) {
customSetter()
}
if (setter) {
setter.call(obj, newVal)
} else if (getter) {
// #7981: for accessor properties without setter
return
} else if (!shallow && isRef(value) && !isRef(newVal)) {
value.value = newVal
return
} else {
val = newVal
}
childOb = !shallow && observe(newVal, false, mock)
if (__DEV__) {
dep.notify({
type: TriggerOpTypes.SET,
target: obj,
key,
newValue: newVal,
oldValue: value
})
} else {
dep.notify()
}
}
})
return dep
}
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
简单实现一个 Obsever 类
- 1.实现对对象的获取操作
利用 Object 中的 defineProperty,在获取数据时触发 get 方法,在设置数据时触发 set 方法
let obj = {
name: "jw",
age: 19,
}
function obsever(obj) {
if (typeof obj == "object") {
for (let key in obj) {
defineReactive(obj, key, obj[key])
}
}
}
function defineReactive(obj, key, value) {
Object.defineProperty(obj, key, {
get() {
console.log("获取数据:" + value)
return value
},
set(val) {},
})
}
obsever(obj)
console.log(obj.name)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
结果:
获取数据:jw
jw
2
- 2.实现对对象的设置操作
对 set 进行修改,获取修改后的值,赋值给 value
set(val) {
console.log("你将数据:"+value+",更新成了:"+val);
value = val;
}
2
3
4
设置数据,会触发 set 方法
obj.age=20
结果:
你将数据:19,更新成了:20
- 3.解决嵌套数据的问题
如下数据,当我们的数据是嵌套的会出现问题
let obj = {
name: "jw",
age: {
age: 19,
},
}
2
3
4
5
6
修改数据
obj.age.age=20
结果:
获取数据:[object Object]
没有触发更新数据的操作
由于对 obsever 中只是对数据中第一层的 key 和 value 进行了遍历,对数据中 value 是否是对象没有管,导致问题产生,所以如果数据中 value 也是一个对象的话我们也要对里面的对象用 defineProperty 定义,所有需要在调用 defineReactive 后,再次调用 obsever(value),产生递归,这样里面的对象也会被拦截
这里我们需要做一些处理
我们需要在 set 和 defineReactive 中设置回调,对数据再次进行监听
function defineReactive(obj, key, value) {
obsever(value)
Object.defineProperty(obj, key, {
get() {
return value
},
set(val) {
console.log("你将数据:" + value + ",更新成了:" + val)
value = val
},
})
}
2
3
4
5
6
7
8
9
10
11
12
结果:
你将数据:19,更新成了:20
- 2.设置数据
修改数据
obj.age={
name:1
}
obj.age.name=2
2
3
4
结果:
你将数据:[object Object],更新成了:[object Object]
出现这种结果是因为我们设置 age 的时候是触发了定义的 defineProperty 方法,但是里面的 name 没有设置 defineProperty,我们应该在设置值的时候也去调用 obsever 方法,当这个 value 是对象时也会添加 defineProperty
所以,需要修改
set(val) {
obsever(val);
console.log("你将数据:"+value+",更新成了:"+val);
value = val;
}
2
3
4
5
结果:
你将数据:[object Object],更新成了:[object Object]
你将数据:1,更新成了:2
2
# 如果属性不存在
默认后增加的内容并不会刷新视图
# 如果设置的是数组
数组调用 push 是无效的,Object.defineProperty 不支持数组
修改数据
obj.age=[1,2,3,4]
obj.age.push(5)
2
结果:
你将数据:[object Object],更新成了:1,2,3,4
我们可以重写 push 方法
let oldPush = Array.prototype.push;
Array.prototype.push= function(value) {
console.log("数据更新了");
oldPush.call(this, value);
};
2
3
4
5
结果:
你将数据:[object Object],更新成了:1,2,3,4
数据更新了
2
可以在 push 数据的时候进行拦截
# 可以把数组上的所有方法进行重写
let arr = ["push", "slice", "shifit", "unshift"];
arr.forEach(method => {
let oldPush = Array.prototype[method];
Array.prototype[method] = function(value) {
console.log("数据更新了");
oldPush.call(this, value);
};
});
2
3
4
5
6
7
8
# 数组不能通过长度修改,也不能通过数组的索引进行修改
修改数组的长度
obj.age=[1,2,3,4]
obj.age.length--
2
结果:
你将数据:[object Object],更新成了:1,2,3,4
改变数组长度没有触发更新操作
# 6.initComputed
- /src/vue/src/core/instance/state.ts
function initComputed(vm: Component, computed: Object) {
// $flow-disable-line
const watchers = (vm._computedWatchers = Object.create(null))
// computed properties are just getters during SSR
const isSSR = isServerRendering()
for (const key in computed) {
const userDef = computed[key]
const getter = isFunction(userDef) ? userDef : userDef.get
if (__DEV__ && getter == null) {
warn(`Getter is missing for computed property "${key}".`, vm)
}
if (!isSSR) {
// 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.
if (!(key in vm)) {
defineComputed(vm, key, userDef)
} else if (__DEV__) {
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)
} else if (vm.$options.methods && key in vm.$options.methods) {
warn(
`The computed property "${key}" is already defined as a method.`,
vm
)
}
}
}
}
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
# 6.1 defineComputed
- /src/vue/src/core/instance/state.ts
export function defineComputed(
target: any,
key: string,
userDef: Record<string, any> | (() => any)
) {
const shouldCache = !isServerRendering()
if (isFunction(userDef)) {
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 (__DEV__ && sharedPropertyDefinition.set === noop) {
sharedPropertyDefinition.set = function () {
warn(
`Computed property "${key}" was assigned to but it has no setter.`,
this
)
}
}
Object.defineProperty(target, key, sharedPropertyDefinition)
}
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
# 7.initWatch
- /src/vue/src/core/instance/state.ts
function initWatch(vm: Component, watch: Object) {
for (const key in watch) {
const handler = watch[key]
if (isArray(handler)) {
for (let i = 0; i < handler.length; i++) {
createWatcher(vm, key, handler[i])
}
} else {
createWatcher(vm, key, handler)
}
}
}
2
3
4
5
6
7
8
9
10
11
# 7.1 createWatcher
- /src/vue/src/core/instance/state.ts
function createWatcher(
vm: Component,
expOrFn: string | (() => any),
handler: any,
options?: Object
) {
if (isPlainObject(handler)) {
options = handler
handler = handler.handler
}
if (typeof handler === 'string') {
handler = vm[handler]
}
return vm.$watch(expOrFn, handler, options)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
# 7.2 $watch
- /src/vue/src/core/instance/state.ts
Vue.prototype.$watch = function (
expOrFn: string | (() => any),
cb: any,
options?: Record<string, any>
): Function {
const vm: Component = this
if (isPlainObject(cb)) {
return createWatcher(vm, expOrFn, cb, options)
}
options = options || {}
options.user = true
const watcher = new Watcher(vm, expOrFn, cb, options)
if (options.immediate) {
const info = `callback for immediate watcher "${watcher.expression}"`
pushTarget()
invokeWithErrorHandling(cb, vm, [watcher.value], vm, info)
popTarget()
}
return function unwatchFn() {
watcher.teardown()
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 7.3 Watcher
- /src/vue/src/core/observer/watcher.ts
export default class Watcher implements DepTarget {
vm?: Component | null
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
onStop?: Function
noRecurse?: boolean
getter: Function
value: any
post: boolean
// dev only
onTrack?: ((event: DebuggerEvent) => void) | undefined
onTrigger?: ((event: DebuggerEvent) => void) | undefined
constructor(
vm: Component | null,
expOrFn: string | (() => any),
cb: Function,
options?: WatcherOptions | null,
isRenderWatcher?: boolean
) {
recordEffectScope(
this,
// if the active effect scope is manually created (not a component scope),
// prioritize it
activeEffectScope && !activeEffectScope._vm
? activeEffectScope
: vm
? vm._scope
: undefined
)
if ((this.vm = vm) && isRenderWatcher) {
vm._watcher = this
}
// options
if (options) {
this.deep = !!options.deep
this.user = !!options.user
this.lazy = !!options.lazy
this.sync = !!options.sync
this.before = options.before
if (__DEV__) {
this.onTrack = options.onTrack
this.onTrigger = options.onTrigger
}
} else {
this.deep = this.user = this.lazy = this.sync = false
}
this.cb = cb
this.id = ++uid // uid for batching
this.active = true
this.post = false
this.dirty = this.lazy // for lazy watchers
this.deps = []
this.newDeps = []
this.depIds = new Set()
this.newDepIds = new Set()
this.expression = __DEV__ ? expOrFn.toString() : ''
// parse expression for getter
if (isFunction(expOrFn)) {
this.getter = expOrFn
} else {
this.getter = parsePath(expOrFn)
if (!this.getter) {
this.getter = noop
__DEV__ &&
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.
*/
get() {
pushTarget(this)
let value
const vm = this.vm
try {
value = this.getter.call(vm, vm)
} catch (e: any) {
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.
*/
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)
}
}
}
/**
* Clean up for dependency collection.
*/
cleanupDeps() {
let i = this.deps.length
while (i--) {
const dep = this.deps[i]
if (!this.newDepIds.has(dep.id)) {
dep.removeSub(this)
}
}
let tmp: any = 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.
*/
update() {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
this.run()
} else {
queueWatcher(this)
}
}
/**
* Scheduler job interface.
* Will be called by the scheduler.
*/
run() {
if (this.active) {
const 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
const oldValue = this.value
this.value = value
if (this.user) {
const info = `callback for watcher "${this.expression}"`
invokeWithErrorHandling(
this.cb,
this.vm,
[value, oldValue],
this.vm,
info
)
} else {
this.cb.call(this.vm, value, oldValue)
}
}
}
}
/**
* Evaluate the value of the watcher.
* This only gets called for lazy watchers.
*/
evaluate() {
this.value = this.get()
this.dirty = false
}
/**
* Depend on all deps collected by this watcher.
*/
depend() {
let i = this.deps.length
while (i--) {
this.deps[i].depend()
}
}
/**
* Remove self from all dependencies' subscriber list.
*/
teardown() {
if (this.vm && !this.vm._isBeingDestroyed) {
remove(this.vm._scope.effects, this)
}
if (this.active) {
let i = this.deps.length
while (i--) {
this.deps[i].removeSub(this)
}
this.active = false
if (this.onStop) {
this.onStop()
}
}
}
}
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
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237