前言
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
方法,进行数据的更新。