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

要实现mvvm的双向绑定,就必须要实现以下几点:

  1. Compile—指令解析系统,对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数

  2. Observer—数据监听系统,能够对数据对象的所有属性进行监听,如有变动可拿到最新值并通知订阅者

  3. Dep+Watcher—发布订阅模型,作为连接Observer和Compile的桥梁,能够订阅并收到每个属性变动的通知,执行指令绑定的相应回调函数,从而更新视图。

    Dep是发布订阅者模型中的发布者:get数据的时候,收集订阅者,触发Watcher的依赖收集;set数据时发布更新,通知Watcher 。一个Dep实例对应一个对象属性或一个被观察的对象,用来收集订阅者和在数据改变时,发布更新。

    Watcher是发布订阅者模型中的订阅者:订阅的数据改变时执行相应的回调函数(更新视图或表达式的值)。一个Watcher可以更新视图,如html模板中用到的{{test}},也可以执行一个$watch监督的表达式的回调函数(Vue实例中的watch项底层是调用的$watch实现的),还可以更新一个计算属性(即Vue实例中的computed项)。

mvvm入口函数,整合以上三者,具体如图所示:

compire可以参看《双向绑定的实现原理》,这里不做过多解读。

Observer,Dep和Watcher类的实现及原理,推荐阅读《Vue源码解读一:Vue数据响应式原理》,一般开发者需要关注:

收集依赖指的是谁收集依赖,依赖又是指的什么?

Watcher,作用是分割表达式,收集依赖并且在值变化的时候调用回调函数。

我们上面说过一个Dep对应着一个数据(这个数据可能是:对象的属性、一个对象、一个数组);一个Watcher对应可以是一个模板也可以是一个$watch对应的表达式、函数等,无论那种情况,他们都依赖于data里面的数据,所以这里说的依赖其实就是模板或表达式所依赖的数据,对应着相关数据的Dep。

Watcher的四个使用场景

  • 第一种:观察模板中的数据

  • 第二种:观察创建Vue实例时watch选项里的数据

  • 第三种:观察创建Vue实例时computed选项里的数据所依赖的数据

  • 第四种:调用$watch api观察的数据或表达式

Watcher只有在这四种场景中,Watcher才会收集依赖,更新模板或表达式,否则,数据改变后,无法通知依赖这个数据的模板或表达式:

所以在解决数据改变,模板或表达式没有改变的问题时,可以这么做:

首先仔细看一看数据是否在上述四种应用场景中,以便确认数据已经收集依赖;其次查看改变数据的方式,确定这种方式会使数据的改变被拦截(关于这一点,上面Obsever相关内容中说的比较多)。

对于Observer需要注意的是:

getter/setter方法拦截数据的不足

    1. 当对象增删的时候,是监控不到的。比如:data={a:"a"},这个时候如果我们设置data.test="test",这个时候是监控不到的。因为在observe data的时候,会遍历已有的每个属性(比如a),添加getter/setter,而后面设置的test属性并没有机会设置getter/setter,所以检测不到变化。同样的,删除对象属性的时候,getter/setter会跟着属性一起被删除掉,拦截不到变化。

      vm.$set/Vue.set和vm.$delete/Vue.delete这样的api来解决这个问题

    2. getter/setter是针对对象的对于数组的修改(push(),pop(),shift(),unshift(),splice(),sort(),reverse())等方法,arr发生了改变,此时是需要更新视图的,但是arr的getter/setter拦截不到变化(只有在赋值的时候才会调用setter,比如:arr=[6,7,8])。

      对于这种情况,vue通过改写Array的默认方法,在调用这些方法的时候发布更新消息。一般无需关注,但是对于如下两种情况:

      1. 当你利用索引直接设置一个项时,例如:vm.items[indexOfItem] = newValue

      2. 当你修改数组的长度时,例如:vm.items.length = newLength

需要vm.$set/Vue.set和vm.items.splice(newLength)解决,具体参看官方说明

  1. 每次给数据设置值得时候,都会调用setter函数,这个时候就会发布属性更新消息,即使数据的值没有变。从性能方便考虑我们肯定希望值没有变化的时候,不更新模板。(像Angular这样把批量操作延时到一次更新,一次做完所有数据变更,然后整体应用到界面上)

整体感知virtual DOM

virtual DOM分为三个步骤:

1.createElement(): 用 JavaScript对象(虚拟树) 描述 真实DOM对象(真实树)

2.diff(oldNode, newNode) : 对比新旧两个虚拟树的区别,收集差异

3.patch() : 将差异应用到真实DOM树

有的时候 第二步 可能与 第三步 合并成一步(Vue 中的patch就是这样)

