概述
从源码入手来分析Vue中常用的写法(持续更新中···)
如何调试源码请参考另一篇文章(https://blog.csdn.net/seeyousayhi/article/details/108067185)
先说几个高频点
- 1.v-for中key的作用和工作原理
- 2.v-if和v-for优先级问题
1.v-for中key的作用和工作原理
工作中v-for非常常见,那你知道写key的时候发生了什么吗,一起来看一下
源码位置:vuesrccorevdompatch.js (updateChildren) 也就是diff发生的地方
先上例子:
在一秒后插入999这个数字
// template部分
<p v-for="item in list">{{item}}</p>
// script部分
data() {
return { list: [1, 2, 3, 4, 5 ] }
},
mounted() {
setTimeout(() => {
// 在3前面插入999
this.list.splice(2,0, 999)
}, 1000)
}
diff的时候我们可以认为就是新老数组之间的比较:
不用key结果图解:
使用key:
// 第一次循环 patch 1
1 2 3 4 5
1 2 999 3 4 5
// 第二次循环patch 2
2 3 4 5
2 999 3 4 5
// 第三次循环patch 3
3 4 5
999 3 4 5
// 第四次循环patch 4
3 4
999 3 4
// 第五次循环patch 3
3
999 3
// 老数组循环之后即全部完成,新数组中剩下999,找到关系,创建999并插入到3前面
可以看出来不使用key的时候,老数组和新数组 是按顺序来比较的,1 和1 ,2和2,3和999 ,4和3 以此类推。
使用了key之后 1和1,2和2,然后是5和5,4和4,3和3 ,最后剩下一个999,此时比较的顺序发生了变化!!
疑问来了,为什么会是这样呢???
从源码中找答案,我们进去看一下:
核心方法 updateChildren,里面发生的就是比较规则,updateChildren主要作⽤是⽤⼀种较⾼效的⽅式⽐对新旧两个VNode的children得出最⼩操作补丁。执⾏⼀个双循环是传统⽅式,vue中针对web场景特点做了特别的算法优化。
从方法刚开始的定义我们可以大致了解到,首先是定义了4个index,和4个vnode,分别是新老数组左右头尾两侧都有⼀个下标(index)和vnode本身(vnode)的这么一个标记,如图:
在遍历过程中这⼏个变量都会向中间靠拢。 当oldStartIdx > oldEndIdx或者newStartIdx > newEndIdx时结束循环,并伴随着vnode的移动,来判断追加还是删除。
循环开始时进入while中,走完前两个isUndefined会进入到else if(sameVnode)的方法中。
让我们进去看看:
当我们不写key的时候,key的值就是undefined,undefined === undefined 是成立的,此时我们比较的是同级元素tag标签也成立,isComment不是注释也成立,后面一样的也成立,会认为此时我们比较的是相同元素,而后在实际的dom更新函数中执行到3和999的时候,值不相同,强制更新,再后面4和3也不同,强制更新。
反观写key的时候,上面进行的比较 1和1的key相同,2和2的key相同,3和999的key不同,此时不会发生更新,而是发现还有相同的index,就进入到newEndIndex和oldEndIndex的else if 循环当中, 从后面开始比较了,直到剩下一个999,dom才更新。
结论
1.最重要的一点,key的作用主要是为了高效的更新虚拟DOM,其原理是vue在patch过程中通过key可以判断两个节点是否是同一个,从而避免频繁更新 不同的元素,减少DOM操作,使得性能更加高效。
2.若不设置或者不合理设置key,在列表更新时会引发一些意外的bug。
3.vue中使用相同标签名元素的过渡切换时,也会使用到key,其目的也是为了可以让vue区分它们,否则vue只会替换其内部属性而不会触发过渡效果。
2.v-if和v-for优先级问题
先上代码,需求是根据list里面的 isShow来判断显隐,来看看这种写法的渲染函数是怎样的,打印一下render内容
<div id="app">
<div>
<div v-if="item.isShow" v-for="item in list">{{item.id}}</div>
</div>
</div>
<script>
let vm = new Vue({
el: '#app',
data: {
list: [{ id: 1, isShow: true },{ id: 2, isShow: false }]
}
})
console.log(vm.$options.render)
// 打印结果
下面是打印结果,发现一堆_开头的函数,都是vue底层实现的内部方法。
_l 是渲染列表函数,_c 是创建标签,_e是空函数
function anonymous() {
with(this) {
return _c('div', {
attrs: {
"id": "app"
}
}, [_c('div', _l((list),
function(item) {
return (item.isShow) ? _c('div', [_v(_s(item.id))]) : _e()
}), 0), _v(" "), _c('div', [_v(_s(renderContent))])])
}
}
可以看出来是先执行_l 进行了一轮循环,然后判断item.isShow来进入函数_c还是函数_e,此时能发现是for循环的优先级比较高,然后是if
换另一种写法,通过computer计算属性来filter出要循环的列表
<div id="app">
<div>
<!-- <div v-if="item.isShow" v-for="item in list">{{item.id}}</div> -->
<div v-for="option in options">{{option.id}}</div>
</div>
</div>
<script>
let vm = new Vue({
el: '#app',
data: {
list: [{ id: 1, isShow: true }, { id: 2, isShow: false }],
},
computed: {
options() {
return this.list.filter(item => item.isShow)
}
}
})
console.log(vm.$options.render);
再来看一下render函数
with(this) {
return _c('div', {
attrs: {
"id": "app"
}
}, [_c('div', _l((options), function(option) {
return _c('div', [_v(_s(option.id))])
}), 0)])
}
通过比较,发现利用了computed 就跳过了判断 ,直接_l ,很显然这种方法是很好的
再通过源码来看一下for与if优先级的比较,源码位置在 src/compiler/codegen/index.js
export function genElement (el: ASTElement, state: CodegenState): string {
if (el.parent) {
el.pre = el.pre || el.parent.pre
}
if (el.staticRoot && !el.staticProcessed) {
return genStatic(el, state)
} else if (el.once && !el.onceProcessed) {
return genOnce(el, state)
} else if (el.for && !el.forProcessed) {
return genFor(el, state)
} else if (el.if && !el.ifProcessed) {
return genIf(el, state)
} else if (el.tag === 'template' && !el.slotTarget && !state.pre) {
return genChildren(el, state) || 'void 0'
} else if (el.tag === 'slot') {
return genSlot(el, state)
} else {
从源码的实现上来看 for的优先级就是高于if,如果两者写在同级的话,执行顺序就是先循环再判断,因为渲染的时候会执行很多次,就会浪费一些性能,我们在平常写的时候就可以避免这种写法!!!
结论:
1.vue在实现上 v-for 优先于 v-if被解析
2.如果两者写在同级,每次渲染都会先循环再判断,浪费了性能
最后
以上就是细腻毛巾为你收集整理的从源码入手来分析Vue中常用的写法的全部内容,希望文章能够帮你解决从源码入手来分析Vue中常用的写法所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复