前言
vue 中数据是普通的js对象,当数据变化时,视图会进行更新。数据之所以能驱动视图的更新,关键的部分就是它的响应式系统。
初始化数据
-
initState
在new Vue()初始化阶段,this._init(options)方法执行的时候,会执行initState(vm),它定义在src/core/instance/state.js中。
export function initState (vm: Component) {
// ...
if (opts.data) {
initData(vm)
}
// ...
}
initState的作用主要是初始化props,methods,data,computed,watcher等属性,这里我们重点看看initData。
-
initData
function initData (vm: Component) {
// ...
observe(data, true /* asRootData */)
}
initData,初始化data用的,在最后有一行observe(data, true)
-
observe
export function observe (value: any, asRootData: ?boolean): Observer | void {
// ...
ob = new Observer(value)
// ...
}
observe()是将用户定义的数据data变成响应式的数据。主要是去实例化一个Observe对象实例,用于收集依赖和派发更新。
-
new Observer(value)
export class Observer {
constructor (value: any) {
this.value = value
this.walk(value)
}
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
}
在Observer中,对对象的每一项执行,defineReactive(obj, keys[i])。
-
defineReactive
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
const dep = new Dep() // 依赖管理器
val = obj[key]
observe(val) // 递归对象
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
// 收集依赖
},
set: function reactiveSetter (newVal) {
// 更新依赖
}
})
}
收集依赖
// mountComponent
export function mountComponent(
vm: Component, // 组件实例
el: ?Element, // 挂载的元素
hydrating?: boolean // 服务端渲染相关
): Component {
...
updateComponent = () => {
// vm._render(),生成 vnode,在 instance/render.js 中
// vm._update(),更新 dom
vm._update(vm._render(), hydrating)
}
// watcher 会调用 updateComponent,先生成 vnode ,然后调用 update 更新 dom;
new Watcher(vm, updateComponent, noop, {
before() { ... }
}, true /* isRenderWatcher */)
return vm
}
// watcher
export default class Watcher {
constructor(
vm: Component, // 组件实例
expOrFn: string | Function,
cb: Function, // 当监听的数据变化时,会触发该回调
options?: ?Object,
isRenderWatcher?: boolean
) {
...
// expOrFn 是 `updateComponent` 方法
this.getter = expOrFn
this.value = this.lazy
? undefined
: this.get()
}
get() {
// 添加 target,相当于 watcher
pushTarget(this)
...
try {
// 相当于执行 updateComponent()
value = this.getter.call(vm, vm)
} catch (e) {
...
}
}
vue 在mount 过程中会执行mountComponent,而在mountComponent中会new 一个 Watcher,然后里面执行this.get()方法。这个get()方法中,又执行了pushTarget(this),是将当前的watcher保存到数组中。
Dep.target = null
const targetStack = []
export function pushTarget (target: ?Watcher) {
targetStack.push(target)
Dep.target = target // 当前的 watcher 实例
}
pushTarget 的定义在 src/core/observer/dep.js 中。然后get()方法中,执行了
this.getter.call(vm, vm) // 相当于执行vm._update(vm._render(), hydrating)
首先执行的vm._render()方法中,如果有使用data数据,会触发defineProperty重写的get方法。每个对象值都会持有一个dep,在触发get时,会调用dep.depend()方法。
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
if (Dep.target) {
dep.depend()
// ...
}
return value
},
set: function reactiveSetter (newVal) {
// 派发更新
}
})
export default class Dep {
static target: ?Watcher;
id: number;
subs: Array<Watcher>;
// ...
constructor () {
this.id = uid++
this.subs = [] // 对象 key 值对应的 watcher 集合
}
addSub (sub: Watcher) {
this.subs.push(sub) // 将 watcher 添加到subs 数组中
}
depend () {
if (Dep.target) {
Dep.target.addDep(this) // 执行 addDep
}
}
// ...
}
-------------------------------------------------
addDep(dep: Dep) {
const id = dep.id
if (!this.newDepIds.has(id)) {
// ...
dep.addSub(this) // 执行 dep.addSub
}
}
dep.depend()会执行Dep.target.addDep(this)方法。接着执行dep.addSub(this)方法,最后会执行this.subs.push(sub)。也就是说,data对象每个key值对应的watcher,都放入到subs中,后续数据如果有变化,可以通知 watcher 需要进行更新。
派发更新
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
// 收集依赖
},
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
// ...
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = !shallow && observe(newVal)
dep.notify()
}
})
当我们对响应的数据做了改变时,会触发set 方法,有两个比较关键的地方是,一个是childOb = !shallow && observe(newVal),它是将新设置的值变成一个响应式的值。另一个是dep.notify(),通知watcher进行更新。notify -> update -> queueWatcher。
notify () {
const subs = this.subs.slice()
// ...
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
------------------------------------------------
update() {
// ...
queueWatcher(this) //
}
-
queueWatcher
const queue = []
let has = {}
export function queueWatcher (watcher: Watcher) {
const id = watcher.id
if (has[id] == null) {
has[id] = true
// ...
queue.push(watcher) // 将要更新的 watcher 添加到 queue 队列
// ...
nextTick(flushSchedulerQueue) // 异步更新,相当于 promise.then
}
}
queueWatcher是将要更新的watcher添加到queue,然后在nextTick后执行flushSchedulerQueue,统一更新这些watcher。
-
flushSchedulerQueue
function flushSchedulerQueue () {
currentFlushTimestamp = getNow()
flushing = true
let watcher, id
queue.sort((a, b) => a.id - b.id)
// ...
for (index = 0; index < queue.length; index++) {
watcher = queue[index]
if (watcher.before) {
watcher.before()
}
id = watcher.id
has[id] = null
watcher.run()
}
// ...
}
flushSchedulerQueue主要的一些作用是:
-
队列排序
queue.sort((a, b) => a.id - b.id)
每次更新按着id(new watcher 时生成) 的大小顺序来更新。
1、组件更新顺序是,先父组件,然后在是子组件。
2、自定义的watch,优先于渲染watcher,因为自定义watcher先创建的.computed watcher->自定义 watcher -> render watcher,这个顺序是根据它们初始化顺序来的。
3、如果一个子组件在父组件的watcher执行期间被销毁,那么这个子组件的watcher会被跳过,所以父组件应该先执行。 -
watcher.run()
run() {
if (this.active) {
const value = this.get()
// ...
if (
value !== this.value
) {
const oldValue = this.value
this.value = value
// ...
this.cb.call(this.vm, value, oldValue)
// ...
}
// ...
}
}
run()方法是执行watcher 的回调函数,先通过this.get()获得当前的新值value,使用this.value获取旧值oldValue,然后将这两个值传入到回调函数中,所以,在我们在watch 中可以拿到新值和旧值的原因。同时,在执行this.get()方法时,会再次触发vm._update(vm._render(), hydrating),然后进行diff比对,进行视图的更新。
总结
在组件初始化时,vue会使用defineProperty劫持data 数据的get 和set 方法。在new Watcher 时,会触发data对象的get方法,进行数据依赖收集。当数据改变时,会触发data对象的set方法,进行数据的更新。