vuevnode的简单介绍
本篇文章给大家谈谈vuevnode,以及对应的知识点,希望对各位有所帮助,不要忘了收藏本站喔。
本文目录一览:
- 1、Vue是如何实现对{{}}处理完成之后再次处理的?
- 2、【面试题解析】从 Vue 源码分析 key 的作用
- 3、Vue原理解析(五):彻底搞懂虚拟Dom到真实Dom的生成过程
- 4、VUE之自定义指令
Vue是如何实现对{{}}处理完成之后再次处理的?
vue的模板被编译成虚拟旦数帆节点,也就是vNode。就是你说的替换。
vue 监听到vNode依赖的响应性数据发成变化时,就会对模雹vNode进行更新。
每次vNode更新,vue都会吧更新的vNode重新渲染到它的父节点上。这就毕斗是你说的再次处理。
[img]【面试题解析】从 Vue 源码分析 key 的作用
最近看了面试题中有一个这样的题, v-for 为什么要绑定 key?
Vue 中 key 很多人都弄不清楚有什么作用,甚至还有些人认为不绑定 key 就会报错。
其实没绑定 key 的话,Vue 还是可以正常运行的,报警告是因为没通过 Eslint 的检查。
接下来将通过源码一步步分析这个 key 的作用。
Virtual DOM 最主要保留了 DOM 元素的层级关系和一些基本属性,本质上就是一个 JS 对象。相对于真实的 DOM,Virtual DOM 更简单,操作起来速度更快。
如果需要改变 DOM,则会通过新旧 Virtual DOM 对比,找出需要修改的节点进行真实的 DOM 操作,从而减小性能消耗。
传统的 Diff 算法需要遍历一个树的每个节点,与另一棵树的每个节点对比,时间复杂度为 O(n²)。
Vue 采用的 Diff 算法则通过逐级对比,大大降低了复杂性,时间复杂度为 O(n)。
VNode 更新首先会经过 patch 函数, patch 函数源码如下:
vnode 表示更新后的节点,oldVnode 表示更新前的节点,通过对比新旧节点进行操作。
1、vnode 未定义,oldVnode 存在则触发 destroy 的钩子函数
2、oldVnode 未定义,则根据 vnode 创建新的元素
3、oldVnode 不为真拆派实元素并且 oldVnode 与 vnode 为同一节点,则会调用 patchVnode 触发更新
4、oldVnode 为真实元素或者 oldVnode 与 vnode 不是同一节点,另做处理
接下来会进入 patchVnode 函数,源码如下:
1、vnode 的 text 不存在,则会比对 oldVnode 与 vnode 的 children 节点进行更新操作
2、vnode 的 text 存在,则会修改 DOM 节点的 text
接下来在 updateChildren 函数内就可以看到 key 的用处。
key 的作用主要是给 VNode 添加唯一标识,通过这个 key,可以更快找到新旧 VNode 的变化,从而进一步操作。
key 的作用主要表现在以下这段源码中。
updateChildren 过程为:
1、分别用两个指针(startIndex, endIndex)表示 oldCh 和 newCh 的头尾节点
2、对指针所对应的节点做一个两两比较,判断是否属于同一节点
3、如果4种比较都没有匹配,那么判断是否有 key,有 key 就会用 key 去做一个比较;无 key 则会通过遍历的形式进行比较
4、比较的过程中,指针往中间靠,当有一个 startIndex endIndex,则表示有一个已经遍历完了,比较结束
从 VNode 的渲染过程可以得知,Vue 的 Diff 算法先进行的是同级比较,然后再比较子节点。
子节点比较会通过 startIndex、endIndex 两个指针进行两两比较,再通旅誉贺过 key 比对子节点。如果没设置 key,则会通过遍历的方式匹配节点,增加性能消耗。
所以不绑定 key 并不会有问题,绑定 key 之后在性能上有一定的提升。
综上,key 主要是应用在 Diff 算法中,作用是为了更快速定位出相同的新旧节点,尽量减少 DOM 的创建和销毁的操作。
希望以上内容能够对各位小伙伴有所帮助,祝大家面试顺利。
Vue 的文档中对 key 的说明如下:
关于就地修改,关键在于 sameVnode 的实现,源码如下:
可以看出,当 key 未绑定时,主要通过元素的标签等进行判断,在 updateChildren 内会将 oldStartVnode 与 newStartVnode 判断为同一节点。
如果 VNode 中只包含了文本节点,在 patchVnode 中可以直接替换文本节点,而不需要移动节点的位置,确实在不绑定 key 的情况下效率要高一丢丢。
某些情况下不绑定 key 的效率更高,那为什么大部分Eslint的规则还是要求绑定 key 呢?
因为在实际项目中,大多数情况下 v-for 的节点内并虚姿不只有文本节点,那么 VNode 的字节点就要进行销毁和创建的操作。
相比替换文本带来的一丢丢提升,这部分会消耗更多的性能,得不偿失。
了解了就地修改,那么我们在一些简单节点上可以选择不绑定 key,从而提高性能。
如果你喜欢我的文章,希望可以关注一下我的公众号【前端develop】
Vue原理解析(五):彻底搞懂虚拟Dom到真实Dom的生成过程
再有一棵树形结构的 JavaScript 对象后,我们现在需要做的就是将这棵树跟真实的 Dom 树形成映射关系,首先简单回顾之前遇到的 mountComponent 方法:
我们已经执行完了 vm._render 方州并法拿到码闭了 VNode ,现在将它作为参数传给 vm._update 方法并执行。 vm._update 这个方法的作用就是就是将 VNode 转为真实的 Dom ,不过它有两个执行的时机:
我们现在先来看下 vm._update 方法的定义:
这里的 vm.$el 是之前在 mountComponent 方法内就挂载的,一个真实 Dom 元素。首次渲染会传入 vm.$el 以及得到的 VNode ,所以看下 vm.__patch__ 定义:
__patch__ 是 createPatchFunction 方法内部返回的一个方法,它接受一个对象:
nodeOps 属性:封装了操作原生 Dom 的一些方法的集合,如创建、插入、移除这些,再使用到的地方再详解。
modules 属性:创建真实 Dom 也需要生成它的如 class / attrs / style 等属性。 modules 是一个数组集合,数组的每一项都是这些属性对应的钩子方法,这些属性的创建、更新、销毁等都有对应钩子方法,当某一时刻需要做某件事,执行对应的钩子即可。比如它们都有 create 这个钩子方法,如将这些 create 钩子收集到一个数组内,需要在真实 Dom 上创建这些属性时,依次执行数组的每一项,也就是依次创建了它们。
这里大家记住一句话即可,无论 VNode 是什么类型的节点,只有三种类型的节点会被创建并插入到的 Dom 中:元素节点、注释节点、和文本节点。
我们接着来看下 createPatchFunction 它究竟返回一个什么样的方法:
首次渲染时没有 oldVnode , oldVnode 就是 $el ,一个真实的 dom ,经过 emptyNodeAt(oldVnode) 方法包装:
再将传入的 $el 属性转为了 VNode 格式之后,我们继续:
createElm 方法开始生成真实的 Dom , VNode 生成真实的 Dom 的方式还是分为元素节点和组件两种方式,所以我们册模迹使用上一章生成的 VNode 分别说明。
大家可以先看下这个流程图有一个印象即可,接下来再看具体实现时相信思路会清晰很多:
开始创建 Dom ,我们来看下它的定义:
依次判断是否是元素节点、注释节点、文本节点,分别创建它们然后插入到父节点里面,这里主要介绍创建元素节点,另外两个并没有复杂的逻辑。我们来看下 createChild 方法定义:
开始创建子节点,遍历 VNode 的每一项,每一项还是使用之前的 createElm 方法创建 Dom 。如果某一项又是数组,继续调用 createChild 创建某一项的子节点;如果某一项不是数组,创建文本节点并将它添加到父节点内。像这样使用递归的形式将嵌套的 VNode 全部创建为真实的 Dom 。
再看一遍流程图,相信大家疑惑已经减少很多:
首先还是看张简易流程图,留个印象即可,方便理清之后的逻辑顺序:
执行 createComponent 方法,如果是元素节点不会返回任何东西,所以是 undefined ,会继续走接下来的创建元素节点的逻辑。现在是组件,我们看下 createComponent 的实现:
首先会将组件的 vnode.data 赋值给 i ,是否有这个属性就能判断是否是组件 vnode 。之后的 if(isDef(i = i.hook) isDef(i = i.init)) 集判断和赋值为一体, if 内的 i(vnode) 就是执行的组件 init(vnode) 方法。这个时候我们来看下组件的 init 钩子方法做了什么:
activeInstance 是一个全局的变量,再 update 方法内赋值为当前实例,再当前实例做 __patch__ 的过程中作为子组件的父实例传入,在子组件的 initLifecycle 时构建组件关系。将 createComponentInstanceForVnode 执行的结果赋值给了 vnode.componentInstance ,所以看下它的返回的结果是什么:
再组件的 init 方法内首先执行 createComponentInstanceForVnode 方法,这个方法的内部就会将子组件的构造函数实例化,因为子组件的构造函数继承了基类 Vue 的所有能力,这个时候相当于执行 new Vue({...}) ,接下来又会执行 _init 方法进行一系列的子组件的初始化逻辑,我们回到 _init 方法内,因为它们之间还是有些不同的地方:
前面都还执行的好好的,最后却因为没有 el 属性,所以没有挂载, createComponentInstanceForVnode 方法执行完毕。这个时候我们回到组件的 init 方法,补全剩下的逻辑:
我们在 init 方法内手动挂载这个组件,接着又会执行组件的 _render() 方法得到组件内元素节点 VNode ,然后执行 vm._update() ,执行组件的 __patch__ 方法,因为 $mount 方法传入的是 undefined , oldVnode 也是 undefined ,会执行 __patch__ 内的这段逻辑:
这次执行 createElm 时没有传入第三个参数父节点的,那组件创建好的 Dom 放哪生效了?没有父节点也要生成 Dom 不是,这个时候执行的是组件的 __patch__ ,所以参数 vnode 就是组件内元素节点的 vnode 了:
很明显这个时候不是组件了,即使是组件也没关系,大不了还是执行一遍 createComponent 创建组件的逻辑,因为总会有组件是由元素节点组成的。这个时候我们执行一遍创建元素节点的逻辑,因为没有第三个参数父节点,所以组件的 Dom 虽然创建好了,并不会在这里插入。请注意这个时候组件的 init 已经完成,但是组件的 createComponent 方法并没有完成,我们补全它的逻辑:
无论是嵌套多么深的组件,遇到组件的后就执行 init ,在 init 的 __patch__ 过程中又遇到嵌套组件,那就再执行嵌套组件的 init ,嵌套组件完成 __patch__ 后将真实的 Dom 插入到它的父节点内,接着执行完外层组件的 __patch__ 又插入到它的父节点内,最后插入到 body 内,完成嵌套组件的创建过程,总之还是一个由里及外的过程。
再回过头来看这张图,相信会好理解很多~
接下来会将 updateComponent 传入到一个 Watcher 的类中,这个类是干嘛的,我们下一章再说明,接下来执行 mounted 钩子方法。至此 new Vue 的整个流程就全部走完了。我们回顾下从 new Vue 开始它的执行顺序:
最后我们还是以一道 vue 可能会被问到的面试题作为本章的结束吧~
顺手点个赞或关注呗,找起来也方便~
你可能会用的上的一个vue功能组件库,持续完善中...
VUE之自定义指令
声明自定义指令:
bind:只调用一次,指令第一次绑定到元素时,用这个钩子函数可以定义一个在绑定时执行一次的初始化动作。
inserted:被绑定元素插入父节点时调用(父节点存在历宏即可调用,不必存在与document中)
update:被绑定的元帆答素所在的模板更新时调用,而不论绑定值是否变化。通过比较更新前后的绑定值,可以忽略不必要的模板更新
componentUpdated:被绑定元素所在模板完成一次更新周期时调用。
unbind:只调用一次,指令与元素解绑时调用。
钩子函数参数有:
el:指令所绑定的元素,可以用来直接操作DOM
bindng:一个对象,包含以下属性
1)name:指令名,不包含v-前缀
2)value:指令的绑定值,例态烂慧如:v-my-directive=“1+1”,value的值就是2
3)oldValue:指令绑定的前一个值,仅在update和componentUpdated钩子中可用,无论值是否改变都可用
4)expression:绑定值的表达式或变量名。例如 v-my-directive="1 + 1" , expression 的值是 "1 + 1"。
5)arg:传给指令的参数。例如v-my-directive:foo,arg的值是foo
6)modifiers:一个包含修饰符的对象。例如: v-my-directive.foo.bar, 修饰符对象 modifiers 的值是 { foo: true, bar: true }。
vnode:vue编译生成的虚拟节点
oldVnode:上一个虚拟节点,仅在update和componentUpdated钩子中可用,
关于vuevnode和的介绍到此就结束了,不知道你从中找到你需要的信息了吗 ?如果你还想了解更多这方面的信息,记得收藏关注本站。