总所周知,vue的一个大特色就是实现了双向数据响应,数据改变,视图中引用该数据的部分也会自动更新

一.双向数据绑定基本思路

“数据改变,视图中引用该数据的部分也会自动更新“,从这句话,我们可以分析出以下几点:

  • 数据要绑定监听事件
  • 视图要绑定更新事件
  • 监听事件会触发更新事件

vue的双向数据响应也是基于以上思路开发的

二.vue基于数据劫持+发布订阅模式实现双向绑定

2.1数据劫持

利用Object.defineProperty深度遍历每个响应数据,给每个响应数据和其子属性添加get和set方法.

Object.defineProperty的get方法在数据被调用时执行.所以get()用于收集依赖,所谓的依赖就是使用当前响应数据的dom

Object.defineProperty的set方法在数据修改时被执行.所以set()方法用于通知监听事件,我的数据更新了defaultReative(data,key,value)

  1. defaultReative(data,key,value){
  2. //先去做递归
  3. this.observer(value);
  4. //给每个响应数据的get方法上都添加一个dep实例
  5. var dep = new Dep();
  6. //数据劫持
  7. Object.defineProperty(data,key,{
  8. get(){
  9. /*在渲染html的时候会调用响应数据,也就执行了get方法;watcher实例是订阅者,渲染时会给当前dom初始化一个watcher实例,也就是给当前的dom添加了订阅者,
  10.          * 订阅者内部会有更新dom的方法,数据变化的时候,发布者通知订阅者,订阅者内部去调用更新dom的方法.
  11.  
  12.          * watcher内部有dep.target赋值操作,赋值为watcher本身,
  13.           这样才能确保有我们收集的依赖就是调用了当前响应数据的订阅者.
  14.          */
  15. Dep.target && dep.addDep(Dep.target);
  16. return value;
  17. },
  18. set(newVal){
  19. if(newVal == value)return;
  20. //赋值操作 当外部设置data身上的某个属性的时候 将新值赋值给旧值
  21. value = newVal;
  22.  
  23. dep.notify();
  24.  
  25. }
  26. })
  27. }

dep是发布订阅模式的一环,会在下面讲解

2.2发布订阅模式

发布—订阅模式可以广泛应用于异步编程中,这是一种替代传递回调函数的方案.这种模式让一个对象不在显示地调用另一个对象的接口,实现了多个对象松耦合联系在一起.

只需要订阅感兴趣的事件发生点,当事件执行时就会通知给所有订阅者.

一个简单的发布-订阅模式的实现主要是以下三点内容:

  • 指定好发布者;
  • 发布者有一个缓存列表,里面存放了回调函数,以便发布后通知订阅者;
  • 发布消息的时候遍历缓存列表,依次触发订阅者的回调

在vue中dep就类似发布者,下面是dep的实现

  1. class Dep{
  2. constructor(){
  3. this.deps = [];
  4. }
  5. addDep(dep){
  6. this.deps.push(dep);
  7. }
  8. notify(){
  9. this.deps.forEach((item)=>{
  10. item.update();
  11. })
  12. }
  13. }

我们给每一个响应数据get()内部添加一个deps列表,列表内部就是调用该响应数据的订阅者,

我们给每一个响应数据set()内部添加一个dep.notify(),这个方法去通知订阅者调用内部的更新函数,更新视图

  1. defaultReative(data,key,value){
  2. //先去做递归
  3. this.observer(value);
  4. //给每个响应数据的get方法上都添加一个dep实例
  5. var dep = new Dep();
  6. //数据劫持
  7. Object.defineProperty(data,key,{
  8. get(){
  9. /*在渲染html的时候会调用响应数据,也就执行了get方法;watcher实例是订阅者,渲染时会给当前dom初始化一个watcher实例,也就是给当前的dom添加了订阅者,
  10.          * 订阅者内部会有更新dom的方法,数据变化的时候,发布者通知订阅者,订阅者内部去调用更新dom的方法.
  11.  
  12.          * watcher内部有dep.target赋值操作,赋值为watcher本身,
  13.           这样才能确保有我们收集的依赖就是调用了当前响应数据的订阅者
  14.          */
  15. Dep.target && dep.addDep(Dep.target);
  16. return value;
  17. },
  18. set(newVal){
  19. if(newVal == value)return;
  20. //赋值操作 当外部设置data身上的某个属性的时候 将新值赋值给旧值
  21. value = newVal;
  22.  
  23. dep.notify();
  24.  
  25. }
  26. })
  27. }
