Vue 源码分析-逻辑层

预期的效果:

监听input的输入,input在输入的时候,会触发 watch与computed函数,并且会更新原始的input的数值。所以直接跟input相关的处理就有3处,但实际上会有连带性的触发,触发watch的input函数的时候,还会触发this.answer对应的依赖处理

看看内部是如何处理的:

Vue在初始化data的时候,会通过Object.defineProperty重新定义input的set与get访问接口,同时会创建一个记录并且保持其数据对应的依赖watcher对象的Dep对象,这个Dep对象是通过闭包的方式保存在每个独立的data中,而Dep就是用于收集当前data所依赖的Watcher对象

简单来说

  1. 在data中定义了input,那么意味着需要对这个变量进行defineProperty的处理,并创建Dep对象

  2. watch中的input函数会变成一个Watcher对象,因为它与input有关系,所以需要在data的input的Dep中保存一份引用

  3. computed中的compiledMarkdown函数会变成一个Watcher对象,,因为它与input有关系,所以需要在data的input的Dep中保存一份引用

input数据的监控内部创建的Dep的结构,watch与computed明明只有2个对应的Watcher对象,为什么subs会有3个呢?多增加的一个是干什么的?这个多出的Watcher就是vue2中的虚拟dom的处理,后面会提到

这里最终可以简单的梳理下更新的流程:当input数据发生变化的时候,只需要调用响应依赖的Watcher对象,Watcher对象就会负责各自的更新处理。这里面向对象的设计优势就体现出来了,将行为分布在各个对象中,并让这些对象负责自己的行为,所以每个不同Watcher对象更新各自的特点,处理各自的逻辑

更新

更新逻辑:

vue1的 dom更新方式采用队列+直接更新的处理,这种简单粗暴。vue2在vue1的设计上,继续保留了队列的处理方式,同时结合了时下最流行的 virtual dom

记得在Vue1中,每个Watcher对象都会保存各自的dom节点的处理方式,通过对Watcher的的处理达到直接更新DOM的目的。Vue2因为引入的Virtual Dom的机制,所以Watcher的工作就需要变化了,大多数的Watcher不再直接负责DOM的更新操作,而只是更新数据。这里用了大多数,因为还有一个Watcher是跟Virtual Dom相关的。所以这就是在上文提到的Dep中会多一个Watcher的原因了

Virtual DOM

虚拟DOM的文章现在已经很多了,但是如何紧密结合vue中,到实际的运用是我们分析的重点,这里只是粗略下,我还要抽时间把算法看完先

原理:

简单的说,直接通过JS操作浏览器API去绘制DOM节点是很慢的,大量的页面处理中,开发者不经意就会调用更多多余或者重复的操作,这种是有性能开销的。那么有什么办法减少这种是误操作呢?就是通过一种方式能算出来最小的更新量,从而提高效率。既然要计算出对小的更新量,那么就会有对比,需要通过对新旧两个节点的对比从而计算出。DOM的操作很慢,但是JS确很快的,DOM 树上的结构、属性信息我们都可以很容易地用 JavaScript 对象表示出来,既然我们可以用JS对象表示DOM结构,那么当数据状态发生变化而需要改变DOM结构时,我们先通过JS对象表示的虚拟DOM计算出实际DOM需要做的最小变动,反过来,就可以根据这个用 JavaScript 对象表示的树结构来构建一棵真正的DOM树,操作实际DOM更新了, 从而避免了粗放式的DOM操作带来的性能问题。

根据上面的原理,Virtual DOM在实现上首先就必须先建立可以对比的JS对象,这个叫做vnode,也就是虚拟DOM了,这个对象是真实DOM结构的一个映射,通过对比更新前后vnode的变化差异diff,记录下来的不同就是我们需要对页面真正的 DOM 操作。

Virtual DOM算法,简单总结下包括几个步骤:

  1. 用JS对象描述出DOM树的结构,然后在初始化构建中,用这个描述树去构建真正的DOM,并实际展现到页面中

  2. 当有数据状态变更时,重新构建一个新的JS的DOM树,通过新旧对比DOM数的变化diff,并记录两棵树差异

  3. 把步骤2中对应的差异通过步骤1重新构建真正的DOM,并重新渲染到页面中,这样整个虚拟DOM的操作就完成了,视图也就更新了

