剖析Vue数据劫持的实现原理

原文:https://github.com/DMQ/mvvm (包含原文源码)

由于源码的注释比较少,我自己加了注释的地址:https://github.com/zengqingxiao/MVVM/tree/master

双向数据绑定方法

  1. 发布者-订阅者模式 - 微信小程序, React
  2. 脏值匹配 – Angular
  3. 数据劫持 – vue

发布者-订阅者模式

一般通过方法实现数据和视图的绑定监听,更新数据通常做法为vm.set(‘property’, value)

脏值匹配

angular.js 是通过脏值检测的方式比对数据是否有变更,来决定是否更新视图,最简单的方式就是通过 setInterval() 定时轮询检测数据变动,当然Google不会这么low,(用一些监听)angular只有在指定的事件触发时进入脏值检测,大致如下

  • DOM事件,譬如用户输入文本,点击按钮等。( ng-click )
  • XHR响应事件,发生请求 ( $http // Angular中的api)
  • 浏览器Location变更事件 ( $location // Angular中的api)
  • Timer事件( $timeout , $interval // Angular中的api)
  • 执行 $digest() 或 $apply() // Angular中的方法

数据劫持

vue中数据劫持解析图

Observer:数据监听器,能够对data中全部属性进行监听,如果数据有变动可以拿到最新值,并且通知订阅者(Watcher)

通过 Object.defineProperty 设置为 存储描述符

Compile: 指令解析器,对元素上的指令和{{}}中的文本进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数

Watcher:订阅器,作为连接Observer和Compile的桥梁

  • 在初始化的时候通过Compile(解析指令)来绑定和订阅器Dep的关系
  • 在数据变化的时候可以更新订阅者,或者接收到Dep发送变化,从而更新视图

大概流程

  1. 执行Observer函数(数据监听器)对data中的全部属性进行监听,并且对其用Object.defineProperty中的存储描述符来添加get()和set()方法,并且构建了相关的dep对象,使其如果data中的数据发生修改那么就会通知Dep(但是在开始阶段Dep还是没有创建的,在后续创建)
  2. 执行Compile(指令解析器):来初始化页面对node中的文本节点和元素节点进行解析,通过Updater方法来对node进行渲染,
  3. 在初始化的过程中我们对那些文本节点和元素(指令)节点进行new Watcher构造函数进行,构造相关Watcher对象(包括了当前表达式exp对应的值,和vm对象,和回调函数),这里的回调函数是Vue对指令或{{}}的语法糖的函数执行,同时对当前文本节点或者指令节点的表达式进行了执行一遍
  4.   /**
       * {{}} 文本实例  -->这个只是大概的展示 并不是源码内容
       * @param {*} node // 当前文本节点
       * @param {*} value // 需要赋值的值
       */
      textUpdater: function (node, value) {
        node.textContent = typeof value == 'undefined' ? '' : value; // 对节点的textContent赋值
      },
    
  5. 而在构架watcher的时候我们会对表达式中的值读取一遍,为什么呢?(例如下面的例子{{a.b.c}}) 其实就是为了触发我们前面在observer中对每一个data设置的访问器属性get,作用就是把当前watcher,通过执行原型链上的方法将其watcher放入到相关联的Dap的subs数组中(也就是说subs数组中放的是watcher),那么触发set访问器函数的时候就会找到相关Dep中的subs数组
  6. 那么我们每一个data就有一个Dep,而这个Dep中又包含了subs数组,subs数组中包含了Watcher,当某一个值发生改变遍历subs,找到和data相关的watcher,而这个watcher在触发他相关的回调函数,并且由于watcher中包含了vm对象因此可以通过就会去找到相关的node去和改变表达式的值

简单概述Dep

data每一个值都有一个Dep,Dep中有subs数组,其包含了Watcher,所以当某一个data数据发生了改变就会遍历Dep中的subs数组中的Watcher,通过Wathcer中包含了(包括了当前表达式exp对应的值,和vm对象,和回调函数),所以可以通过vm对象找到相关Dom去触发回调函数来改变相关值

注意

例如一个表达式{{A.B.C}}我们表达式只是获取c的值但是这个c这个值相关的watcher分别会放入到a,a.b,a.b.c的Dpe中的subs数组中,主要当a修改那么a.b,a.b.c相关的watcher就会被触发,那么相关的回调函数触发修改node,那么vue是如何做到的呢,其实就是我们遍历如果要获取a.b.c要首先获取到a的值,在获取到a.b的值,那么在获取对应值的时候触发了get()函数,就会把当前表达式{{a.b.c}}所关联的Watcher放入到对应的Dep的subs数组中

 getVMVal: function () {
    var exp = this.exp.split('.');
    var val = this.vm._data; // 初始为data 如果exp为a.b.c 那么过程为 data.a  data.a.b  data.a.b.c
    exp.forEach(function (k) {
      val = val[k];
    });
    return val;
  }

 

 

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值