watcher

watcher类似订阅者,他会和dom去做关联.watcher内部有一个update方法,用于更新dom节点.

watcher也会跟Dep做关联,在constructor中,调用dep.target=this,目的是让Dep指向当前的watcher.响应数据对应的订阅者不同,每个响应数据收集到对应的依赖就需要指定this,因为在渲染dom的时候,我们会给每个dom节点都初始化一个watcher实例,this就是这个watcher实例

下面是一个watcher方法的实现

  1. //监听数据的变化
  2. class Watcher{
  3. constructor(vm,exp,callback){
  4. this.$vm = vm;
  5. this.$exp = exp;
  6. this.callback = callback;
  7. //给dep添加了一个静态属性target,值是watcher自己
  8. Dep.target = this;
  9. //触发属性的getter方法
  10. this.$vm[this.$exp];
  11. Dep.target = null;
  12. }
  13. //在index.js中一旦数据有变动会触发dep.notify方法,
  14. //nodity会遍历dep中收集的watcher,调用watcher中的update方法,在update方法中执行,
  15. //这里(compile)传递给watcher的方法(updateFn)更新视图
  16. update(){
  17. //视图更新
  18. this.callback.call(this.$vm,this.$vm[this.$exp])
  19. }
  20. }

在渲染dom的时候,将dom方法的更新函数传递给watcher做回调

  1. update(el,vm,exp,type){
  2. //为了其他的一些指令需要的一些公共的逻辑在这个方法里面编写
  3. //比如这里是text指令,updateFn=textupdate
  4. var updateFn = this[type+'update'];
  5. //如果textupdate方法存在,就去执行textupdate方法
  6. updateFn && updateFn(el,vm[exp]);
  7. //给每个节点都添加上监听
  8. //添加监听数据的订阅者(给watcher传递数据和回调),在index.js中一旦数据有变动会触发dep.notify方法,
  9. //nodity会遍历dep中收集的watcher,调用watcher中的update方法,在update方法中执行,
  10. //这里(compile)传递给watcher的方法(updateFn)更新视图
  11. new Watcher(vm,exp,(value)=>{
  12. updateFn && updateFn(el,value);
  13. });
  14. }

总结.

vue在编译html模版的时候,会给每个dom节点初始化watcher订阅者,把该节点的更新函数传递给watcher类中

vue在初始化时会深度遍历每一个响应属性,给每个响应属性添加上get和set方法,当html渲染时用到了响应数据就去调用get方法得到数据,同时get内部通过依赖收集知道了调用的订阅者都有那些;

在修改响应数据时会调用set方法,在方法内部通过dep.notify通知订阅者,订阅者去调用update更新dom节点

