VUE中的数据双向绑定是通过数据劫持的方式实现的,核心的便是object.defineProperty(),它内部分为三个部分:
observer 可以递归地监听对象上的所有属性,当属性改变时触发相应的watcher。
watcher 观察者,当监听的数据值修改时,执行相应的回调函数,更新模板内容。
dep 连接observer watcher,每一个observer对应一个dep,内部维护一个数组,保存与该observer相关的watcher。
由初始化数据进入到observe(value)方法,为给定的数据绑定observer实例,在observe方法中的核心就是
ob = new Observer(value)
在getter方法中,把watcher添加到dep中,在setter方法中,触发watcher执行回调(生成render函数,生成虚拟dom,映射在页面上)。
1.observer class中属性dep定义为new Dep(),对数组和对象类型分别处理。对数组调用observeArray方法,对对象调用walk方法。
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)
if (Array.isArray(value)) {
const augment = hasProto
? protoAugment
: copyAugment
augment(value, arrayMethods, arrayKeys)
this.observeArray(value)
} else {
this.walk(value)
}
}
1.1walk方法:遍历所有实例属性,将之调用defineReactive方法。
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
1.2observeArray方法,对数组中的每个元素调用observe方法。
observeArray (items: Array) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
1.11 深入查看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
if (!getter && arguments.length === 2) {
val = obj[key]
}
const setter = property && property.set
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()
}
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = !shallow && observe(newVal)
dep.notify()
}
})
}
传入方法中的参数为 obj key ,分别表示对象本身和 实例属性名。
(1)在此方法中首先创建dep实例,之后获取对于实例属性的描述,通过Object.getOwnPropertyDescriptor,获取实例属性是否可以修改,若configurable为false,那么从方法中返回。
(2)获取属性的get方法,若属性为数据属性而不为访问器属性,则get值为undefined,并将结果赋值为getter,如果此属性为数据属性,并且传入了两个参数,则把对象对应的实例属性值赋值为val。
(3)获取属性的set方法值。若为数据属性则为undefined,并将其赋值为setter。
(4)若参数中未包含true false,则调用observe(val)方法,并将其赋值为childOb 这里涉及到observe方法。会创建observer实例,并返回。
(5)利用Object defineProperty定义obj的key实例属性描述为可遍历,可修改,设置其get,set方法。
get方法:a.若此属性已包含get方法,则在obj作用域下调用get方法。若无get方法,则获取其实例属性值val。将get方法返回值或者obj[key]赋值为value。b.如果Dep.target有值时,也就是目前存在唯一的watcher正在起作用时,通过dep实例调用depend方法,来为watcher和此dep实例添加依赖。此处的depend方法接下来细谈。
b.1 depend方法:若当前存在watcher,则调用Watcher 类型的addDep方法。
export default class Dep {
static target: ?Watcher;
depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}
b.11 深入addDep方法:若Watcher的新dep中未包含此dep实例,也就是此属性头一次被监听,那么将此dep实例添加进newDeps中,若果depId中未包含此dep实例,则调用dep实例的addSub方法
/**
* Add a dependency to this directive.
*/
Watcher.prototype.addDep = function addDep (dep) {
var id = dep.id;
if (!this.newDepIds.has(id)) {
this.newDepIds.add(id);
this.newDeps.push(dep);
if (!this.depIds.has(id)) {
dep.addSub(this);
}
}
};
b.111调用addSub方法。为dep实例的subs数组中添加watcher。
Dep.prototype.addSub = function addSub (sub) {
this.subs.push(sub);
};
if (childOb) { childOb.dep.depend() if (Array.isArray(value)) { dependArray(value) }
此处在研究observe后进行补充。
set方法
(1)首先判断此属性是否包含get方法,如果包含则获取get方法在obj对象的作用域下调用的返回值,如果没有get方法,则获取实例属性的值,并将其赋值为value。
(2)判断为实例属性新赋的值是否全等于value值,也就是实例属性已有的值和新赋值是否全等,或者value自身与自身就不全等或者新值与自身不全等的情况下,从set方法中返回。
(3)如果已有set方法,则在obj作用域下调用set方法,否则将val定义为新赋的值
(4)根据是否传入true false 调用observe(newval)方法,将返回值赋值为childOb
(5)调用dep实例的notify方法。对dep实例的subs数组中的watcher调用update方法,此处延伸下去。
notify () {
// stabilize the subscriber list first
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
a.watcher的update方法会调用run方法。
run () {
if (this.active) {
const value = this.get();
if (
value !== this.value ||
// Deep watchers and watchers on Object/Arrays should fire even
// when the value is the same, because the value may
// have mutated.
isObject(value) ||
this.deep
) {
// set new value
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);
}
}
}
get方法:先将目前正在执行工作的watcher添加到Dep的targetStack中,设置全局变量Dep.target,之后touch Watcher初始化时传入的参数expOrFn中
涉及到的每一项数据,然后触发该数据项的getter函数;设置dep.target是依赖收集过程中的重要一步,getter函数中就是通过判断Dep.target的
有无来判断是Watcher初始化时调用的还是普通数据读取,如果有则进行依赖收集。
get () {
pushTarget(this);//设置全局变量Dep.target,将watcher保存在这个全局变量中。
let value;
const vm = this.vm;
try {
value = this.getter.call(vm, vm);
} catch (e) {
if (this.user) {
handleError(e, vm, `getter for watcher "${this.expression}"`);
} else {
throw e
}
} finally {
// "touch" every property so they are all tracked as
// dependencies for deep watching
if (this.deep) {
traverse(value);
}
popTarget();
this.cleanupDeps();
}
return value
}
class Dep中
Dep.target = null;
const targetStack = [];
function pushTarget (_target) {
if (Dep.target) targetStack.push(Dep.target);
Dep.target = _target;
}
watcher对象的run方法中会调用cb方法,而该方法会进而调用patch方法,进行diff,比较新旧值差距,进而渲染页面