VUE双向绑定的原理

 

 

 

 

 

 

 

 

MVC模式是单向绑定,即model绑定到view,当我们用js代码更新model时,view就会自动更新。

MVVM模式

MVVM模式就是model-view-viewmodel,它实现了view的变动,自动反应在viewmodel,反之亦然。

我对于双向绑定的理解就是用户更新了view,model的数据也自动被更新了。这种情况就是双向绑定。

再说细点就是在单向绑定的基础上给可输入元素(input, textarea)元素添加(change(input)事件)change事件触发,view的状态就被更新了

VUE双向绑定的原理

发布-订阅模式

订阅者和发布者模式,通常用于消息队列中。一般有两种形式来实现消息队列,一是使用生产者和消费者来实现,二是订阅-发布者来实现,其中使用订阅者和发布者实现的消息队列的方式,就会用订阅者模式。

所谓的订阅者,就像我们在日常生活中,订阅报纸一样。我们订阅报纸的时候,通常都得需要再报社或者一些中介机构进行注册。当有新版的报纸发刊的时候,邮递员就需要向订阅该报纸的人,依次发放报纸,因此代码实现该模式,通常需要两个步骤

初始化发布者,订阅者。
订阅者需要注册到发布者,发布者发布消息,依次向订阅者发布消息。

 订阅者注册

VUE双向绑定的原理

发布者发布消息

VUE双向绑定的原理

 

举一个常见的例子,双十一商品打折,只有关注该商品的客户才能收到该商品打折信息。

商品信息类主要成员变量用来存放所有关注该商品的顾客(ArrayList),同时存在发布消息的方法,客户关注商品的方法(即向客户容器中添加客户)等。

 1 //商品类
 2 class Product{
 3     private ArrayList<Client> clientArrayList = new ArrayList<Client>();
 4 
 5     public void notify(String info){
 6         //对于所有关注该商品的顾客进行遍历,依次将信息发布出去
 7         for(Client client:clientArrayList){
 8             client.receiveInfo(info);
 9         }
10     }
11 
12     public void register(Client client){
13         clientArrayList.add(client);
14     }
15 }

客户类,即订阅者,用来接受商品(发布者)的打折信息。

 1 class ConcreteClient {
 2 
 3     private String name;
 4 
 5     public ConcreteClient(String name){
 6         this.name = name;
 7     }
 8     //用来接受发布者发布的消息
 9     public void receiveInfo(String info) {
10         System.out.println(this.name + ":收到信息(" + info + ")");
11     }
12 }

测试

 1 @Test
 2 public void test(){
 3         Product product = new Product();
 4 
 5         //a 用户关注商品
 6         product.register(new ConcreteClient("a"));
 7         //b 用户关注商品
 8         product.register(new ConcreteClient("b"));
 9         //c 用户关注商品
10         product.register(new ConcreteClient("c"));
11         //d 用户关注商品
12         product.register(new ConcreteClient("d"));
13 
14         product.notify("商品编号为D1403121717大减价");
15     }

运行结果

 1 a:收到信息(商品编号为D1403121717大减价);

2 b:收到信息(商品编号为D1403121717大减价);

3 c:收到信息(商品编号为D1403121717大减价);

4 d:收到信息(商品编号为D1403121717大减价); 

 

VUE双向绑定原理

vue数据双向绑定原理就是通过数据劫持结合发布者-订阅者模式的方式来实现的。

我们看一下vue初始化的数据是什么东西

 1 var vm = new Vue({
 2     data: {
 3         obj: {
 4             a: 1
 5         }
 6     },
 7     created: function () {
 8         console.log(this.obj);
 9     }
10 });

结果

VUE双向绑定的原理

我们看出a有两个相对应的get和set方法,为什么会多出这两个方法呢?因为vue是通过Object.defineProperty()来实现数据劫持的

Object.defineProperty()是用来做什么?它可以控制一个对象属性的一些特有操作,比如读写权,是否可以枚举

在平时很容易就可以打印一个对象的属性数据:

var Book = {
  name: 'vue权威指南'
};
console.log(Book.name);  // vue权威指南

如果想要执行console.log(book.name)的同时,直接给书名加个书名号,要怎么处理呢,或者说通过什么监听Book的属性值。这时候,Object.defineProperty()就派上的用场,代码如下

