Vue2.x的数据绑定是通过数据劫持的方式来实现的,其中最核心的便是Object.defineProperty()
,而Vue3.0里面数据绑定是通过Proxy
实现的
Vue2.x的双向数据绑定,有三个比较核心的部分
Observer
:通过Object.defineProperty
来做数据劫持,递归地监听对象上的所有属性,在属性值改变的时候,触发相应的Watcher
Watcher
:订阅者,当监听的数据值修改时,执行通知Dep
执行对应的响应的回调函数
Dep
:将Observer
和Watcher
关联起来,这个双向数据绑定实现的设计模式为发布订阅模式,Dep
就相当于发布订阅模式的中间对象
Observer
export class Observer {
value: any;
dep: Dep;
vmCount: number; // number of vms that have this object as root $data
constructor (value: any) {
this.value = value
this.dep = new Dep()
this.vmCount = 0
// 通过Object.defineProperty设置_ob_值,使用_ob_可以直接拿到Observer对象
def(value, '__ob__', this)
if (Array.isArray(value)) {
if (hasProto) {
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
// 如果是数组,调用observeArray处理
this.observeArray(value)
} else {
// 如果是其他对象调用walk处理
this.walk(value)
}
}
walk (obj: Object) {
const keys = Object.keys(obj)
// 遍历对象属性,对每个属性添加数据邦数据绑定
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
observeArray (items: Array<any>) {
// 如果是数组,则循环遍历数组,调用observe
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}
defineReactive
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
const dep = new Dep()
// 获取obj上对应属性key的描述符,如果configurable为false,属性不能改变,return
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}
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添加到target中,然后将dep添加到subs中
dep.depend()
if (childOb)
childOb.dep.depend()
if (Array.isArray(value)) {
// 如果传入的值为数组,遍历这个数组,对每一个元素depend
dependArray(value)
}
}
}
return value
},
set: function reactiveSetter (newVal) {
// 获取对象原来的值
const value = getter ? getter.call(obj) : val
// 如果新设置的值和原来的值一样,直接return
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
// 打印错误
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
// 若属性上设置了getter没设置setter,return
if (getter && !setter) return
if (setter) {
setter.call(obj, newVal)
} else {
// 设置新的值
val = newVal
}
// 创建一个新的observe,并触发对应的更新事件
childOb = !shallow && observe(newVal)
dep.notify()
}
})
}
observe
这个方法主要是返回与对象相关的Observer
对象,如果没有,则新创建一个Observer
对象并返回
defineReactive中还有几个方法,protoAugment
和copyAugment
var arrayProto = Array.prototype
var arrayMethods = Object.create(arrayProto)
function protoAugment (target, src) {
target.__proto__ = src;
}
protoAugment(value, arrayMethods)
对于数组对象来说在当前环境如果能使用__proto__
对象(__proto__ in {}
),则调用protoAugment
,其实就是将当前的数组对象__proto__
指向数组原型对象,然后对于push
、pop
、shift
、unshift
、splice
、sort
、reverse
这些操作数组的方法,遍历,添加到arrayMethods
上,操作之后,调用ob.dep.notify()
触发更新
copyAugment
则是在循环中把arrayMethods
上的arrayKeys
方法添加到value
上
Dep
Dep
的定义比较简单,就是简单的保存了一个Wacher
数组,有一些增删的方法,最后notify
调用对应Wacher
更新的方法
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 () {
const subs = this.subs.slice()
if (process.env.NODE_ENV !== 'production' && !config.async) {
subs.sort((a, b) => a.id - b.id)
}
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
Watcher
export default class Watcher {
......
constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: ?Object,
isRenderWatcher?: boolean
) {
this.vm = vm
if (isRenderWatcher) {
vm._watcher = this
}
vm._watchers.push(this)
// 创建Watcher时传的options
// 如之前调用updateComponet方法时,传入before来调用beforeUpdate钩子函数
if (options) {
this.deep = !!options.deep
this.user = !!options.user
this.lazy = !!options.lazy
this.sync = !!options.sync
this.before = options.before
} else {
this.deep = this.user = this.lazy = this.sync = false
}
this.cb = cb
this.id = ++uid
this.active = true
this.dirty = this.lazy
this.deps = []
this.newDeps = []
this.depIds = new Set()
this.newDepIds = new Set()
this.expression = process.env.NODE_ENV !== 'production'
? expOrFn.toString()
: ''
// 设置getter
// 如果传入expOrFn是一个函数
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
// 如果传入的不是一个函数,通过parsePath解析,赋值给getter
this.getter = parsePath(expOrFn)
if (!this.getter) {
// 如果解析出来的getter不是一个函数,设置为空 noop,并打印错误
this.getter = noop
process.env.NODE_ENV !== 'production' && warn(
`Failed watching path: "${expOrFn}" ` +
'Watcher only accepts simple dot-delimited paths. ' +
'For full control, use a function instead.',
vm
)
}
}
this.value = this.lazy
? undefined
: this.get()
}
/**
* Evaluate the getter, and re-collect dependencies.
*/
get () {
// 设置Dep.tartget为当前watcher,这样它就有addSub、removeSub等方法
pushTarget(this)
let value
const vm = this.vm
try {
// 得到调用getter的value
value = this.getter.call(vm, vm)
} catch (e) {
if (this.user) {
handleError(e, vm, `getter for watcher "${this.expression}"`)
} else {
throw e
}
} finally {
if (this.deep) {
traverse(value)
}
// 清楚Dep,移出target队列,重新设置Dep.target值
popTarget()
this.cleanupDeps()
}
// 在进行模板渲染的时候,value为undefined
return value
}
......
// 在更新数据时,调用dep.notify,会触发update方法
update () {
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
// 若生成Watcher传入配置sync
// 在更新的时候直接调用run方法,调用回掉方法
this.run()
} else {
// 如果不是sync配置,处理Watcher队列
// queueWatcher方法里会使用一个id来表示Watcher的优先级,依次执行上面的run方法
// 更新的时候如果watcher列表正在更新,则把新的watcher添加到对应的位置,并更新
// 否则,在下一个nextTick中执行flushSchedulerQueue
queueWatcher(this)
}
}
run () {
if (this.active) {
const value = this.get()
if (
value !== this.value ||
isObject(value) ||
this.deep
) {
const oldValue = this.value
this.value = value
if (this.user) {
try {
this.cb.call(this.vm, value, oldValue)
} catch (e) {
handleError(e, this.vm, `callback for watcher "${this.expression}"`)
}
} else {
// 回调
this.cb.call(this.vm, value, oldValue)
}
}
}
}
......
}