看到这里可以简单总结下,Vue中Watcher与Virtual DOM的关系:

  1. Watcher 是来决定你要不要更新这个dom

  2. 虚拟DOM是用来找出怎么以最小的代价来更新

Vue2中对应的逻辑

这里不会涉及算法,并非这章的重点,主要看下整个更新过程中,虚拟DOM逻辑是怎么配合工作的。

继续input的数据流向,之前讲到了input中的Dep是保存了3个Watcher对象的引用,其中会有一个Watcher是跟整个页面的渲染有关系的,这个就是用来封装vnode的处理。

当遍历Dep这个保存Watcher数组的时候,会把Watcher加入到一个异步的队列中进行处理

代码进行了简化

function queueWatcher(watcher) {

var id = watcher.id;

if (has[id] == null) {

has[id] = true;

queue.push(watcher);

nextTick(flushSchedulerQueue);

}

}

function flushSchedulerQueue() {

queue.sort(function(a, b) { return a.id - b.id; });

for (index = 0; index ) {

watcher = queue[index];

id = watcher.id;

has[id] = null;

watcher.run();

}

}

这里很关键的一个点就是针对queue进行了排序,原因就是其中有一个Wacher是保存了vnode了,因为最后一步才是vnode的对比更新。必须让前面的Watcher更新数据完毕后,最后vnode才能做真正的对比,不过computed的Wacher不会加入到这个队列中,它会再编译树中动态的执行。

当前面的Watcher执行完毕后,调到最后一个Watcher,可以看到对应的代码

vm._update(vm._render(), hydrating);

  1. 通过vm._render方法构建vnode

  2. 通过vm._update 对比vnode,并渲染到页面中

vm._render

初始化的时,会通过构建出来的JS描述树,生成初始vnode,去绘制初始页面。每次DOM变化的时候,我们还是需要重新构建这个描述树,通过这个描述树去构建新的vnode

但是这个结构是可执行的,可编译的,通过with的方式改变this的上下文,动态执行每个可执行的代码部分,并把每个节点部分都编译成vnode,组成一个有对应层次结构的vnode对象

举例来说

div是最外层的vnode

div有子节点=> p,生成对应vnode

p有子节点=>文本节点answer,生成对应vnode

每个vnode会保存每个对应节点一些计算信息,比如tag、data、 children、text这些都是用于后面的比对计算的

vm._update

通过render拿到了vnode,然后通过update对比vnode绘制到页面

update这个方法内部有段代码

vm.$el = vm.__patch__(prevVnode, vnode);

从这个字面意思就明显知道,更新补丁,用于对比新旧2个vnode,

vue2有个专门的patch文件用于vnode的对比策略,patch内部会细分很多策略出来

  1. 如果vnode不存在但是oldVnode存在,就意味着要销毁

  2. 如果oldVnode不存在但是vnode存在,说明意图是要创建新节点

  3. 当vnode和oldVnode都存在时,就需要更新了

每一种策略都对应的不同的处理方式,更新才意味着需要对比新旧的vnode,首先是需要判断下两个节点是否值得比较,在这个例子里面只改变了属性input与answer的值,所以,这里是属于同节点内的属性变更的,所以检测vnode的变化也是相对最简单,递归子节点,通过patchVnode检测每个节点属性的变化

if(sameVnode(oldStartVnode, newStartVnode)) {

patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue);

oldStartVnode = oldCh[++oldStartIdx];

newStartVnode = newCh[++newStartIdx];

}

当对比到差异时,例如文本answer被改变,那么对应的vnode在对比的时候,就能找到差异,然后重新设置值,此刻的node就是真实的DOM引用的,如果改变了textContent就意味着页面上呈现的数据就直接被改变了

if (oldVnode.text !== vnode.text) {

nodeOps.setTextContent(elm, vnode.text);

}

function setTextContent (node, text) {

node.textContent = text;

}

