MVVM(三)数据劫持-->数据绑定
1.数据绑定(model==>View):
(1). 一旦更新了data中的某个属性数据, 所有界面上直接使用或间接使用了此属性的节点都会更新(更新)
2.数据劫持
(1). 数据劫持是vue中用来实现数据绑定的一种技术
(2). 基本思想: 通过defineProperty()来监视data中所有属性(任意层次)数据的变化, 一旦变化就去更新界面
3.四个重要对象
(1). Observer
* 用来对data所有属性数据进行劫持的构造函数
* 给data中所有属性重新定义属性描述(get/set)
* 为data中的每个属性创建对应的dep对象
(2). Dep(Depend)
* data中的每个属性(所有层次)都对应一个dep对象
* 创建的时机:
* 在初始化define data中各个属性时创建对应的dep对象
* 在data中的某个属性值被设置为新的对象时
* 对象的结构
{
id, // 每个dep都有一个唯一的id
subs //包含n个对应watcher的数组(subscribes的简写)
}
* subs属性说明
* 当一个watcher被创建时, 内部会将当前watcher对象添加到对应的dep对象的subs中
* 当此data属性的值发生改变时, 所有subs中的watcher都会收到更新的通知, 从而最终更新对应的界面
(3). Compile
* 用来解析模板页面的对象的构造函数(一个实例)
* 利用compile对象解析模板页面
* 每解析一个表达式(非事件指令)都会创建一个对应的watcher对象, 并建立watcher与dep的关系
* complie与watcher关系: 一对多的关系
(4). Watcher
* 模板中每个非事件指令或表达式都对应一个watcher对象
* 监视当前表达式数据的变化
* 创建的时机: 在初始化编译模板时
* 对象的组成
{
vm, //vm对象
exp, //对应指令的表达式
cb, //当表达式所对应的数据发生改变的回调函数
value, //表达式当前的值
depIds //表达式中各级属性所对应的dep对象的集合对象
//属性名为dep的id, 属性值为dep
}
(5). 总结: dep与watcher的关系: 多对多
* 一个data中的属性对应对应一个dep, 一个dep中可能包含多个watcher(模板中有几个表达式使用到了属性)
* 模板中一个非事件表达式对应一个watcher, 一个watcher中可能包含多个dep(表达式中包含了几个data属性)
* 数据绑定使用到2个核心技术
* defineProperty()
* 消息订阅与发布
//创建监视对象 function observe(value, vm) { // value必须是对象, 因为监视的是对象内部的属性 if (!value || typeof value !== 'object') { return; } // 创建一个对应的观察都对象 return new Observer(value); }; // Observer构造函数 function Observer(data) { // 保存data对象 this.data = data; // 走起 this.walk(data); } 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) { // 创建与当前属性对应的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); // 通过dep dep.notify(); } }); } }; defineReactive 建立关系 dep对象里面存放着id跟sub数组; data: { name: 'sadamu', // dep0 wife: { // dep1 name: 'binbin', // dep2 age: 18 // dep3 } }
Dep对象里面存放
Dep{
Id =0;
Subs = [],表示的是name属性
}
Dep{
Id =1;
Subs = [],表示的是wife对象。里面属性递归 //subs存放的是监视对象
}
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();
}
// watcher是一个监视对象 对属性进行监视;
1. 什么时候一个dep中关联多个watcher?
多个指令或表达式用到了当前同一个属性 {{name}} {{name}}
2. 什么时候一个watcher中关联多个dep?
多层表达式的watcher对应多个dep {{a.b.c}}
<div id="test">
<p>{{name}}</p>
<p v-text="name"></p>
<p v-text="wife.name"></p>
<button v-on:click="update">更新</button>
</div>
列出:watcher与dep的对应关系
<p>{{name}}</p> <p v-text="name"></p> 都对name属性操作
Name = dep{id =0;subs[2个监视对象]},因为要对视图更改多个地方
<p v-text="wife.name"></p> dep{id =2;subs[1个监视对象]} 只需要更改一个地方