前言

本文章结合VUE核心技术-尚硅谷的56-62集做的归纳总结。

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()
            * 消息订阅与发布

4.双向数据绑定
    1). 双向数据绑定是建立在单向数据绑定(model==>View)的基础之上的
    2). 双向数据绑定的实现流程:
          * 在解析v-model指令时, 给当前元素添加input监听
          * 当input的value发生改变时, 将最新的值赋值给当前表达式所对应的data属性

数据绑定 model==>View

  • 一旦更新了data中的某个属性数据, 所有界面上直接使用或间接使用了此属性的节点都会更新(更新)

数据劫持

  1. 数据劫持是vue中用来实现数据绑定的一种技术
  2. 基本思想: 通过defineProperty()来监视data中所有属性(任意层次)数据的变化, 一旦变化就去更新界面

四个重要对象

数据劫持-数据绑定 - 打开控制台调试

Observer

  • 用来对data所有属性数据进行劫持的构造函数
  • 给data中所有属性重新定义属性描述(get/set)
  • 为data中的每个属性创建对应的dep对象

observe

   mvvm.js 内部会执行 observe 方法

Observer

  observe 会创建 Observer 观察者实例

MVVM原理
Observer使用

  观察者内部会执行 walk 函数遍历数据
  walk函数会遍历 data 中的数据,然后分别将data的键值对传入到 defineReactive 中

defineReactive

   defineReactive 会再次对子元素的键值对对象调用 observe 方法,实现递归遍历所有子层级

defineProperty
defineProperty

  遍历到的值会放到data中进行绑定
  通过 get 和 set 方法来对数据进行监听和更新

Dep(Depend)

Watcher

  在上一节中也提到上面的代码执行,唯独跳过了 Watcher 的说明
  这里就是用 Watcher 来监视数据变化,表达式exp对应的属性发生变化就执行更新方法

Watcher

  Watcher 内部会执行 this.get() 获取数据

get

  在 get 中又用到了 Dep 对象

Dep

  而 Dep 自身又 id 属性,每一次创建 id 都会 ++
  里面有 subs 表示订阅者,可以实现监听。
  subs 数组存放的是 Watcher 监听者

Dep

  回到前面可以看到 Dep 实在 defineProperty 之前创建的,属于初始化的时候更新的

Watcher

  而Watcher是在 bind 方法中生成的,属于更新过程创建

关系
关系

watcher 和 dep 对应关系

  每一组需要更新的元素会创建 watcher
  dep则对依赖关系

双向数据绑定

双向数据绑定 - 打开控制台调试

编译指令属性

  编译指令属性到会去 编译 v-model 属性

CompileUtil

   compileUtil 中有 model 函数
  model函数会执行bind方法

bind

  bind方法 会创建 Watcher 实现数据监听

CompileUtil

  接着获取 VM 数据
  接着会添加事件监听器,通过 input 监听可以实现输入的触发
  当 input 发生时,获取 input 的数据更新到变量当中

modelUpdater

  最后更新modelUpdater更新页面



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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
function Observer(data) {
// 保存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) {
// 创建与当前属性对应的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();
}
});
}
};

function observe(value, vm) {
// value必须是对象, 因为监视的是对象内部的属性
if (!value || typeof value !== 'object') {
return;
}
// 创建一个对应的观察都对象
return new Observer(value);
};


var uid = 0;

function Dep() {
// 标识属性
this.id = uid++;
// 相关的所有watcher的数组
this.subs = [];
}

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;



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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
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.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);
}
},
addDep: function (dep) {
if (!this.depIds.hasOwnProperty(dep.id)) {
// 建立dep到watcher
dep.addSub(this);
// 建立watcher到dep的关系
this.depIds[dep.id] = dep;
}
},
get: function () {
Dep.target = this;
// 获取当前表达式的值, 内部会导致属性的get()调用
var value = this.getVMVal();

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;
}
};