前端Vue 源码分析-逻辑层

news/2024/7/10 2:06:58 标签: vue, 前端

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 ;     

}

总结
以上就是我要说的内容,希望以上的内容可以帮助到正在默默艰辛的大家,希望大家在往后的工作与面试中一切顺利。
那如何学习才能快速入门并精通呢?
当真正开始学习的时候难免不知道从哪入手,导致效率低下影响继续学习的信心。
但最重要的是不知道哪些技术需要重点掌握,学习时频繁踩坑,最终浪费大量时间,所以有一套实用的视频课程用来跟着学习是非常有必要的。
本次给大家推荐一个免费的学习群,里面概括移动应用网站开发,css,html,webpack,vue node angular以及面试资源等。
对web开发技术感兴趣的同学,欢迎加入Q群:866109386,不管你是小白还是大牛我都欢迎,还有大牛整理的一套高效率学习路线和教程与您免费分享,同时每天更新视频文件资料。
最后,祝大家早日学有所成,拿到满意offer,快速升职加薪,走上人生巅峰。
在这里插入图片描述


http://www.niftyadmin.cn/n/1301358.html

相关文章

微信小程序保存图片到相册

先来看小程序中的保存图片到相册的api wx.saveImageToPhotosAlbum({filePath : "./test.png", //这个只是测试路径,没有效果success(res) {console.log("success");},fail : function(res) {console.log(res);} })上面的saveImageToPhotosAlb…

springboot2.1.3整合dubbo2.6.5(提供者、消费者Demo 注解方式无xml)

linux中启动zookeeper dubbo-api public interface DubboDemoService {List<Brand> findAll(); }dubbo-provider pom.xml <dependencies><dependency><groupId>com.chuai</groupId><artifactId>dubbo-api</artifactId><versio…

微信小程序制作海报保存到相册发朋友圈

这个功能应该分三步来做&#xff1a; 一、制作海报图片 二、保存图片到相册 三、手动发朋友圈再到相册中取图片 详细步骤&#xff1a; 一、制作海报 1.要制作能保存到相册的图片&#xff0c;我们需要一个canvas标签&#xff0c;在我们的wxml的最后加入canvas标签&#xff1a; &…

面试web前端工程师的两个星期的心路历程及面试题

毕业接近3年半了&#xff0c;年初公司进行了一次业务调整&#xff0c;hr小姐姐明里暗里让我们自动滚蛋&#xff0c;但是我牛脾气上来了你不说可能我自己会走&#xff0c;你这一说我偏不走了&#xff0c;于是乎&#xff0c;生生熬到9月份。。。 9月中旬开始改简历&#xff0c;投…

mybatis sum(参数) 列名作为参数

项目中有很多个字段&#xff0c;当字段为空的时候&#xff0c;求该列的平均值并赋值给该字段。如&#xff1a; idnameage 当我需要插入一条数据的时候&#xff0c;这条数据的age为空&#xff0c;那么需要先从数据库查询age的平均值&#xff0c;然后赋值给当前的数据。当字段很…

Angular、React、Vue三选一,前端工程师更青睐使用哪款框架?

现在前端三架马车Angular、React、Vue各有所长。Angular从一开始就带有很明显的工程化血统&#xff0c;齐全的cli命令&#xff0c;背靠TypeScript&#xff0c;涉及模块、服务以及指令等概念&#xff0c;使用后端的依赖注入思想&#xff0c;特有模板语法。React和Vue就"轻&…

重启服务器前后需要做的事

重启前 1. 看服务器启动了哪些应用&#xff0c;并导入记录到本地 ps看哪些应用正在运行&#xff08;more–>空格或回车翻页&#xff09; ps -ef|more如tomcat、apache的httpd、zookeeper、memcache、还有springboot springcloud项目、solr等等服务。 导出ps信息到本地 …

TexturePacker破解版教程及下载

如果已经了解如何破解的同学可以忽略文章&#xff0c;直接下载&#xff1a; 百度网盘https://pan.baidu.com/s/1xPQEpsg33goeBEF-wXvuog#list/path%2F,密码egyf。 大家只需要下载TexturePacker_3.0.9.rar就可以了&#xff1a;虽然百度网盘下载的不是一般的慢&#xff0c;但是好…