建议PC端观看,移动端代码高亮错乱
在 Vue 的初始化阶段,_init 方法执行的时候,会执行 initState(vm) 方法:
// src/core/instance/state.js
export function initState (vm: Component) {
// ...
const opts = vm.$options
if (opts.props) initProps(vm, opts.props)
if (opts.data) {
initData(vm)
} else {
observe(vm._data = {}, true /* asRootData */)
}
// ...
}
initState 方法主要是对 props、methods、data、computed 和 wathcer 等属性做了初始化操作。这里我们重点分析 props 和 data,对于其它属性的初始化我们之后再详细分析。
看个流程图:
其中 props 部分之后的章节还会深入分析,这里只需知道 initProps 调用了 defineReactive 即可
1. initProps
// src/core/instance/state.js
function initProps (vm: Component, propsOptions: Object) {
const propsData = vm.$options.propsData || {}
const props = vm._props = {}
const keys = vm.$options._propKeys = []
const isRoot = !vm.$parent
if (!isRoot) {
// 关闭观测的开关,observe将是无效调用
toggleObserving(false)
}
for (const key in propsOptions) {
keys.push(key)
const value = validateProp(key, propsOptions, propsData, vm)
// 简化后的
defineReactive(props, key, value)
// 数据代理,对于非根实例的子组件而言,代理发生在 Vue.extend 阶段
// 这是一种优化手段,不用为每个组件实例都调用 Object.defineProperty 来实现代理
if (!(key in vm)) {
proxy(vm, `_props`, key)
}
}
// 打开观测的开关
toggleObserving(true)
}
initProps 的初始化主要过程:
- 关闭观测的开关,具体的之后章节还会介绍,这里先简单了解:
- 其实就是将
src/core/observer/index.js文件中的shouldObserve全局变量置为false。 - 这使得
defineReactive中调用observe是一个无效调用。 - 因为对于对象的
prop值,子组件的prop值始终指向父组件的prop值,只要父组件的prop值变化,就会触发子组件的重新渲染,所以这个observe过程是可以省略的。
- 其实就是将
- 遍历定义的
props配置。遍历的过程主要做两件事情:- 一个是调用
defineReactive方法把每个prop对应的值变成响应式,可以通过vm._props.xxx访问到定义props中对应的属性。 - 另一个是通过
proxy把对vm.xxx的访问代理到vm._props.xxx上。这里的一个细节是对于非根实例的子组件而言,代理发生在Vue.extend阶段,在之后章节还会介绍。
- 一个是调用
- 开启观测的开关。
2. initData
// src/core/instance/state.js
function initData (vm: Component) {
let data = vm.$options.data
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
// ...
const keys = Object.keys(data)
// ...
let i = keys.length
while (i--) {
const key = keys[i]
// 简化后的...
proxy(vm, `_data`, key)
}
// observe data
observe(data, true /* asRootData */)
}
initData 的初始化主要做两件事:
- 遍历
data对象,通过proxy把vm.xxx代理到vm._data.xxx上 - 调用
observe方法观测整个data的变化,把data也变成响应式,可以通过vm._data.xxx访问到定义data中对应的属性。
可以看到,无论是 props 或是 data 的初始化都是把它们变成响应式对象,这个过程我们接触到几个函数,接下来我们来详细分析它们。
其中 proxy 在之前的章节已经介绍过了,这里就不介绍了。
3. observe
observe 的功能就是用来监测数据的变化,它的定义在 src/core/observer/index.js 中:
// src/core/observer/index.js
// 观测开关
export let shouldObserve: boolean = true
export function toggleObserving (value: boolean) {
shouldObserve = value
}
export function observe (value: any, asRootData: ?boolean): Observer | void {
if (!isObject(value) || value instanceof VNode) {
return
}
let ob: Observer | void
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__
} else if (
shouldObserve && // 观测开关打开
!isServerRendering() && // 非SSR
(Array.isArray(value) || isPlainObject(value)) && // value是数组或普通对象
Object.isExtensible(value) && // value可扩展
!value._isVue // value是非vue对象
) {
ob = new Observer(value)
}
if (asRootData && ob) {
ob.vmCount++
}
return ob
}
observe 方法的作用就是给非 VNode 的对象类型数据添加一个 Observer,如果已经添加过则直接返回,否则在满足一定条件下(见注释)去实例化一个 Observer 对象实例。接下来我们来看一下 Observer 的作用。
4. Observer
Observer 是一个类,它的作用是给对象的属性添加 getter 和 setter,用于依赖收集和派发更新:
// src/core/observer/index.js
export class Observer {
value: any;
dep: Dep;
vmCount: number; // 将当前对象作为根$data的vm实例的数量
constructor (value: any) {
this.value = value
this.dep = new Dep()
this.vmCount = 0
def(value, '__ob__', this)
if (Array.isArray(value)) {
// ...
this.observeArray(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])
}
}
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}
Observer 的构造函数逻辑很简单,首先实例化 Dep 对象,这是给 Vue.set 用的,在之后的章节会介绍。
接着通过执行 def 函数把自身实例添加到数据对象 value 的 __ob__ 属性上,def 的定义在 src/core/util/lang.js 中:
// src/core/util/lang.js
export function def (obj: Object, key: string, val: any, enumerable?: boolean) {
Object.defineProperty(obj, key, {
value: val,
enumerable: !!enumerable,
writable: true,
configurable: true
})
}
value对象上多了一个__ob__的属性,指向Observer实例。- 同时
enumerable默认置为false,这样当for循环时不会遍历到这个属性。
回到 Observer 的构造函数,接下来会对 value 做判断,对于数组会调用 observeArray 方法,否则对纯对象调用 walk 方法。可以看到 observeArray 是遍历数组再次调用 observe 方法,而 walk 方法是遍历对象的 key 调用 defineReactive 方法,那么我们来看一下这个方法是做什么的。
5. defineReactive
defineReactive 的功能就是定义一个响应式对象,给对象动态添加 getter 和 setter,它的定义在 src/core/observer/index.js 中:
// src/core/observer/index.js
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
}
// cater for pre-defined getter/setters
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 () {
// ...
return value
},
set: function reactiveSetter (newVal) {
// ...
}
})
}
defineReactive 函数最开始初始化 Dep 对象的实例,接着拿到 obj 的属性描述符,然后对子对象递归调用 observe 方法,这样就保证了无论 obj 的结构多复杂,它的所有子属性也能变成响应式的对象,这样我们访问或修改 obj 中一个嵌套较深的属性,也能触发 getter 和 setter。最后利用 Object.defineProperty 去给 obj 的属性 key 添加 getter 和 setter。而关于 getter 和 setter 的具体实现,我们会在之后介绍。
总结
如果我们有如下的对象
{
a: 1,
b: [2, 3, 4],
c: {
d: 5
}
}
经过观测之后:
{
__ob__, // Observer类的实例,里面保存着Dep实例 __ob__.dep => dep(uid:0)
a: 1, // 在defineReactive闭包里存在dep(uid:1)
b: [2, 3, 4], // 在defineReactive闭包里存在着dep(uid:2),还有b.__ob__.dep => dep(uid:3)
c: { // 在defineReactive闭包里存在着dep(uid:4)
__ob__, // Observer类的实例,里面保存着Dep实例__ob__.dep => dep(uid:5)
d: 5 // 在闭包里存在着dep(uid:6)
}
}