Vue 是通过劫持对象的 setter 和 getter 方法来做数据绑定的,核心的方法是Object.defineProperty()
要实现 MVVM 的双向绑定,需要实现以下几点:
- Observer: 为所有数据添加监听器 Dep
- Dep (监听器), data 中每个对象(和子对象)都有持有一个该对象, 当所绑定的数据有变更时, dep.notify() 方法被调用, 通知 Watcher
- Compile (HTML指令解析器),对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数
- Watcher,作为连接 Observer 和 Compile 的桥梁,当 Compile 解析指令时会生成一个 Watcher 并给它绑定一个 update 方法 , 并添加到当前正在解析的指令所依赖的对象的 Dep 对象上
这样形成的对象结构图就成了下面这样:
源码分析
Vue 对象实例化后会调用到 initData 方法
源码路径: src/instance/state.js
function initData (vm: Component) {
let data = vm.$options.data
data = vm._data = typeof data === 'function' ? data.call(vm) : data || {}
if (!isPlainObject(data)) {
data = {}
process.env.NODE_ENV !== 'production' && warn(
'data functions should return an object.',
vm
)
}
// proxy data on instance
const keys = Object.keys(data)
const props = vm.$options.props
let i = keys.length
while (i--) {
if (props && hasOwn(props, keys[i])) {
process.env.NODE_ENV !== 'production' && warn(
`The data property "${keys[i]}" is already declared as a prop. ` +
`Use prop default value instead.`,
vm
)
} else {
proxy(vm, keys[i]) //把 data 的属性代理到 vm 实例上
}
}
// observe data
observe(data) // 观察 data 对象
data.__ob__ && data.__ob__.vmCount++
}
源码路径: src/observer/index.js
export function observe (value: any): Observer | void {
if (!isObject(value)) {
return
}
let ob: Observer | void
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__ // 如果参数 value 对象中已经存在有效的 __ob__ 对象就直接赋值
} else if ( observerState.shouldConvert &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue) {
ob = new Observer(value) // 新建一个 Observer 对象
}
return ob
}
源码路径: src/observer/index.js
export class Observer {
value: any;
dep: Dep;
vmCount: number; // number of vms that has this object as root $data
constructor (value: any) {
this.value = value
this.dep = new Dep()
this.vmCount = 0
def(value, '__ob__', this) // value.__ob__ = this; 将本对象( Observer)赋值给 value 的 __ob__ 属性, 所以 Vue.$data 中每个对象都 __ob__ 属性,包括 Vue.$data 对象本身
if (Array.isArray(value)) {
const augment = hasProto ? protoAugment : copyAugment
augment(value, arrayMethods, arrayKeys)
this.observeArray(value)
} else {
this.walk(value)
}
}
/**
* Walk through each property and convert them into
* getter/setters. This method should only be called when
* value type is Object.
*/
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i], obj[keys[i]]) // 给对象中每个属性定义 setter 和 getter 方法
}
}
/**
* Observe a list of Array items.
*/
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i]) // 如果是列表继续执行 observe 方法, 其中会继续新建 Observer 对象, 直到穷举完毕执行 walk 方法
}
}
}
/**
* Define a reactive property on an Object.
*/
export function defineReactive (obj: Object, key: string, val: any, customSetter?: Function) {
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 // 获取已经实现的 getter 方法
const setter = property && property.set // 获取已经实现的 setter 方法
let childOb = observe(val)
Object.defineProperty(obj, key, { // 给 obj 对象中的 key 属性定义 setter 和 getter 方法
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
if (Dep.target) { // Dep.target 全局变量指向的就是当前正在解析指令的 Complier 生成的 Watcher
dep.depend() // 会执行到 dep.addSub(Dep.target), 将 Watcher 添加到 Dep 对象的 Watcher 列表中
if (childOb) {
childOb.dep.depend()
}
if (Array.isArray(value)) {
dependArray(value)
}
}
return value
},
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
//....
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = observe(newVal)
dep.notify() // 如果数据被重新赋值了, 调用 Dep 的 notify 方法, 通知所有的 Watcher
}
})
}
源码路径: src/observer/dep.js
export default class Dep {
static target: ?Watcher;
id: number;
subs: Array<Watcher>;
constructor () {
this.id = uid++
this.subs = []
}
addSub (sub: Watcher) {
this.subs.push(sub)
}
removeSub (sub: Watcher) {
remove(this.subs, sub)
}
depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}
notify () {
// stablize the subscriber list first
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update() // 通知所有绑定的 Watcher
}
}
}
源码路径: src/observer/watcher.js
export default class Watcher {
/**
* Subscriber interface.
* Will be called when a dependency changes.
*/
update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
this.run()
} else {
queueWatcher(this) // 大部分情况会执行该方法, 加入到一个等待队列, 在下一次刷新触发的时候一起执行
}
}
}
参考资料
剖析Vue原理&实现双向绑定MVVM
Vue 双向数据绑定原理分析