前端Vue 源码分析-逻辑层的更多相关文章

  1. [Vue源码分析] v-model实现原理

    最近小组有个关于vue源码分析的分享会,提前准备一下… 前言:我们都知道使用v-model可以实现数据的双向绑定,及实现数据的变化驱动dom的更新,dom的更新影响数据的变化.那么v-model是怎么 ...

  2. Vue源码分析(二) : Vue实例挂载

    Vue源码分析(二) : Vue实例挂载 author: @TiffanysBear 实例挂载主要是 $mount 方法的实现,在 src/platforms/web/entry-runtime-wi ...

  3. Vue源码分析(一) : new Vue() 做了什么

    Vue源码分析(一) : new Vue() 做了什么 author: @TiffanysBear 在了解new Vue做了什么之前,我们先对Vue源码做一些基础的了解,如果你已经对基础的源码目录设计 ...

  4. Vue2源码分析-逻辑梳理

    很久之前就看完vue1,但是太懒就一直没写博客,这次打算抽下懒筋先把自己看过了记录下来,否则等全部看完,估计又没下文了 看源码总需要抱着一个目的,否则就很难坚持下去,我并没做过vue的项目,我几乎很少 ...

  5. vue源码分析—Vue.js 源码目录设计

    Vue.js 的源码都在 src 目录下,其目录结构如下 src ├── compiler # 编译相关 ├── core # 核心代码 ├── platforms # 不同平台的支持 ├── ser ...

  6. vue源码分析—认识 Flow

    认识 Flow Flow 是 facebook 出品的 JavaScript 静态类型检查⼯具.Vue.js 的源码利⽤了 Flow 做了静态类型检查, 所以了解 Flow 有助于我们阅读源码 Flo ...

  7. Vue 源码分析—— 目录结构

    一,Vue.js 的源码都是在src 目录下,其目录结构如下. 1.compiler 目录包含Vue.js 所有编译相关的代码.它包括把所有模板解析成ast 语法树, ast 语法树优化等功能. 2. ...

  8. vue源码分析—Vue.js 源码构建

    Vue.js 源码是基于 Rollup 构建的,它的构建相关配置都在 scripts 目录下.(Rollup 中文网和英文网) 构建脚本 通常一个基于 NPM 托管的项目都会有一个 package.j ...

  9. vue源码分析之目录架构(一)

    compiler compiler 目录包含 Vue.js 所有编译相关的代码.它包括把模板解析成 ast 语法树,ast 语法树优化,代码生成等功能 core core 目录包含了 Vue.js 的 ...

随机推荐

  1. 1Z0-053 争议题目解析481

    1Z0-053 争议题目解析481 考试科目:1Z0-053 题库版本:V13.02 题库中原题为: 481.Which statement is true about a running sessi ...

  2. App Widget简单应用

    首先后台进程创建一个PendingIntent对象,其中PendingIntent中包含一个真正的Intent,创建完成后将此PendingIntent对象交给桌面控件所在的进程,当用户点击桌面控件或 ...

  3. WordPress博客搬家注意事项

    博客域名还有一段时间就到期了,准备更换域名和空间,会出现一些问题,我这里在网上收集整理了一下,基本上会遇到两个重要的问题. 首先第一个问题就是数据的搬迁中出现的错误. 我这里用的是phpmyadmin ...

  4. CVPR2015文章下载

    http://www.cv-foundation.org/openaccess/content_cvpr_2015/html/Liu_Real-Time_Part-Based_Visual_2015_ ...

  5. [SQL]断开并更改数据库名

    EXEC sp_dboption 'my', 'Single User', 'TRUE' EXEC sp_renamedb 'my', 'mycrjtest' EXEC sp_dboption 'my ...

  6. 浅谈购物车中cookie的使用

    购物车对于电商网站来说是一个非常重要的模块.最近自己的项目中也用到了,所以拿出来说说事! 购物车是用户选择商品的一个缓存的地方.其中包含了商品的基本信息,例如:商品的描述,商品的价格,商品的数量等等. ...

  7. 简单说说Android自定义view学习推荐的方式

    这几天比较受关注,挺开心的,嘿嘿. 这里给大家总结一下学习自定义view的一些技巧.  以后写自定义view可能不会写博客了,但是可以开源的我会把源码丢到github上我的地址:https://git ...

  8. 【Docker】(3)---linux部署Docker、Docker常用命令

    linux部署Docker.Docker常用命令 本次部署Linux版本:CentOS 7.4 64位. 说明: 因为Docker是基于Linux 64bit的 所以Docker要求64位的系统且内核 ...

  9. 去掉字符空格js

    1. 去掉字符串两端的空格 String.prototype.trim = function () { return this.replace(/(^\s*)|(\s*$)/g, "&quo ...

  10. JS中replace替换全部的正确应用

    一般使用 var str = "test-test-test"; str = "test-test-test".replace("test" ...