概念
- 数据绑定(model==>View):一旦更新了 data 中的某个属性数据,所有界面上直接使用或间接使用了此属性的节点都会更新
- 数据劫持:数据劫持是 vue 中用来实现数据绑定的一种技术
- 基本思想: 通过 defineProperty()来监视 data 中所有属性(任意层次)数据的变化, 一旦变化就去更新界面
数据劫持:Observer()
- 在 new MVVM 时执行 observe(data, this)
- observe()负责判断 vm._data 是不是一个对象,然后创建一个对应的观察者对象 new Observer(value)
- Observer(value)保存 vm._data 对象到当前观察者对象的实例的 data 属性中,然后调用 walk()
- walk()中遍历 data 中所有属性,对每个属性调用 convert()
- convert()对指定属性实现响应式数据绑定 this.defineReactive(this.data, key, val)
function MVVM(options) {
// 对data进行监视,重点是给data的每个属性定义get,set方法
observe(data, this);
}
1
2
3
4
2
3
4
function observe(value, vm) {
// value必须是对象, 因为监视的是对象内部的属性
if (!value || typeof value !== 'object') {
return;
}
// 创建一个对应的观察者对象
return new Observer(value);
}
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
function Observer(data) {
// 保存data对象
this.data = data;
// 走起
this.walk(data);
}
1
2
3
4
5
6
2
3
4
5
6
walk: function(data) {
var me = this;
// 遍历data中所有属性
Object.keys(data).forEach(function(key) {
// 针对指定属性进行处理
me.convert(key, data[key]);
});
},
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
convert: function(key, val) {
// 对指定属性实现响应式数据绑定
this.defineReactive(this.data, key, val);
},
1
2
3
4
2
3
4
负责记录订阅:dep()
- this.defineReactive()中首先创建一个 dep 对象 new Dep()
- dep()的实例中首先保存一个 id 标识当前属性,然后创建一个 subs 属性,是一个保存相关的所有 watcher 的数组
- 然后 this.defineReactive()间接递归调用实现对 data 中所有层次属性的劫持 observe(val),当 val 不是一个对象,在 observe()判断 data 中就会退出递归
- 然后给 vm._data 中的属性定义属性(添加 set/get)
- 其中 getter 中首先判断,,Dep.target 的初始值 null,所以不执行 dep.depend()
- getter 最后返回 vm._data 中对应属性
- setter 中首先把新值保存到 vm._data 对应属性中,然后如果设置的新值是 object 的话,进行监听 observe(newVal)
- 最后执行 dep.notify(),它负责遍历当前属性的 dep 实例中的 subs,调用每个元素的 update(),通知所有相关的 watcher(一个订阅者)去更新页面
defineReactive: function(data, key, val) {
// 创建与当前属性对应的dep对象
var dep = new Dep();
// 间接递归调用实现对data中所有层次属性的劫持
var childObj = observe(val);
// 给data重新定义属性(添加set/get)
Object.defineProperty(data, key, {
enumerable: true, // 可枚举
configurable: false, // 不能再define
get: function() {
// 建立dep与watcher的关系
if (Dep.target) {
dep.depend();
}
// 返回属性值
return val;
},
set: function(newVal) {
if (newVal === val) {
return;
}
val = newVal;
// 新的值是object的话,进行监听
childObj = observe(newVal);
// 调用每个元素的update()
dep.notify();
}
});
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
function Dep() {
// 标识属性
this.id = uid++;
// 相关的所有watcher的数组
this.subs = [];
}
1
2
3
4
5
6
2
3
4
5
6
Dep.prototype = {
notify: function() {
// 通知所有相关的watcher(一个订阅者)
this.subs.forEach(function(sub) {
sub.update();
});
}
};
Dep.target = null;
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
负责更新节点:Watcher()
- 由于刚开始 dep.subs 是空,所以 Watcher()第一次发生在模板编译中
- 模板中每一个用到模板语法并且用了 vm._data 的值的地方在刚开始解析时通过调用 Watcher(),向它传进对应的更新节点的函数
// 真正用于解析指令的方法
bind: function(node, vm, exp, dir) {
// 创建表达式对应的watcher对象
new Watcher(vm, exp, function(value, oldValue) {
/*更新界面*/
// 当对应的属性值发生了变化时, 自动调用, 更新对应的节点
updaterFn && updaterFn(node, value, oldValue);
});
},
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
- Watcher()首先在实例中创建五个属性,get()负责获取值
- vm:vm 对象,
- exp:对应指令的表达式,
- cb:当表达式所对应的数据发生改变的回调函数,
- value:表达式当前的值,
- depIds:表达式中各级属性所对应的 dep 对象的 id
function Watcher(vm, exp, cb) {
this.cb = cb; // callback
this.vm = vm;
this.exp = exp;
this.depIds = {}; // {0: d0, 1: d1, 2: d2}
this.value = this.get();
}
1
2
3
4
5
6
7
2
3
4
5
6
7
- get()中 Dep.target=this,this 是 Watcher()的当前实例
get: function () {
Dep.target = this;
// 获取当前表达式的值, 内部会导致属性的get()调用
var value = this.getVMVal();
Dep.target = null;
return value;
},
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
- 然后调用 gteVMVal(),遍历表达式中所有用到 vm._data 的值的地方,去 vm._data 中取值
getVMVal: function () {
var exp = this.exp.split('.');
var val = this.vm._data;
exp.forEach(function (k) {
val = val[k];
});
return val;
}
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
- 取值导致下面代码运行
if (Dep.target) {
dep.depend();
}
dep.notify();
1
2
3
4
2
3
4
- Dep.target 此时不是是 Watcher()的当前实例是 Watcher()的当前实例,dep.depend()负责往 dep 对象的 subs 属性添加 Dep.target
depend: function() {
Dep.target.addDep(this);
},
1
2
3
2
3
- 如果 depIds 中没有 dep. id,则把当前 watcher 添加到 dep 的 subs 中,还把 dep.id 添加到当前 watcher 的 depIds 中,建立 watcher 到 dep 的关系
addDep: function (dep) {
if (!this.depIds.hasOwnProperty(dep.id)) {
// 建立dep到watcher
dep.addSub(this);
// 建立watcher到dep的关系
this.depIds[dep.id] = dep;
}
},
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
addSub: function(sub) {
this.subs.push(sub);
},
1
2
3
2
3
- 然后调用 dep.notify()
notify: function() {
// 通知所有相关的watcher(一个订阅者)
this.subs.forEach(function(sub) {
sub.update();
});
}
1
2
3
4
5
6
2
3
4
5
6
- 里面调用 sub.update()去更新节点
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);
}
}
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
双向数据绑定
双向数据绑定是建立在单向数据绑定(model==>View)的基础之上的
双向数据绑定的实现流程:
- 在解析 v-model 指令时, 给当前元素添加 input 监听
- 当 input 的 value 发生改变时, 将最新的值赋值给当前表达式所对应的 data 属性
- 当把最新的值赋值给当前表达式所对应的 data 属性,触发 setter
// 解析: v-model
model: function(node, vm, exp) {
this.bind(node, vm, exp, 'model');
var me = this,
val = this._getVMVal(vm, exp);
node.addEventListener('input', function(e) {
var newValue = e.target.value;
if (val === newValue) {
return;
}
me._setVMVal(vm, exp, newValue);
val = newValue;
});
},
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
_setVMVal: function(vm, exp, value) {
var val = vm._data;
exp = exp.split('.');
exp.forEach(function(k, i) {
// 非最后一个key,更新val的值
if (i < exp.length - 1) {
val = val[k];
} else {
val[k] = value;
}
});
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
总结:四个重要对象
Observer
- 用来对 data 所有属性数据进行劫持的构造函数
- 给 data 中所有属性重新定义属性描述(get/set)
- 为 data 中的每个属性创建对应的 dep 对象
Dep(Depend)
- data 中的每个属性(所有层次)都对应一个 dep 对象
- 创建的时机:
- 在初始化 define data 中各个属性时创建对应的 dep 对象
- 在 data 中的某个属性值被设置为新的对象时
- 对象的结构
{
id, // 每个dep都有一个唯一的id
subs //包含n个对应watcher的数组(subscribes的简写)
}
1
2
3
4
2
3
4
- subs 属性说明
- 当一个 watcher 被创建时, 内部会将当前 watcher 对象添加到对应的 dep 对象的 subs 中
- 当此 data 属性的值发生改变时, 所有 subs 中的 watcher 都会收到更新的通知, 从而最终更新对应的界面
Compile
- 用来解析模板页面的对象的构造函数(一个实例)
- 利用 compile 对象解析模板页面
- 每解析一个表达式(非事件指令)都会创建一个对应的 watcher 对象, 并建立 watcher 与 dep 的关系
- complie 与 watcher 关系: 一对多的关系
Watcher
- 模板中每个非事件指令或表达式都对应一个 watcher 对象
- 监视当前表达式数据的变化
- 创建的时机: 在初始化编译模板时
- 对象的组成
{
vm, //vm对象
exp, //对应指令的表达式
cb,//当表达式所对应的数据发生改变的回调函数
value, //表达式当前的值
depIds//表达式中各级属性所对应的dep对象的集合对象
//属性名为dep的id, 属性值为dep
}
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
dep 与 watcher 的关系
- 一个 data 中的属性对应对应一个 dep, 一个 dep 中可能包含多个 watcher(模板中有几个表达式使用到了属性)
- 模板中一个非事件表达式对应一个 watcher, 一个 watcher 中可能包含多个 dep(表达式中包含了几个 data 属性)