Vue 源码分析-逻辑层css
预期的效果:html
监听input的输入,input在输入的时候,会触发 watch与computed函数,而且会更新原始的input的数值。因此直接跟input相关的处理就有3处,但实际上会有连带性的触发,触发watch的input函数的时候,还会触发this.answer对应的依赖处理vue
看看内部是如何处理的:node
Vue在初始化data的时候,会经过Object.defineProperty从新定义input的set与get访问接口,同时会建立一个记录而且保持其数据对应的依赖watcher对象的Dep对象,这个Dep对象是经过闭包的方式保存在每一个独立的data中,而Dep就是用于收集当前data所依赖的Watcher对象webpack
简单来讲web
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,快速升职加薪,走上人生巅峰。