在此,我们详细分析一下几个vue经典问题:

v-if和v-for哪个优先级高

  • v-for优先级高,如果v-for和v-if放在同一个元素上,则会先循环再判断条件,所以这导致每次渲染都会先渲染整个列表。
  • 解决方法可以把v-if往上一层容器元素放,如果判断条件是列表渲染的内容,则应该用计算属性过滤下要循环的数组。
    在源码compiler/codegen/index.js中,只要碰到一个元素,就会执行genElement函数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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) //for在前面
    } else if (el.if && !el.ifProcessed) {
    return genIf(el, state) //if在后面
    } 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 {

key的作用

  • key的作用为更高效的更新虚拟DOM。
  • vue在patch的过程中,判断两个节点是否是相同节点,key是一个必要条件,如果不定义key,vue会认为两个节点是相同节点,会强制更新,使得整个patch过程比较低效,影响性能。
  • 实际渲染列表必须设置key,不要用数组下标,不然如果对这个数组进行元素删除,会串位。
  • 从源码中可以知道,vue判断两个节点是否相同时主要判断两者的key和元素类型等,因此如果不设置key,它的值就是undefined,则可能永远认为这是两个相同节点,只能去做更新操作,这造成了大量的dom更新操作,明显是不可取的。(问:两个节点相同需要更新,还是不同需要更新?)
  • patch的过程(patch就是diff),假设在ABCDE的BC中间插入一个F,如果没有定义key,则会做5次更新,1次新增操作,如果定义了key,则会头尾比较(如下),只需要执行一次新增操作。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 第1次循环patch A
A B C D E
A B F C D E
// 第2次循环patch B
B C D E
B F C D E
// 第3次循环patch E
C D E
F C D E
// 第4次循环patch D
C D
F C D
// 第5次循环patch C
C
F C
// oldCh全部处理结束,newCh中剩下的F,创建F并插入到C前面

在源码中,src\core\vdom\patch.js - sameVnode()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function sameVnode (a, b) {
return (
a.key === b.key && ( //先判断key,然后判断标签类别
(
a.tag === b.tag &&
a.isComment === b.isComment &&
isDef(a.data) === isDef(b.data) &&
sameInputType(a, b)
) || (
isTrue(a.isAsyncPlaceholder) &&
a.asyncFactory === b.asyncFactory &&
isUndef(b.asyncFactory.error)
)
)
)
}

vue在对比两组节点时用的首尾猜测,没有用双循环,节省很多性能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
if (isUndef(oldStartVnode)) {
oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left
} else if (isUndef(oldEndVnode)) {
oldEndVnode = oldCh[--oldEndIdx]
} else if (sameVnode(oldStartVnode, newStartVnode)) {
patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
oldStartVnode = oldCh[++oldStartIdx]
newStartVnode = newCh[++newStartIdx]
} else if (sameVnode(oldEndVnode, newEndVnode)) {
patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)
oldEndVnode = oldCh[--oldEndIdx]
newEndVnode = newCh[--newEndIdx]
} else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)
canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))
oldStartVnode = oldCh[++oldStartIdx]
newEndVnode = newCh[--newEndIdx]
} else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
oldEndVnode = oldCh[--oldEndIdx]
newStartVnode = newCh[++newStartIdx]
} else {

双向绑定以及它的实现原理

  • 双向绑定是指令v-model,v-model是语法糖,默认情况下相当于:value和@input
  • 使用v-model可以减少大量繁琐的事件处理代码
  • v-model通常在原生表单里使用,在定义组件上如果要使用它需要在组件内绑定value并处理输入事件

diff算法

  • diff算法在vue里叫做patch,通过新旧虚拟DOM对比,将变化的地方转换为DOM操作
  • 在vue2里,每个组件只有一个watcher,所以要精确找到变化发生的地方,所以要patch
  • 组件数据变化时,对应的watcher会通知更新并执行更新函数,它会执行渲染函数获取全新虚拟DOM:newVnode,此时就会执行patch对比上次渲染的oldVnode,和新的渲染结果newVnode。
  • patch过程遵循深度优先、同层比较的策略,两个节点之间比较时,如果它们拥有子节点,会先比较子节点;比较两组子节点时,会假设头尾节点可能相同先做尝试,没有找到相同节点后才按照通用方式遍历查找;查找结束再按情况处理剩下的节点;借助key通常可以非常精确找到相同节点,因此整个patch过程非常高效。

vue中组件之间的通信方式?

  • 父子组件:props/ $emit / $on / ,$parent / $children ,$attrs / $listeners
  • 跨层级:provide / inject ,eventbus,vuex,$root

简单说已说你对vuex理解

  • Vuex 是已个专为 Vue.js 应⽤程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
  • 我在使用vuex过程中有如下理解:先先是对核心概念的理解和运用,将全局状态放入state对象中,它本身一棵状态树,组件中使用store实例的state访问这些状态;然后有配套的mutation方法修改这些状态,并且只能用mutation修改状态,在组件中调用commit方法提交mutation;如果应用中有异步操作或者复杂逻辑组合,我们需要编写action,执⾏结束如果有状态修改仍然需要提交mutation,组件中调用这些action使用dispatch方法派发。最后是模块化,通过modules选项组织拆分出去的各个子模块,在访问状态时注意添加子模块的名称,如果子模块有设置namespace,那么在提交mutation和派发action时还需要额外的命名空间前缀。
  • vuex在实现单项数据流时需要做到数据的响应式,通过源码的学习发现是借用了vue的数据响应化特性实现的,它会利用Vue将state作为data对其进⾏响应化处理,从而使得这些状态发生变化时,能够导致组件重新渲染。