vue-双向响应数据底层原理分析的更多相关文章

  1. 梳理vue双向绑定的实现原理

    Vue 采用数据劫持结合发布者-订阅者模式的方式来实现数据的响应式,通过Object.defineProperty来劫持数据的setter,getter,在数据变动时发布消息给订阅者,订阅者收到消息后 ...

  2. Vue双向绑定的实现原理系列(四):补充指令解析器compile

    补充指令解析器compile github源码 补充下HTML节点类型的知识: 元素节点 Node.ELEMENT_NODE(1) 属性节点 Node.ATTRIBUTE_NODE(2) 文本节点 N ...

  3. Vue双向绑定的实现原理系列(一):Object.defineproperty

    了解Object.defineProperty() github源码 Object.defineProperty()方法直接在一个对象上定义一个新属性,或者修改一个已经存在的属性, 并返回这个对象. ...

  4. Vue双向绑定的实现原理系列(三):监听器Observer和订阅者Watcher

    监听器Observer和订阅者Watcher 实现简单版Vue的过程,主要实现{{}}.v-model和事件指令的功能 主要分为三个部分 github源码 1.数据监听器Observer,能够对数据对 ...

  5. HashMap底层原理分析(put、get方法)

    1.HashMap底层原理分析(put.get方法) HashMap底层是通过数组加链表的结构来实现的.HashMap通过计算key的hashCode来计算hash值,只要hashCode一样,那ha ...

  6. vue双向绑定(数据劫持+发布者-订阅者模式)

    参考文献:https://www.cnblogs.com/libin-1/p/6893712.html 实现mvvm主要包含两个方面,数据变化更新视图,视图变化更新数据. 关键点在于data如何更新v ...

  7. Vue双向绑定的实现原理及简单实现

    vue数据双向绑定原理   vue数据双向绑定是通过(数据劫持)+(发布者-订阅者模式)的方式来实现的,而所谓的数据劫持就是通过Object.defineProperty() 来实现的,所谓的Obje ...

  8. Android应用程序组件Content Provider在应用程序之间共享数据的原理分析

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6967204 在Android系统中,不同的应用 ...

  9. JMM和Volatile底层原理分析

    JMM和volatile分析 1.JMM:Java Memory Model,java线程内存模型 JMM:它是一个抽象的概念,描述的是线程和内存间的通信,java线程内存模型和CPU缓存模型类似,它 ...

随机推荐

  1. 委托的 `DynamicInvoke` 小优化

    委托的 DynamicInvoke 小优化 Intro 委托方法里有一个 DynamicInvoke 的方法,可以在不清楚委托实际类型的情况下执行委托方法,但是用 DynamicInvoke 去执行的 ...

  2. Cucumber(3)——命令以及日志

    目录 回顾 基本执行命令 关于日志的生成 回顾 在上一节中,我介绍了cucumber一些基本的语法内容,如果你还没有进行相关的了解或者环境的配置,你可以点击这里来进行了解一下 在本节中,我会对cucu ...

  3. Redisson 实现分布式锁的原理分析

    写在前面 在了解分布式锁具体实现方案之前,我们应该先思考一下使用分布式锁必须要考虑的一些问题.​ 互斥性:在任意时刻,只能有一个进程持有锁. 防死锁:即使有一个进程在持有锁的期间崩溃而未能主动释放锁, ...

  4. windows搭建Selenium

    安装 pip install -U selenium 安装浏览器驱动 用不同的浏览器需要安装不同的驱动,驱动放置的路径添加到path中. Firefox geodriver Chrome 下载驱动Ch ...

  5. selenium 时间等待的方法

    一.强制等待固定秒数 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } 执行到sl ...

  6. [php] 猴子偷桃

    <?php /* 10:五只猴子采得一堆桃子,猴子彼此约定隔天早起后再分食. 不过,就在半夜里,一只猴子偷偷起来,把桃子均分成五堆后, 发现还多一个,它吃掉这桃子,并拿走了其中一堆.第二只猴子醒 ...

  7. Serlvet容器与Web应用

    对启动顺序的错误认识 之前一直有个观点,应用运行在Servlet容器中,因为从Servlet容器与Web应用的使用方式来看,确实很有这种感觉. 我们每次都是启动Servlet容器,然后再启动我们的应用 ...

  8. pytorch 中LSTM模型获取最后一层的输出结果,单向或双向

    单向LSTM import torch.nn as nn import torch seq_len = 20 batch_size = 64 embedding_dim = 100 num_embed ...

  9. PHP Callable强制指定回调类型的方法

    如果一个方法需要接受一个回调方法作为参数,我们可以这样写 <?php function dosth($callback){ call_user_func($callback); } functi ...

  10. thinkphp5 input坑

    取值方式改了而已?a1=1&a2=2这种可以用input(get.) a1/1/a2/2 用input('a1')和input('a2') post方法当然是input('post.') 我觉 ...