大家好,我是鼠目。「这是我参与2022首次更文挑战的第4天,活动详情查看:2022首次更文挑战」
数据响应式,是MVVM框架的一大特点,也就是通过某种策略感知数据的变化。大家都知道Vue是利用了Js的Object.defineProperty(),通过定义对象的属性getter/setter拦截了对属性的访问。
它是在那一步做了属性的拦截呢?
记不记得在src/core/instance/init.js下,Vue.prototype._init方法中有个initState方法进行组件数据的初始化,让我们继续深入initState函数,src/core/instance/state.js
export function initState (vm: Component) {
//在vue的实例下,创建了_watcher
vm._watchers = []
const opts = vm.$options
if (opts.props) initProps(vm, opts.props)
if (opts.methods) initMethods(vm, opts.methods)
// 如果,存在options中存在data,对data进行初始化。
if (opts.data) {
initData(vm)
} else {
//否则直接走observe,observe是干什么的呢?后续再进行介绍。
observe(vm._data = {}, true /* asRootData */)
}
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
可以看到,initState函数中,创建了一个watcher数组,并进行了数据初始化,顺序是props->methods->data->computed->watch,所以我们可以在data中直接赋值props传入的数据。现在,让我们直接去看其中最关键的initData。
function initData (vm: Component) {
let data = vm.$options.data
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
// 判断是否有重名信息 props and methods, 代码省略
// proxy 是一层代理, 可以直接通过this.a 访问到this._data.a的数据
proxy(vm, `_data`, key)
// observe data
// 遍历响应式处理
observe(data, true /* asRootData */)
}
所以看出来了,其实initData中最关键的就是这个observe函数,继续进入
export function observe (value: any, asRootData: ?boolean): Observer | void {
// 判断是不是对象,或者是不是虚拟dom,不是对象,或者是虚拟dom的情况,直接返回,不做响应式处理
if (!isObject(value) || value instanceof VNode) {
return
}
// Observer作用?
// 1.将传入value做响应式处理
let ob: Observer | void
// 如果一个对象有__ob__(也就是observer的实例),就说明已经做过响应式处理,则直接返回ob
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
) {
// 初始化传入需要响应式的对象
ob = new Observer(value)
}
// 这个干啥的,也暂时不明白
if (asRootData && ob) {
ob.vmCount++
}
return ob
}
observe函数中,它其实也主要只做了一件事情,就是new Observer(value) 那么这个Observer又是何方神圣呢?
export class Observer {
value: any;
//dep
dep: Dep;
vmCount: number; // number of vms that have this object as root $data
constructor (value: any) {
// 2.此处dep目的?
// 如果使用Vue.set/delete添加或删除属性,负责通知更新
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)
}
this.observeArray(value)
} else {
对象执行的方法
this.walk(value)
}
}
/**
* Walk through all properties and convert them into
* getter/setters. This method should only be called when
* value type is Object.
*/
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
/**
* Observe a list of Array items.
*/
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}