组件初始化时会执行initState方法,主要是对 props、methods、data、computed、watcher 等属性做了初始化操作。这里我们分析 data
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)
}
}
-
initData
function initData (vm: Component) {
let data = vm.$options.data
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
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
)
}
// 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 (process.env.NODE_ENV !== 'production') {
if (methods && hasOwn(methods, key)) {
warn(
`Method "${key}" has already been defined as a data property.`,
vm
)
}
}
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)) {
proxy(vm, `_data`, key)
}
}
// observe data
observe(data, true /* asRootData */)
}
initData主要做了两件事:
1、调用了proxy 把data的数据代理代理vue实例上。
2、调用observer把数据变成响应式。我们重点分析observer
-
observer && Observer
export function observe (value: any, asRootData: ?boolean): Observer | void {
//......此处省去无关紧要代码
ob = new Observer(value)
return ob
}
export class Observer {
value: any;
dep: Dep;
vmCount: number;
constructor (value: any) {
this.value = value
this.dep = new Dep()
this.vmCount = 0
def(value, '__ob__', this)
if (Array.isArray(value)) {
//......实现数组响应式 后面的文章有单独的分析
} else {
this.walk(value)
}
}
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
}
Observer主要做了三件事:
1、给对象添加一个__ob__的属性;
2、给对象添加一个dep 依赖收集器;
3、循环对象,调用defineReactive,给对象每个属性值添加响应式
-
defineReactive
export const defineReactive = (
obj: Object,
key: string,
val: any,
customSetter?: Function,
shallow?: boolean
) => {
const dep = new Dep()
//......此处省略一些次要代码
let childOb = !shallow && observer(val)
Object.defineProperty(obj, key, {
get() {
if(Dep.target) {
dep.depend()
if(childOb) {
childOb.dep.depend()
}
}
return val
},
set(newVal) {
if(val !== newVal) {
val = newVal
childOb = observer(newVal)
dep.notify()
}
}
})
}
defineReactive给对象的属性实现响应式:
1、给对象的属性创建一个Dep依赖收集器
2、执行observer(val),因为可能是嵌套对象
3、定义get方法,当对象的该属性被访问时,执行dep.depend收集器dep会收集Watcher对象(观察者)。那什么时候会执行get方法,Watcher对象又是谁呢?
- Dep是一个Class,它有一个静态属性
target
,这是一个全局的Watcher,同一个时间只能有一个全局的Watcher被计算 - Vue的观察者Watcher分为三类。第一种是渲染Watcher,用来观察数据变化后重新渲染dom的;第二种是computed Watcher;第三种是自定义Watcher
- 当Vue组件初始化computed计算属性时,计算属性会使用data的值,那么get方法就会被执行,此时的Watcher对象就是computed Watcher
- 当Vue组件初始化自定义watch的时候,我们用watch去监听data的变化,此时该data的get方法会被执行,此时的Watcher对象就是自定义的Watcher
- 当Vue组件执行mount挂载组件的时候,会去生成VNode,VNode会访问到data,然后get方法会被执行,此时Watcher对象就是渲染Watcher
4、定义set方法,当数据发生改变时,执行dep.notify派发通知,get方法收集的Watcher接到通知后执行对应的操作,如果是渲染Watcher则执行更新dom的操作。新的数据可能也是个对象·,所以需要执行observer(newVal),给新值添加响应式
5、大家可能有个疑问,childOb.dep.depend()这里的作用。childOb是一个Observer对象,我们上面分析Observer的时候,Observer对象里也有一个dep收集器,Observer会执行this.dep=new Dep(),并且循环对象调用defineReative。defineReative已经给对象的属性添加了dep,那Observer对象里的this.dep有什么用呢