3 数据劫持
数据劫持是vue中用来实现数据绑定的一种技术,通过defineProperty()来监视data中所有属性(任意层次)数据的变化, 一旦变化就去更新界面。
function Watcher(vm, exp, cb) {
this.cb = cb; // <1>保存回调函数,主要用与更新节点
this.vm = vm;
this.exp = exp;
this.depIds = {}; // 声明dep容器 用来存放dep
this.value = this.get();
}
Watcher.prototype = {
update: function() {
this.run();
},
run: function() {
var value = this.get(); //
var oldVal = this.value;
if (value !== oldVal) {
this.value = value;
this.cb.call(this.vm, value, oldVal); // <2>调用回调函数更新对应的界面
}
},
addDep: function(dep) {
if (!this.depIds.hasOwnProperty(dep.id)) {
dep.addSub(this); // 将watcher加入对应的dep 在observe里调用
this.depIds[dep.id] = dep; // 将dep加入watcher的depIds里
}
},
get: function() {
Dep.target = this;
var value = this.getVMVal(); //获取当前表达式的值, 内部会导致属性的get()调用
Dep.target = null;
return value;
},
getVMVal: function() {
var exp = this.exp.split('.');
var val = this.vm._data;
exp.forEach(function(k) {
val = val[k];
});
return val;
}
};
在第一篇文章中,new myVue的时候我们调用了observe方法,我们看一下observe都干了什么。
function Observer(data) {
this.data = data;
this.walk(data);
}
Observer.prototype = {
walk: function(data) {
var me = this;
// 遍历data中所有属性,针对指定属性进行处理
Object.keys(data).forEach(function(key) {
me.convert(key, data[key]);
});
},
convert: function(key, val) {
// 对指定属性实现响应式数据绑定
this.defineReactive(this.data, key, val);
},
defineReactive: function(data, key, val) {
// <3>创建与当前属性对应的dep对象 用于存放wather
var dep = new Dep();
// 间接递归调用实现对data中所有层次属性的劫持
var childObj = observe(val);
// <4>给data中的每一个属性重新定义:(添加set/get)
Object.defineProperty(data, key, {
enumerable: true, // 可枚举
configurable: false, // 不能再define
get: function() {
if (Dep.target) {
dep.depend(); //<5>建立dep与watcher的关系,调用Watcher里的depend
}
return val;
},
set: function(newVal) {
if (newVal === val) {
return;
}
val = newVal;
// 新的值是object的话,进行监听
childObj = observe(newVal);
dep.notify(); //notify函数遍历dep里的watcher 并触发对应的update函数
}
});
}
};
function observe(value, vm) {
// <1>判断传进来的data是不是对象, 因为监视的是对象内部的属性
if (!value || typeof value !== 'object') {
return;
}
// <2>创建一个对应的观察都对象
return new Observer(value);
};
var uid = 0;
function Dep() {
this.id = uid++; //生成唯一depId
this.subs = []; // 相关的所有watcher的数组
}
Dep.prototype = {
addSub: function(sub) {
this.subs.push(sub);
},
depend: function() {
Dep.target.addDep(this);
},
removeSub: function(sub) {
var index = this.subs.indexOf(sub);
if (index != -1) {
this.subs.splice(index, 1);
}
},
notify: function() {
// 通知所有相关的watcher(一个订阅者)
this.subs.forEach(function(sub) {
sub.update();
});
}
};
Dep.target = null;
总结:
-
Observer 用来对data所有属性数据进行劫持的构造函数,给data中所有属性重新定义属性描述(get/set),为data中的每个属性创建对应的dep对象
-
Dep(Depend) data中的每个属性(所有层次)都对应一个dep对象,在初始化define data中各个属性时创建对应的dep对象
对象的结构
{ id, // 每个dep都有一个唯一的id subs //包含n个对应watcher的数组(subscribes的简写) }
-
Compile 用来解析模板页面的对象的构造函数,每解析一个表达式(非事件指令)都会创建一个对应的watcher对象, 并建立watcher与dep的关系
-
Watcher 模板中每个非事件指令或表达式都对应一个watcher对象,监视当前表达式数据的变化
对象的组成
{
vm, //vm对象
exp, //对应指令的表达式
cb, //当表达式所对应的数据发生改变的回调函数
value, //表达式当前的值
depIds //表达式中各级属性所对应的dep对象的集合对象,属性名为dep的id, 属性值为dep
//例如:a.b.c 就是 {0: a的depid, 1: b的depid, 2: c的depid}
}