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
。