Maison  >  Article  >  interface Web  >  Introduction au principe de comparaison de dom virtuel dans Vue (exemple d'explication)

Introduction au principe de comparaison de dom virtuel dans Vue (exemple d'explication)

不言
不言avant
2019-02-20 13:42:554014parcourir

Le contenu de cet article est une introduction au principe de comparaison du dom virtuel dans Vue (explication avec exemples). Il a une certaine valeur de référence. Les amis dans le besoin peuvent s'y référer.

Parlons d'abord de la raison pour laquelle il existe une étape de comparaison du DOM virtuel. Nous savons que Vue est une vue basée sur les données (les changements dans les données entraîneront des changements dans la vue), mais vous constatez que lorsque certaines données sont sélectionnées. changements, la vue est partielle Comment trouver avec précision la vue correspondant aux données et la mettre à jour en actualisant au lieu de restituer l'ensemble ? Ensuite, vous devez obtenir la structure DOM avant et après la modification des données, trouver les différences et la mettre à jour !

Le dom virtuel est essentiellement un objet simple extrait du dom réel. Tout comme un simple p contient plus de 200 attributs, mais le seul qui est vraiment nécessaire peut être tagName, donc une opération directe sur le dom réel affectera grandement les performances !

Introduction au principe de comparaison de dom virtuel dans Vue (exemple dexplication)

Le nœud virtuel simplifié (vnode) contient grossièrement les attributs suivants :

{
  tag: 'p',       // 标签名
  data: {},         // 属性数据,包括class、style、event、props、attrs等
  children: [],     // 子节点数组,也是vnode结构
  text: undefined,  // 文本
  elm: undefined,   // 真实dom
  key: undefined    // 节点标识
}

La comparaison du dom virtuel consiste à trouver le nouveau nœud (vnode) et l’ancien nœud (oldVnode), puis corrigez la différence. Le processus général est le suivant

Introduction au principe de comparaison de dom virtuel dans Vue (exemple dexplication)

L'ensemble du processus est relativement simple si l'ancien et le nouveau nœud ne sont pas similaires, créez un DOM directement basé sur le nouveau nœud s'ils le sont. similaire, comparez d'abord les données, y compris la classe et le style. , l'événement, les accessoires, les attributs, etc., s'il y a des différences, appelez la fonction de mise à jour correspondante, puis comparez les nœuds enfants. La comparaison des nœuds enfants utilise le . algorithme diff, qui devrait être l'objet et la difficulté de cet article Bar.

Il convient de noter qu'au cours du Children Compare processus, si des childVnode similaires sont trouvés, ils récursivement entreront dans un nouveau processus de correctif.

Analyse du code source

Cette fois, l'analyse du code source est écrite de manière plus concise. Si j'écris trop, je ne veux pas le lire (┬_┬)

Début

Regardons d'abord la fonction patch() : La fonction

function patch (oldVnode, vnode) {
  var elm, parent;
  if (sameVnode(oldVnode, vnode)) {
    // 相似就去打补丁(增删改)
    patchVnode(oldVnode, vnode);
  } else {
    // 不相似就整个覆盖
    elm = oldVnode.elm;
    parent = api.parentNode(elm);
    createElm(vnode);
    if (parent !== null) {
      api.insertBefore(parent, vnode.elm, api.nextSibling(elm));
      removeVnodes(parent, [oldVnode], 0, 0);
    }
  }
  return vnode.elm;
}

patch() reçoit deux paramètres, l'ancien et le nouveau vnode. Il y a une grande différence entre les deux paramètres. transmis : le elm de oldVnode pointe vers le vrai dom, et le elm de vnode n'est pas défini... Mais après avoir passé la méthode patch(), le elm de vnode pointera également vers ce vrai dom (mis à jour).

La méthode sameVnode() pour déterminer si l'ancien et le nouveau vnodes sont similaires est très simple, qui consiste à comparer si tag et key sont cohérents.

function sameVnode (a, b) {
  return a.key === b.key && a.tag === b.tag;
}

Patch

Pour l'incohérence entre les anciens et les nouveaux vnodes est très simple, c'est-à-dire créer un vrai dom basé sur le vnode et insérer le document DOM au lieu du elm dans le oldVnode .

Le traitement de la cohérence entre les anciens et les nouveaux vnodes est le correctif que nous avons souvent mentionné auparavant. Qu’est-ce qu’un patch exactement ? Il suffit de regarder la méthode patchVnode() :

function patchVnode (oldVnode, vnode) {
  // 新节点引用旧节点的dom
  let elm = vnode.elm = oldVnode.elm;
  const oldCh = oldVnode.children;
  const ch = vnode.children;

  // 调用update钩子
  if (vnode.data) {
    updateAttrs(oldVnode, vnode);
    updateClass(oldVnode, vnode);
    updateEventListeners(oldVnode, vnode);
    updateProps(oldVnode, vnode);
    updateStyle(oldVnode, vnode);
  }

  // 判断是否为文本节点
  if (vnode.text == undefined) {
    if (isDef(oldCh) && isDef(ch)) {
      if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue)
    } else if (isDef(ch)) {
      if (isDef(oldVnode.text)) api.setTextContent(elm, '')
      addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
    } else if (isDef(oldCh)) {
      removeVnodes(elm, oldCh, 0, oldCh.length - 1)
    } else if (isDef(oldVnode.text)) {
      api.setTextContent(elm, '')
    }
  } else if (oldVnode.text !== vnode.text) {
    api.setTextContent(elm, vnode.text)
  }
}

Patching appelle en fait diverses fonctions updateXXX() pour mettre à jour divers attributs du domaine réel. Chaque fonction de mise à jour est similaire, prenons updateAttrs() comme exemple :

function updateAttrs (oldVnode, vnode) {
  let key, cur, old
  const elm = vnode.elm
  const oldAttrs = oldVnode.data.attrs || {}
  const attrs = vnode.data.attrs || {}

  // 更新/添加属性
  for (key in attrs) {
    cur = attrs[key]
    old = oldAttrs[key]
    if (old !== cur) {
      if (booleanAttrsDict[key] && cur == null) {
        elm.removeAttribute(key)
      } else {
        elm.setAttribute(key, cur)
      }
    }
  }
  // 删除新节点不存在的属性
  for (key in oldAttrs) {
    if (!(key in attrs)) {
      elm.removeAttribute(key)
    }
  }
}

L'idée générale de la fonction de mise à jour de l'attribut (Attribute) est :

  • Parcourez l'attribut vnode, et s'il est différent de oldVnode, appelez setAttribute() pour modifier

  • Parcourez l'attribut oldVnode, et s'il n'est pas dans l'attribut vnode ; , appelez removeAttribute() pour supprimer.

Vous constaterez qu'il y a un booleanAttrsDict[key] jugement à l'intérieur, qui est utilisé pour juger s'il se trouve dans le dictionnaire d'attributs de type booléen.

['allowfullscreen', 'async', 'autofocus', 'autoplay', 'checked', 'compact', 'controls', 'declare', ...]

par exemple : <video autoplay></video>, si vous souhaitez désactiver la lecture automatique, vous devez supprimer cet attribut.

Une fois toutes les données comparées, il est temps de comparer les nœuds enfants. Déterminez d'abord si le vnode actuel est un nœud de texte. S'il s'agit d'un nœud de texte, il n'est pas nécessaire de considérer la comparaison des nœuds enfants ; s'il s'agit d'un nœud d'élément, il doit être pris en compte dans trois situations :

  • Les anciens et les nouveaux nœuds ont des enfants, puis entrez la comparaison des nœuds enfants (algorithme diff)

  • Le nouveau nœud a des enfants, mais l'ancien. le nœud n'en a pas, puis créez un nœud dom dans une boucle ;

  • Le nouveau nœud n'a pas d'enfants, mais l'ancien nœud en a, puis supprimez le nœud dom dans une boucle.

Ces deux derniers cas sont relativement simples. Nous analysons directement le premier cas, la comparaison des nœuds enfants.

algorithme diff

Il y a beaucoup de code dans cette partie de la comparaison des sous-nœuds. Parlons d'abord du principe, puis publions le code. Regardons d'abord une image comparant les nœuds enfants :

Introduction au principe de comparaison de dom virtuel dans Vue (exemple dexplication)

Les oldCh et newCh sur la figure représentent respectivement l'ancien et le nouveau tableau de nœuds enfants. Ils ont leurs propres pointeurs de tête et de queue oldStartIdx, oldEndIdx, newStartIdx, newEndIdx. stocké dans le tableau est vnode , pour une compréhension facile, nous utilisons à la place a, b, c, d, etc., qui représentent des objets vnode de différents types d'étiquettes (p, span, p).

La comparaison des nœuds enfants est essentiellement une comparaison en boucle des nœuds de tête et de queue. Le signe de la fin de la boucle est : l'ancien tableau de nœuds enfants ou le nouveau tableau de nœuds enfants a été parcouru (c'est-à-dire oldStartIdx > oldEndIdx || newStartIdx > newEndIdx). Jetez un coup d'œil au processus du cycle  :

  • La première étape Comparez face à face . S'ils sont similaires, les pointeurs de l'ancienne tête et de la nouvelle tête reculent (c'est-à-dire oldStartIdx++ && newStartIdx++), le vrai dom reste inchangé, et le cycle suivant est entré s'ils ne sont pas similaires, la deuxième étape ; est entré.

  • Étape 2 Comparez queue à queue. S'ils sont similaires, l'ancien et le nouveau pointeur de queue avancent (c'est-à-dire oldEndIdx-- && newEndIdx--), le vrai DOM reste inchangé, et s'ils ne sont pas similaires, la troisième étape est entrée ;

  • Étape 3 Comparez tête-bêche. S'ils sont similaires, l'ancien pointeur de tête recule et le nouveau pointeur de queue avance (c'est-à-dire oldStartIdx++ && newEndIdx-- La tête dans la séquence DOM non confirmée est déplacée vers la fin et entre dans le cycle suivant si c'est le cas). pas similaire, passez à la quatrième étape.

  • Étape 4 Comparez la queue et la tête. S'ils sont similaires, l'ancien pointeur de queue est déplacé vers l'avant, le nouveau pointeur de tête est déplacé vers l'arrière (c'est-à-dire oldEndIdx-- && newStartIdx++), la queue dans la séquence DOM non confirmée est déplacée vers la tête et le cycle suivant est entré ; s'ils ne sont pas similaires, la cinquième étape est entrée.

  • Étape 5 : Si le nœud a une clé et que le même Vnode est trouvé dans l'ancien tableau de nœuds enfants (la balise et la clé sont cohérentes), alors déplacez son dom vers la tête du séquence de dom réel actuelle. Le nouveau pointeur de tête recule (c'est-à-dire newStartIdx++); sinon, le dom correspondant au vnode (vnode[newStartIdx].elm) est inséré dans la tête de la séquence de dom réel actuelle, et le nouveau pointeur de tête recule. (c'est-à-dire newStartIdx++).

Regardons d'abord la situation sans la clé, et mettons une animation pour y voir plus clair !

Introduction au principe de comparaison de dom virtuel dans Vue (exemple dexplication)

Je crois qu'après avoir lu l'image, vous aurez une meilleure compréhension de l'essence de l'algorithme de comparaison. L'ensemble du processus est relativement simple. Dans l'image ci-dessus, un total de 6 cycles ont été renseignés, impliquant chaque situation. Décrivons chacun un par un :

  • La première fois est similaire (les deux a), et le dom ne change pas, les anciens et les nouveaux pointeurs de tête reculent. Une fois le nœud a confirmé, la séquence dom réelle est : a,b,c,d,e,f, et la séquence dom non confirmée est : b,c,d,e,f ;

  • La deuxième fois est similaire (les deux sont f), le DOM ne change pas et les anciens et les nouveaux pointeurs de queue sont avancés. Une fois le nœud f confirmé, la séquence dom réelle est : a,b,c,d,e,f, et la séquence dom non confirmée est : b,c,d,e >), la tête dans la séquence DOM réelle restante actuelle est déplacée vers la queue, l'ancienne le pointeur de tête est déplacé vers l'arrière et le nouveau pointeur de queue est déplacé vers l'avant.

    Une fois le nœud confirmé, la séquence dom réelle est :
  • , et la séquence dom non confirmée est :
  •  >), la queue de la séquence DOM réelle restante actuelle est déplacée vers le début, l'ancienne le pointeur de queue est déplacé vers l'avant et le nouveau pointeur de tête est déplacé vers l'arrière. Une fois le nœud

    confirmé, la séquence dom réelle est : b, et la séquence dom non confirmée est : b ; a,c,d,e,b,fc,d,e

  • La cinquième fois n'est pas similaire, et est directement inséré dans l’en-tête de la séquence Confirm the dom. Une fois le nœud
  • inséré, la séquence dom réelle est :

    , et la séquence dom non confirmée est : e ; ea,e,c,d,b,fc,d

    La sixième fois n'est pas similaire, et il est directement inséré dans l’en-tête Confirmer la séquence dom. Une fois le nœud
  • inséré, la séquence dom réelle est :
  • , et la séquence dom non confirmée est :

     ; deux situations à considérer : ga,e,g,c,d,b,fc,d

    Le nouveau tableau de points d'octets (newCh) est parcouru (
  • ). Ensuite, vous devez supprimer tous les anciens dom redondants (
  • ), qui sont

    dans l'exemple ci-dessus ha,e,g,h,c,d,b,fc,d

    Le nouveau tableau de points d'octets (oldCh) est parcouru (
  • ). Ensuite, vous devez ajouter tous les nouveaux dom supplémentaires (
).