Vue的实现原理总结

  1. 首先,在实例化的过程中,把一个普通 JavaScript 对象传给 Vue 实例的 data选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter。

  2. Dep 是一个依赖收集器。data 下的每一个属性都有一个唯一的 Dep 对象,在 get 中收集仅针对该属性的依赖,然后在 set 方法中触发所有收集的依赖。

  3. 在Watcher中对表达式求值,从而触发数据的get。在求值之前将当前Watch实例设置到全局,使用pushTarget(this)方法。

  4. 在get()中收集依赖,this.subs.push(sub),set的时候触发回调Dep.notify()。

  5. Compile中首先将template或el编译成render函数,render函数返回一个虚拟DOM对象(将模板转为 render 函数的时候,实际是先生成的抽象语法树(AST),再将抽象语法树转成的 render 函数)

  6. 当 vm._render 执行的时候,所依赖的变量就会被求值,并被收集为依赖。按照Vue中 watcher.js 的逻辑,当依赖的变量有变化时不仅仅回调函数被执行,实际上还要重新求值,即还要执行一遍

  7. 如果还没有 prevVnode 说明是首次渲染,直接创建真实DOM。如果已经有了 prevVnode 说明不是首次渲染,那么就采用 patch 算法进行必要的DOM操作。这就是Vue更新DOM的逻辑。

最后,安利下:《Vue.js 技术揭秘

参考文章

梳理Vue2.0双向绑定的实现原理

文自《梳理vue双向绑定的实现原理 - vue入坑总结 - 周陆军的个人网站》,如有不妥之前,请源站留言告知。

梳理vue双向绑定的实现原理的更多相关文章

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

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

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

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

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

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

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

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

  5. vue双向绑定原理分析

    当我们学习angular或者vue的时候,其双向绑定为我们开发带来了诸多便捷,今天我们就来分析一下vue双向绑定的原理. 简易vue源码地址:https://github.com/jiangzhenf ...

  6. [Vue源码]一起来学Vue双向绑定原理-数据劫持和发布订阅

    有一段时间没有更新技术博文了,因为这段时间埋下头来看Vue源码了.本文我们一起通过学习双向绑定原理来分析Vue源码.预计接下来会围绕Vue源码来整理一些文章,如下. 一起来学Vue双向绑定原理-数据劫 ...

  7. Vue.js双向绑定的实现原理和模板引擎实现原理(##########################################)

    Vue.js双向绑定的实现原理 解析 神奇的 Object.defineProperty 这个方法了不起啊..vue.js和avalon.js 都是通过它实现双向绑定的..而且Object.obser ...

  8. vue双向绑定的原理及实现双向绑定MVVM源码分析

    vue双向绑定的原理及实现双向绑定MVVM源码分析 双向数据绑定的原理是:可以将对象的属性绑定到UI,具体的说,我们有一个对象,该对象有一个name属性,当我们给这个对象name属性赋新值的时候,新值 ...

  9. Vue双向绑定原理,教你一步一步实现双向绑定

    当今前端天下以 Angular.React.vue 三足鼎立的局面,你不选择一个阵营基本上无法立足于前端,甚至是两个或者三个阵营都要选择,大势所趋. 所以我们要时刻保持好奇心,拥抱变化,只有在不断的变 ...

随机推荐

  1. js为什么是单线程的?10分钟了解js引擎的执行机制

    深入理解JS引擎的执行机制 1.JS为什么是单线程的? 为什么需要异步? 单线程又是如何实现异步的呢? 2.JS中的event loop(1) 3.JS中的event loop(2) 4.说说setT ...

  2. K个排序链表的合并(Hard)

    问题来源:选自leetCode 23:合并K个排序链表 问题描述: 题目给定信息: 不确定需要合并的链表的数目,但依然要求我们把给定的这些有序链表合并成一个链表,并且保证合并的链表依然是有序的. 问题 ...

  3. EMI优化

    一般印刷电路板之间的高速信号线路无法通过FCC和VDE辐射测试. 优化方案有以下3种: 1.高频滤波 通常做法在每个逻辑驱动器上串联一个小阻抗,并经过一个旁路电容接地. 旁路电容接地需足够干净,如机箱 ...

  4. java 实现加密算法(在网上看的,保存)

    import java.util.ArrayList;   import java.util.List;       /**   * DES加密/解密   *   * @Copyright Copyr ...

  5. [LeetCode] Bricks Falling When Hit 碰撞时砖头掉落

    We have a grid of 1s and 0s; the 1s in a cell represent bricks.  A brick will not drop if and only i ...

  6. Pandora 生成 Token

    生成 token 打数据到仓库 通过 api 签名工具实现 最后通过curl -XPOST -H "Content-Type: application/json" -H " ...

  7. 变量类型-Dict

    教程:一:字典的创建        1:字典的介绍------>d = {key1:value1, key2:values2} (1)dictionary(字典) 是 Python 中最有用的数 ...

  8. css_文本溢出

    1.单行文本溢出隐藏,显示省略号 2.多行文本溢出隐藏,显示省略号 1.只针对用webkit内核浏览器渲染页面才会有效果 2.(多行文本溢出隐藏,显示省略号)通用方法

  9. having 的使用

    .用一条SQL 语句 查询出每门课都大于80 分的学生姓名 name kecheng fenshu 张三 语文 张三 数学 李四 语文 李四 数学 王五 语文 王五 数学 王五 英语 select n ...

  10. 初学node.js,安装nodemon,学习debug模式,安装cpu-stat

    1.运行node  文件     node .\01.js      文件内容   console.log('aaaa'); 2.因为每次更新文件都需要重新,所以安装nodemon    npm i ...