Vue的响应式原理主要用到了ES5的Object.defineProperty(obj, prop, descriptor),它能传三个参数
obj:需要定义属性的对象prop:是需要定义或修改的属性的名称descriptor:需要被定义或修改的属性描述符。
我们主要看descriptor,它有很多可选键值
configurable:当且仅当该属性的configurable键值为true时,该属性的描述符才能够被改变,同时该属性也能从对应的对象上被删除。 默认为false。enumerable:当且仅当该属性的enumerable键值为true时,该属性才会出现在对象的枚举属性中。默认为false。value:该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。默认为undefined。writable:当且仅当该属性的writable键值为true时,属性的值,也就是上面的value,才能被赋值运算符(en-US)改变。默认为false。
除了上面几个可选键值外,主要还有 get 和 set
get:属性的 getter 函数,如果没有 getter,则为undefined。当访问该属性时,会调用此函数。执行时不传入任何参数,但是会传入this对象(由于继承关系,这里的this并不一定是定义该属性的对象)。该函数的返回值会被用作属性的值。 默认为undefined。set:属性的 setter 函数,如果没有 setter,则为undefined。当属性值被修改时,会调用此函数。该方法接受一个参数(也就是被赋予的新值),会传入赋值时的this对象。默认为undefined。
在Vue中一旦对象拥有了getter和setter,我们可以简单地把这个对象称为响应式对象,下面来看下响应式对象的具体是怎么样的。
响应式对象
在Vue初始化的时候,执行了_init方法,定义在vue/src/core/instance/init.js,该方法中执行了initState方法,定义在vue/src/core/instance/state.js里面。
这里主要初始化了props,methods,data,computed,watch等,我们可以通过this.message就能访问到data中定义的属性就是通过Object.defineProperty方法,通过_data代理到我们真正的data。
现在再来看下initData,除了代理外还做了响应式操作
在initData最后执行了observe方法,它定义在vue/src/core/observe/index.js,该方法主要执行了new Observer(value)方法
这个Observer类主要做了
- 初始化
oberve实例,赋值给传入data的__ob__属性 - 执行
walk(因为一开始传入data是对象)
而这个walk方法主要遍历传入的data对象,并将该对象和key传入defineReactive,这个defineReactive方法就是响应式原理的核心方法
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()
}
// #7981: for accessor properties without setter
if (getter && !setter) return
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = !shallow && observe(newVal)
dep.notify()
}
})
}
我们可以看到这个方法中又执行let childOb = !shallow && observe(val),它就会判断如果属性又是对象就会递归调用observer让每一层都会转换成响应式对象
而这个方法的的核心就是在对传入的遍历属性定义get和set,下面我们来看看get和set做了什么
依赖收集
Dep
当执行defineReactive方法中,首先执行了const dep = new Dep(),定义在vue/src/core/observer/dep.js中,它有一个静态属性target,它会存放同一时间只能有一个的全局的Watcher
Watcher
Dep主要是用来收集Watcher,而Watcher是什么呢,我们在mountComponent的时候会初始化new Watcher
定义在vue/src/core/observe/watcher.js
Watcher和Dep是互不分离的,而Dep是整个getter依赖收集的核心。
下面来看下getter主要做了什么
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
}
- 执行
dep.depend将各种Watcher收集到dep实例的subs中 childOb.dep.depend(),如果是对象则再对对象的dep收集Watcher- 如果传入的数组则执行
dependArray(value)进行遍历,若存在__ob__执行e.__ob__.dep.depend()收集Watcher
派发更新
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()
}
// #7981: for accessor properties without setter
if (getter && !setter) return
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = !shallow && observe(newVal)
dep.notify()
}
setter主要执行了dep.notify()
notify
这里将之前getter收集存放Watcher的subs属性进行遍历,执行subs[i].update(),即执行watcher.update(),定义在vue/src/core/observe/watcher.js
这里主要执行queueWatcher,定义在vue/src/core/observe/scheduler.js
flushSchedulerQueue
这是一个调度器,它这里做了优化,将需要执行Watcher先推入到queue队列中,最后调用 nextTick(flushSchedulerQueue),当同一时间循环下,现将各种各样的Watcher都推入到队列中,在下一个时间tick中再将队列取出执行flushSchedulerQueue。
run
这里主要执行watcher.run()
可以看到主要调用了const value = this.get()
get
在调用get方法中执行了this.getter,就是传进来的updateComponent回调
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
所以会重新render和patch。