上面说了这么多都是没有key的情况,说添加了:key可以优化v-for的性能,到底是怎么回事呢?因为v-for大部分情况下生成的都是相同tag的标签,如果没有key标识,那么相当于每次头头比较都能成功。你想想如果你往v-for绑定的数组头部push数据,那么整个dom将全部刷新一遍(如果数组每项内容都不一样),那加了key会有什么帮助呢?这边引用一张图:

Introduction au principe de comparaison de dom virtuel dans Vue (exemple dexplication)

key的情况,其实就是多了一步匹配查找的过程。也就是上面循环流程中的第五步,会尝试去旧子节点数组中找到与当前新子节点相似的节点,减少dom的操作!

有兴趣的可以看看代码:

function updateChildren (parentElm, oldCh, newCh) {
  let oldStartIdx = 0
  let newStartIdx = 0
  let oldEndIdx = oldCh.length - 1
  let oldStartVnode = oldCh[0]
  let oldEndVnode = oldCh[oldEndIdx]
  let newEndIdx = newCh.length - 1
  let newStartVnode = newCh[0]
  let newEndVnode = newCh[newEndIdx]
  let oldKeyToIdx, idxInOld, elmToMove, before

  while (oldStartIdx  oldEndIdx) {
    before = isUndef(newCh[newEndIdx+1]) ? null : newCh[newEndIdx + 1].elm
    addVnodes(parentElm, before, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
  } else if (newStartIdx > newEndIdx) {
    removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)
  }
}

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer