前提:已经clone下来vue源码
vue的data属性是在实际使用中最常见的了,以及大家老生常谈的双向绑定。本篇文章介绍了data属性的初始化以及双向绑定中model绑定部分。
先概述一下整体的逻辑过程:
1、初始化vue实例
2、在实例created生命周期之前执行initState函数,这个函数对props、methods、data、computed、watch初始化
3、在data初始化中,vue会对data绑定一个观察者Observer,每次data中的字段更新后都会通知依赖收集器Dep触发响应式更新
接下来说正题:data是在new Vue中初始化的,看一下具体路径
1、new Vue初始化位置:/src/core/instance/index.js中的initMixin函数中
2、initMixin函数在/src/core/instance/state.js中调用了同文件中的initState函数
3、在initState函数中首先判断了用户是否已经配置了data属性,如下图
4、我们直接看一下initData函数
在我们使用vue的时候data可以有两种方式定义,一种是用函数返回一个对象,另一种是直接定义一个对象。
1、在initData函数中,首先判断了data类型是否为函数,如果为函数的话则调用getData函数只是.call一下函数返回对象,将对象首先复制到实例的_data上。
2、这里isPlainObject函数是通过调用data对象的toString函数判断是否为Object,它判断了_toString.call(obj) === '[object Object]'
3、keys是data中的所有字段、props是实例中的props、methods是实例中的methodes,while开始对data中每个key进行遍历,首先判断methods中是否有重名的key,然后再判断props中是否有重名的key。这里还会通过isReserved函数判断是用来判断是否用到了保留关键字符“_”和“$”
4、在key字符串中不含有关键字符,接下来会调用proxy函数,这个函数的工作就是设置好key的get、set属性并配置到vue实例上。这里我们看到proxy中vue.key的get、set方法实际获取、改变的都是vue._data.key。
双向绑定之Observer
接下来就是重头戏了,vue双向绑定中其中之一model绑定,接上文的源码最后一行回调用observe函数开始model绑定。在开始继续上文之前我们先看一下Observe都做了什么
先看看人家官方解释:观察者类附加到每个观察对象。 一旦连接,观察者将目标对象的属性键转换为收集依赖关系和分派更新的getter / setter。说一下这个Observer类:
1、constructor中初始化了value(data\key)、dep(依赖收集器)、vmCount(订阅此观察者的节点)
2、接下来调用了def函数,为data定义__ob__属性,可以理解为将data绑定了一个observer,__ob__指向了这个oberver
3、接下来会判断value是否为数组,我们先看一下value为对象的情况,它会调用this.walk函数:walk函数先获取对象的所有key,然后遍历key并执行defineReactive函数。
我们来看看defineReactive函数都干嘛了(一张图截不下就直接上源码吧)
/**
* Define a reactive property on an Object.
*/
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 () {
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
},
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = !shallow && observe(newVal)
dep.notify()
}
})
}
复制代码
面试中、各种文章中说的双向绑定的get、set就在这个函数中实现了,让我们来解析一下吧
1、new了一个dep依赖收集(后面文章会提起)
2、通过getOwnPropertyDescriptor获取到data中key对应的访问器属性赋值给property,并判断当前key是否在data中并且是可配置的(configurable为true才可以)
3、获取访问器属性中的get、set属性,判断存在setter或不存在getter以及实参各户只有两个时将val为data中key对应的value(在walk中就是传了两个参数)
4、这里我们看到了shallow字段,这里我理解为对val的深度\浅度遍历(如果为对象的话)绑定一个观察者。那么由于walk函数中并没有传则默认!shallow为ture。observe后面我们在讨论
5、接下来就到了万人皆知的get、set了,先说get属性,我看到了它会先判断是否已经设置get属性(第3条中获取),如果有则call getter函数,如果没有则直接返回val值。接下来会判断Dep的target(它的作用后面文章会搭配watcher详细说),这里先理解为每次get的时候都会订阅到Dep的subs中,包括子元素、如果为数组的话则数组中每个子元素都会订阅到Dep的subs中。
6、再看看set属性,首先通过getter来获取到value,然后判断如果新的value与旧值===全等、新value与旧value的getter方法被重写过(每次get的value不一致)则不做任何事情,这也就是当我们this.sthKey = oldValue重复赋值相同,不会触发响应式的原因。
7、接下来判断了一下是否有用户自定义的setter,如果有则setter.call(),如果没有的话则是常规赋值操作。
8、这里我们看到它再次对新的value进行了一次深度遍历为每个子元素注册一个观察者。
9、最后就是dep.notify()了,这个函数就是在data中的数据set之后观察者告诉订阅者要更新了完成model to view的响应式操作。
10、到这里就将data中每个key的get、set属性进行重写结束了,双向绑定也完成了一半。
11、我们回头看一下如果是数组的话,vue是如何做响应式的呢?首先判断了hasProto这个是一个布尔型它返回了__proto__是否在对象中
这里augment会根据hasProto赋值上面两个不同的函数,protoAugemnt很简单就是将value的__proto__赋值arrayMethods(arrayMethods为Array.prototype),copyAugment则遍历arrayKeys(arrayKeys为arrayMethods的OwnPropertyNames)每一个key,调用def函数,为data定义key属性(也就是定义Array.prototype)。
接下来就是遍历整个数组,对每个子元素调用observe函数。
我们来看看observe函数的实现。
1、首先函数判断了value必须为对象且不为VNode节点
2、初始化一个Observer对象,判断value是否含有__ob__属性且该属性是Observer对象,如果均满足则将初始化的Observer对象指向了value的__ob__属性,其次判断shouldObserve(常量默认为true)、是否为服务端渲染、value为数组或为对象、value对象是否可扩展(isExtensible es6语法)、是否为vue实例,经过以上的判断后ob又指向了新Observer对象(由value初始化)
3、判断当前的asRootData当前的value是否为根结点的data,以及ob对象存在,则ob订阅此观察者的节点加一,最终返回ob对象。
以上就是笔者的理解,接下来会尽量将vue的各个模块都遍历到,如有任何建议欢迎提出。