var Book = {}
var name = '';
Object.defineProperty(Book, 'name', {
  set: function (value) {
    name = value;
    console.log('你取了一个书名叫做' + value);
  },
  get: function () {
    return '《' + name + '》'
  }
})
 
Book.name = 'vue权威指南';  // 你取了一个书名叫做vue权威指南
console.log(Book.name);  // 《vue权威指南》

我们通过Object.defineProperty()方法设置了对象的name属性,对其get和set进行重写操作,顾名思义,get就是读取name属性这个值触发的函数,set就是在设置name属性这个值触发的函数,所以当执行Book.name='vue权威指南'这个语句时,控制台会打印出“你取了一个书名叫vue权威指南”,紧接着,当读取这个属性时,就会输出“《VUE权威指南》”,因为我们在get函数里面对该值进行加工了,如果这个时候我们执行下面的语句,控制台会输出什么

console.log(Book);

结果

VUE双向绑定的原理

乍一看,是不是跟我们在上面打印vue数据长得有点类似,说明vue确实是通过这种方法来进行数据劫持的

思路分析

实现MVVM主要包括两个方面,数据变化更新视图,视图变化更新数据

VUE双向绑定的原理

关键点在于data如何更新view,因为view更新data实际可以通过事件监听即可,比如input标签监听‘input'事件就可以实现了,所以我们着重分析下,当数据改变如何更新视图。

数据更新的重点是如何知道数据改变了,只要知道数据变了,那么接下来的事就好处理了,如何知道数据变了,其实上文就已经给出答案了,就是通过Object.defineProperty()对属性设置一个set函数,当数据改变了就会触发这个函数,所以我们只要将一些需要更新的方法放在这里就可以实现data更新view了

VUE双向绑定的原理

我们已经知道实现数据的双向绑定,首先要对数据进行劫持监听,所以我们需要设置一个监听器,用来监听所有属性,如果属性发生变化了,就需要告诉订阅者Watcher看是否需要更新,因为订阅者有很多个,我们需要一个消息订阅器Dep来专门收集这些订阅者,然后在监听器Observer和订阅者Watcher之间进行统一管理的。接着还需要一个指令解析器Compile,对每个节点元素进行扫描和解析,将相关指令对应初始化成一个订阅者Watcher,并替换模板数据或者绑定的相关函数,从而更新视图。因此接下来我们执行以下三个步骤,实现数据的双向绑定:

1,实现一个监听器Observer,用来劫持并监听所有属性,如果有变动的,就通知订阅者。

2,实现一个订阅者Watcher,可以收到属性的变化通知并执行相应的函数,从而更新视图。

3,实现一个解析器Compile,可以扫描和解析每个节点的相关指令,并根据初始化模板数据以及初始化相应的订阅器。

流程如下

VUE双向绑定的原理

1.实现一个Observer

 Observer是一个数据监听器,其实现核心方法就是前文所说的Object.defineProperty( )。如果要对所有属性都进行监听的话,那么可以通过递归方法遍历所有属性值,并对其进行Object.defineProperty( )处理。如下代码,实现了一个Observer。

 1 function defineReactive(data, key, val) {
 2     observe(val); // 递归遍历所有子属性
 3     Object.defineProperty(data, key, {
 4         enumerable: true,
 5         configurable: true,
 6         get: function() {
 7             return val;
 8         },
 9         set: function(newVal) {
10             val = newVal;
11             console.log('属性' + key + '已经被监听了,现在值为:“' + newVal.toString() + '”');
12         }
13     });
14 }
15  
16 function observe(data) {
17     if (!data || typeof data !== 'object') {
18         return;
19     }
20     Object.keys(data).forEach(function(key) {
21         defineReactive(data, key, data[key]);
22     });
23 };
24  
25 var library = {
26     book1: {
27         name: ''
28     },
29     book2: ''
30 };
31 observe(library);
32 library.book1.name = 'vue权威指南'; // 属性name已经被监听了,现在值为:“vue权威指南”
33 library.book2 = '没有此书籍';  // 属性book2已经被监听了,现在值为:“没有此书籍”
View Code

相关文章: