搜尋
首頁web前端Vue.js深入理解vue2中的VNode和diff演算法

深入理解vue2中的VNode和diff演算法

Nov 17, 2022 pm 08:56 PM
vuevue.jsdiff演算法vnode

深入理解vue2中的VNode和diff演算法

虛擬domdiff演算法是vue學習過程中的一個難點,也是面試中必須掌握的一個知識點。這兩者相輔相成,是vue框架的核心。今天我們再來總結下vue2中的虛擬domdiff演算法。 (學習影片分享:vue影片教學

什麼是VNode

我們知道,render function 會被轉化成VNodeVNode 其實就是一棵以 JavaScript 物件作為基礎的樹,用物件屬性來描述節點,其實它只是一層真實 DOM 的抽象。最終可以透過一系列操作使這棵樹映射到真實環境。

例如有如下template

<template>
  <span class="demo" v-show="isShow"> This is a span. </span> 
</template>

它換成VNode 以後大概就是下面這個樣子

{
  tag: "span",
  data: {
    /* 指令集合数组 */
    directives: [
      {
        /* v-show指令 */
        rawName: "v-show",
        expression: "isShow",
        name: "show",
        value: true,
      },
    ],
    /* 静态class */
    staticClass: "demo",
  },
  text: undefined,
  children: [
    /* 子节点是一个文本VNode节点 */
    {
      tag: undefined,
      data: undefined,
      text: "This is a span.",
      children: undefined,
    },
  ],
};

總的來說,VNode 就是一個JavaScript 物件。這個JavaScript 物件能完整地表示出真實DOM

為什麼vue要使用VNode

筆者認為有兩點原因

  • 由於Virtual DOM#是以JavaScript 物件為基礎而不依賴真實平台環境,所以使它具有了跨平台的能力,比如說瀏覽器平台、Weex、Node 等。

  • 減少操作DOM,任何頁面的變化,都只使用VNode進行操作對比,只需要在最後一次進行掛載更新DOM,避免了頻繁操作DOM,減少了瀏覽器的回流和重繪從而提高頁面效能

diff演算法

下面我們來看看元件更新所涉及的diff演算法

前面我們講依賴收集的時候有說到,渲染watcher傳遞給Watcherget方法其實是updateComponent方法。

updateComponent = () => {
  vm._update(vm._render(), hydrating)
}

new Watcher(vm, updateComponent, noop, {
  before () {
    if (vm._isMounted) {
      callHook(vm, &#39;beforeUpdate&#39;)
    }
  }
}, true /* isRenderWatcher */)

所以元件在響應式資料變更的時候會再次觸發方法,接下來我們來詳細分析一下updateComponent裡面的_update方法。

_update

_update方法中做了初始渲染和更新的區分,雖然都是呼叫__patch__方法,但是傳遞的參數不一樣。

// src/core/instance/lifecycle.js

Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
  const vm: Component = this
  const prevEl = vm.$el
  const prevVnode = vm._vnode
  vm._vnode = vnode
  // 初次渲染没有 prevVnode,组件更新才会有
  if (!prevVnode) {
    // 初次渲染
    vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
  } else {
    // 更新
    vm.$el = vm.__patch__(prevVnode, vnode)
  }
  
  // ...
}

下面我們再來看看__patch__方法

#__patch__

patch方法接收四個參數,由於初始渲染的時候oldVnodevm.$elnull,所以初始渲染是沒有oldVnode

// src/core/vdom/patch.js

return function patch (oldVnode, vnode, hydrating, removeOnly) {
  // 新节点不存在,只有oldVnode就直接销毁,然后返回
  if (isUndef(vnode)) {
    if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
    return
  }

  let isInitialPatch = false
  const insertedVnodeQueue = []
  // 没有老节点,直接创建,也就是初始渲染
  if (isUndef(oldVnode)) {
    isInitialPatch = true
    createElm(vnode, insertedVnodeQueue)
  } else {
    const isRealElement = isDef(oldVnode.nodeType)
    // 不是真实dom,并且是相同节点走patch
    if (!isRealElement && sameVnode(oldVnode, vnode)) {
      // 这里才会涉及到diff算法
      patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly)
    } else {
      if (isRealElement) {
        // ...
      }

      // replacing existing element
      const oldElm = oldVnode.elm
      const parentElm = nodeOps.parentNode(oldElm)

      // 1.创建一个新节点
      createElm(
        vnode,
        insertedVnodeQueue,
        // extremely rare edge case: do not insert if old element is in a
        // leaving transition. Only happens when combining transition +
        // keep-alive + HOCs. (#4590)
        oldElm._leaveCb ? null : parentElm,
        nodeOps.nextSibling(oldElm)
      )

      // 2.更新父节点占位符
      if (isDef(vnode.parent)) {
        let ancestor = vnode.parent
        const patchable = isPatchable(vnode)
        while (ancestor) {
          for (let i = 0; i < cbs.destroy.length; ++i) {
            cbs.destroy[i](ancestor)
          }
          ancestor.elm = vnode.elm
          if (patchable) {
            for (let i = 0; i < cbs.create.length; ++i) {
              cbs.create[i](emptyNode, ancestor)
            }
            
            const insert = ancestor.data.hook.insert
            if (insert.merged) {
              // start at index 1 to avoid re-invoking component mounted hook
              for (let i = 1; i < insert.fns.length; i++) {
                insert.fns[i]()
              }
            }
          } else {
            registerRef(ancestor)
          }
          ancestor = ancestor.parent
        }
      }

      // 3.删除老节点
      if (isDef(parentElm)) {
        removeVnodes([oldVnode], 0, 0)
      } else if (isDef(oldVnode.tag)) {
        invokeDestroyHook(oldVnode)
      }
    }
  }
   
   //触发插入钩子
  invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
  return vnode.elm
}

patch方法大概流程如下:

  • 沒有新節點只有舊節點直接刪除舊節點。

  • 只有新節點沒有舊節點直接新增節點。

  • 既有新節點又有舊節點則判斷是不是相同節點,相同則進入pathVnodepatchVnode我們後面會重點分析。

  • 既有新節點又有老節點則判斷是不是相同節點,不相同則直接刪除舊節點新增節點。

我們再來看看它是怎麼判斷是同一個節點的。

// src/core/vdom/patch.js

function sameVnode (a, b) {
  return (
    a.key === b.key &&
    a.asyncFactory === b.asyncFactory && (
      (
        a.tag === b.tag &&
        a.isComment === b.isComment &&
        isDef(a.data) === isDef(b.data) &&
        sameInputType(a, b)
      ) || (
        isTrue(a.isAsyncPlaceholder) &&
        isUndef(b.asyncFactory.error)
      )
    )
  )
}

function sameInputType (a, b) {
  if (a.tag !== &#39;input&#39;) return true
  let i
  const typeA = isDef(i = a.data) && isDef(i = i.attrs) && i.type
  const typeB = isDef(i = b.data) && isDef(i = i.attrs) && i.type
  return typeA === typeB || isTextInputType(typeA) && isTextInputType(typeB)
}

判斷兩個VNode節點是否為同一個節點,需要同時滿足下列條件

  • ##key相同

  • 都有非同步元件工廠函數

  • tag(目前節點的標籤名稱)相同

  • #isComment是否同為註解節點

  • #是否

    data(目前節點對應的對象,包含了具體的一些資料訊息,是一個VNodeData型別)

  • 當標籤是

    的時候,type必須相同

#當兩個

VNodetag、key、isComment都相同,並且同時定義或未定義data的時候,且如果標籤為input則type必須相同。這時候這兩個VNode則算sameVnode,可以直接進行patchVnode操作。

patchVnode

下面我們再來詳細分析下

patchVnode方法。

// src/core/vdom/patch.js

function patchVnode (
  oldVnode,
  vnode,
  insertedVnodeQueue,
  ownerArray,
  index,
  removeOnly
) {
  // 两个vnode相同则直接返回
  if (oldVnode === vnode) {
    return
  }

  if (isDef(vnode.elm) && isDef(ownerArray)) {
    // clone reused vnode
    vnode = ownerArray[index] = cloneVNode(vnode)
  }

  const elm = vnode.elm = oldVnode.elm

  if (isTrue(oldVnode.isAsyncPlaceholder)) {
    if (isDef(vnode.asyncFactory.resolved)) {
      hydrate(oldVnode.elm, vnode, insertedVnodeQueue)
    } else {
      vnode.isAsyncPlaceholder = true
    }
    return
  }

  /*
    如果新旧VNode都是静态的,同时它们的key相同(代表同一节点),
    并且新的VNode是clone或者是标记了once(标记v-once属性,只渲染一次),
    那么只需要替换componentInstance即可。
  */
  if (isTrue(vnode.isStatic) &&
    isTrue(oldVnode.isStatic) &&
    vnode.key === oldVnode.key &&
    (isTrue(vnode.isCloned) || isTrue(vnode.isOnce))
  ) {
    vnode.componentInstance = oldVnode.componentInstance
    return
  }

  let i
  const data = vnode.data
  /*调用prepatch钩子*/
  if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {
    i(oldVnode, vnode)
  }

  // 获取新老虚拟节点的子节点
  const oldCh = oldVnode.children
  const ch = vnode.children
  if (isDef(data) && isPatchable(vnode)) {
    for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode)
    if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode)
  }
  
  // 新节点不是文本节点
  if (isUndef(vnode.text)) {
    /*新老节点均有children子节点,则对子节点进行diff操作,调用updateChildren*/
    if (isDef(oldCh) && isDef(ch)) {
      if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)
    /*如果只有新节点有子节点,先清空elm文本内容,然后为当前DOM节点加入子节点。*/
    } else if (isDef(ch)) {
      if (process.env.NODE_ENV !== &#39;production&#39;) {
        checkDuplicateKeys(ch)
      }
      if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, &#39;&#39;)
      addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
    /*如果只有老节点有子节点,则移除elm所有子节点*/
    } else if (isDef(oldCh)) {
      removeVnodes(oldCh, 0, oldCh.length - 1)
    /*当新老节点都无子节点的时候,因为这个逻辑中新节点text不存在,所以直接去除ele的文本*/
    } else if (isDef(oldVnode.text)) {
      nodeOps.setTextContent(elm, &#39;&#39;)
    }
  // 新节点是文本节点,如果文本不一样就设置新的文本  
  } else if (oldVnode.text !== vnode.text) {
    nodeOps.setTextContent(elm, vnode.text)
  }
  /*调用postpatch钩子*/
  if (isDef(data)) {
    if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode)
  }
}

patchVnode方法大概流程如下:

#1.新舊節點相同就直接回傳。

2.如果新旧VNode都是静态的,同时它们的key相同(代表同一节点),并且新的VNode是clone或者是标记了once(标记v-once属性,只渲染一次),那么只需要替换componentInstance即可。

3.新节点不是文本节点,新老节点均有children子节点,则对子节点进行diff操作,调用updateChildren,这个updateChildrendiff算法的核心,后面我们会重点说。

4.新节点不是文本节点,如果老节点没有子节点而新节点存在子节点,先清空老节点DOM的文本内容,然后为当前DOM节点加入子节点。

5.新节点不是文本节点,当新节点没有子节点而老节点有子节点的时候,则移除该DOM节点的所有子节点。

6.新节点不是文本节点,并且新老节点都无子节点的时候,只需要将老节点文本清空。

7.新节点是文本节点,并且新老节点文本不一样,则进行文本的替换。

updateChildren(diff算法核心)

updateChildrendiff算法的核心,下面我们来重点分析。

深入理解vue2中的VNode和diff演算法

这两张图代表旧的VNode与新VNode进行patch的过程,他们只是在同层级的VNode之间进行比较得到变化(相同颜色的方块代表互相进行比较的VNode节点),然后修改变化的视图,所以十分高效。所以Diff算法是:深度优先算法。 时间复杂度:O(n)

function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
  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, vnodeToMove, refElm

  const canMove = !removeOnly

  if (process.env.NODE_ENV !== &#39;production&#39;) {
    checkDuplicateKeys(newCh)
  }

  while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
    if (isUndef(oldStartVnode)) {
      oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left
    } else if (isUndef(oldEndVnode)) {
      oldEndVnode = oldCh[--oldEndIdx]
    // 老 VNode 节点的头部与新 VNode 节点的头部是相同的 VNode 节点,直接进行 patchVnode,同时 oldStartIdx 与 newStartIdx 向后移动一位。
    } else if (sameVnode(oldStartVnode, newStartVnode)) {
      patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
      oldStartVnode = oldCh[++oldStartIdx]
      newStartVnode = newCh[++newStartIdx]
    // 两个 VNode 的结尾是相同的 VNode,同样进行 patchVnode 操作。并将 oldEndVnode 与 newEndVnode 向前移动一位。
    } else if (sameVnode(oldEndVnode, newEndVnode)) {
      patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)
      oldEndVnode = oldCh[--oldEndIdx]
      newEndVnode = newCh[--newEndIdx]
    // oldStartVnode 与 newEndVnode 符合 sameVnode 的时候,
    // 将 oldStartVnode.elm 这个节点直接移动到 oldEndVnode.elm 这个节点的后面即可。
    // 然后 oldStartIdx 向后移动一位,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]
    // oldEndVnode 与 newStartVnode 符合 sameVnode 时,
    // 将 oldEndVnode.elm 插入到 oldStartVnode.elm 前面。
    // oldEndIdx 向前移动一位,newStartIdx 向后移动一位。
    } 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 {
      // createKeyToOldIdx 的作用是产生 key 与 index 索引对应的一个 map 表
      if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
      idxInOld = isDef(newStartVnode.key)
        ? oldKeyToIdx[newStartVnode.key]
        : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
      // 如果没有找到相同的节点,则通过 createElm 创建一个新节点,并将 newStartIdx 向后移动一位。
      if (isUndef(idxInOld)) { // New element
        createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
      } else {
        vnodeToMove = oldCh[idxInOld]
        // 如果找到了节点,同时它符合 sameVnode,则将这两个节点进行 patchVnode,将该位置的老节点赋值 undefined
        // 同时将 newStartVnode.elm 插入到 oldStartVnode.elm 的前面
        if (sameVnode(vnodeToMove, newStartVnode)) {
          patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
          oldCh[idxInOld] = undefined
          canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
        } else {
          // 如果不符合 sameVnode,只能创建一个新节点插入到 parentElm 的子节点中,newStartIdx 往后移动一位。
          createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
        }
      }
      newStartVnode = newCh[++newStartIdx]
    }
  }
  // 当 while 循环结束以后,如果 oldStartIdx > oldEndIdx,说明老节点比对完了,但是新节点还有多的,
  // 需要将新节点插入到真实 DOM 中去,调用 addVnodes 将这些节点插入即可。
  if (oldStartIdx > oldEndIdx) {
    refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm
    addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
  // 如果满足 newStartIdx > newEndIdx 条件,说明新节点比对完了,老节点还有多,
  // 将这些无用的老节点通过 removeVnodes 批量删除即可。
  } else if (newStartIdx > newEndIdx) {
    removeVnodes(oldCh, oldStartIdx, oldEndIdx)
  }
}

vue2diff算法采用的是双端比较,所谓双端比较就是新列表旧列表两个列表的头与尾互相对比,在对比的过程中指针会逐渐向内靠拢,直到某一个列表的节点全部遍历过,对比停止。

首尾对比的四种情况

我们首先来看看首尾对比的四种情况。

  • 使用旧列表的头一个节点oldStartNode新列表的头一个节点newStartNode对比

  • 使用旧列表的最后一个节点oldEndNode新列表的最后一个节点newEndNode对比

  • 使用旧列表的头一个节点oldStartNode新列表的最后一个节点newEndNode对比

  • 使用旧列表的最后一个节点oldEndNode新列表的头一个节点newStartNode对比

首先是 oldStartVnode 与 newStartVnode 符合 sameVnode 时,说明老 VNode 节点的头部与新 VNode 节点的头部是相同的 VNode 节点,直接进行 patchVnode,同时 oldStartIdx 与 newStartIdx 向后移动一位。

深入理解vue2中的VNode和diff演算法

其次是 oldEndVnode 与 newEndVnode 符合 sameVnode,也就是两个 VNode 的结尾是相同的 VNode,同样进行 patchVnode 操作并将 oldEndVnode 与 newEndVnode 向前移动一位。

深入理解vue2中的VNode和diff演算法

接下来是两种交叉的情况。

先是 oldStartVnode 与 newEndVnode 符合 sameVnode 的时候,也就是老 VNode 节点的头部与新 VNode 节点的尾部是同一节点的时候,将 oldStartVnode.elm 这个节点直接移动到 oldEndVnode.elm 这个节点的后面即可。然后 oldStartIdx 向后移动一位,newEndIdx 向前移动一位。

深入理解vue2中的VNode和diff演算法

同理,oldEndVnode 与 newStartVnode 符合 sameVnode 时,也就是老 VNode 节点的尾部与新 VNode 节点的头部是同一节点的时候,将 oldEndVnode.elm 插入到 oldStartVnode.elm 前面。同样的,oldEndIdx 向前移动一位,newStartIdx 向后移动一位。

深入理解vue2中的VNode和diff演算法

最後是以上情況都不符合的時候,這種情況怎麼處理呢?

找出對比

那就是找出對比。

首先透過createKeyToOldIdx方法產生oldVnodekey 與 index 索引對應的一個map # 表。

然後我們根據newStartVnode.key,可以快速地從 oldKeyToIdxcreateKeyToOldIdx 的回傳值)取得相同key 的節點的索引 idxInOld,然後找到相同的節點。

這裡又分三種情況

  • 如果沒有找到相同的節點,則透過 createElm 建立一個新節點,並將 # newStartIdx 向後移動一位。

  • 如果找到了節點,同時它符合 sameVnode,則將這兩個節點進行 patchVnode,將該位置的舊節點賦值undefined(之後如果還有新節點與該節點key相同可以偵測出來提示已有重複的key ),同時將 newStartVnode.elm 插入 oldStartVnode.elm 的前面。同理,newStartIdx 往後移動一位。

深入理解vue2中的VNode和diff演算法

  • 如果不符合 sameVnode,只能建立一個新節點插入到 parentElm  的子節點中,newStartIdx 往後移動一位。

深入理解vue2中的VNode和diff演算法

新增、刪除節點

最後一步就很容易啦,當 while 循環結束以後,如果 oldStartIdx > oldEndIdx,表示舊節點比對完了,但是新節點還有多的,需要將新節點插入到真實DOM 中去,調用 addVnodes 將這些節點插入即可。

深入理解vue2中的VNode和diff演算法

同理,如果滿足 newStartIdx > newEndIdx 條件,說明新節點比對完了,老節點還有多,將這些無用的老節點透過 removeVnodes 批次刪除即可。

深入理解vue2中的VNode和diff演算法

總結

Diff演算法是一種對比演算法。比較兩者是舊虛擬DOM和新虛擬DOM,對比出是哪個虛擬節點更改了,找出這個虛擬節點,並且只更新這個虛擬節點所對應的真實節點,而不用更新其他資料沒發生改變的節點,實現精準地更新真實DOM,進而提高效率和效能

精準主要體現在,diff 演算法首先就是找到可重複使用的節點,然後移動到正確的位置。當元素沒有找到的話再來建立新節點。

擴充

vue中為什麼需要使用key,它的作用是什麼?

keyVuevnode 的唯一標記,透過這個keydiff 操作可以更準確、更快速。

  1. 更準確:因為帶key 就不是就地復用了,在sameNode 函數a.key === b.key 對比中可以避免就地復用的情況。所以會更加準確。
  2. 更快:利用 key 的唯一性產生 map 物件來取得對應節點,比遍歷方式更快。

為什麼不推薦使用index作為key

當我們的清單只涉及到展示,不涉及排序、刪除、新增的時候使用index作為key是沒什麼問題的。因為此時的index在每個元素上是唯一的。

但是如果涉及排序、刪除、新增的時候就不能再使用index作為key了,因為每個元素key不再唯一了。不唯一的key,對diff演算法沒有任何幫助,寫和沒寫是一樣的。

(學習影片分享:web前端開發程式設計基礎影片

以上是深入理解vue2中的VNode和diff演算法的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文轉載於:掘金社区。如有侵權,請聯絡admin@php.cn刪除
React與Vue:Netflix使用哪個框架?React與Vue:Netflix使用哪個框架?Apr 14, 2025 am 12:19 AM

NetflixusesAcustomFrameworkcalled“ Gibbon” BuiltonReact,notReactorVuedIrectly.1)TeamSperience:selectBasedonFamiliarity.2)ProjectComplexity:vueforsimplerprojects:reactforforforproproject,reactforforforcompleplexones.3)cocatizationneedneeds:reactoffipicatizationneedneedneedneedneedneeds:reactoffersizationneedneedneedneedneeds:reactoffersizatization needefersmoreflexibleise.4)

框架的選擇:是什麼推動了Netflix的決定?框架的選擇:是什麼推動了Netflix的決定?Apr 13, 2025 am 12:05 AM

Netflix在框架選擇上主要考慮性能、可擴展性、開發效率、生態系統、技術債務和維護成本。 1.性能與可擴展性:選擇Java和SpringBoot以高效處理海量數據和高並發請求。 2.開發效率與生態系統:使用React提升前端開發效率,利用其豐富的生態系統。 3.技術債務與維護成本:選擇Node.js構建微服務,降低維護成本和技術債務。

反應,vue和Netflix前端的未來反應,vue和Netflix前端的未來Apr 12, 2025 am 12:12 AM

Netflix主要使用React作為前端框架,輔以Vue用於特定功能。 1)React的組件化和虛擬DOM提升了Netflix應用的性能和開發效率。 2)Vue在Netflix的內部工具和小型項目中應用,其靈活性和易用性是關鍵。

前端中的vue.js:現實世界的應用程序和示例前端中的vue.js:現實世界的應用程序和示例Apr 11, 2025 am 12:12 AM

Vue.js是一種漸進式JavaScript框架,適用於構建複雜的用戶界面。 1)其核心概念包括響應式數據、組件化和虛擬DOM。 2)實際應用中,可以通過構建Todo應用和集成VueRouter來展示其功能。 3)調試時,建議使用VueDevtools和console.log。 4)性能優化可通過v-if/v-show、列表渲染優化和異步加載組件等實現。

vue.js和React:了解關鍵差異vue.js和React:了解關鍵差異Apr 10, 2025 am 09:26 AM

Vue.js適合小型到中型項目,而React更適用於大型、複雜應用。 1.Vue.js的響應式系統通過依賴追踪自動更新DOM,易於管理數據變化。 2.React採用單向數據流,數據從父組件流向子組件,提供明確的數據流向和易於調試的結構。

vue.js vs.反應:特定於項目的考慮因素vue.js vs.反應:特定於項目的考慮因素Apr 09, 2025 am 12:01 AM

Vue.js適合中小型項目和快速迭代,React適用於大型複雜應用。 1)Vue.js易於上手,適用於團隊經驗不足或項目規模較小的情況。 2)React的生態系統更豐富,適合有高性能需求和復雜功能需求的項目。

vue怎麼a標籤跳轉vue怎麼a標籤跳轉Apr 08, 2025 am 09:24 AM

實現 Vue 中 a 標籤跳轉的方法包括:HTML 模板中使用 a 標籤指定 href 屬性。使用 Vue 路由的 router-link 組件。使用 JavaScript 的 this.$router.push() 方法。可通過 query 參數傳遞參數,並在 router 選項中配置路由以進行動態跳轉。

vue怎麼實現組件跳轉vue怎麼實現組件跳轉Apr 08, 2025 am 09:21 AM

Vue 中實現組件跳轉有以下方法:使用 router-link 和 <router-view> 組件進行超鏈接跳轉,指定 :to 屬性為目標路徑。直接使用 <router-view> 組件顯示當前路由渲染的組件。使用 router.push() 和 router.replace() 方法進行程序化導航,前者保存歷史記錄,後者替換當前路由不留記錄。

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
3 週前By尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解鎖Myrise中的所有內容
4 週前By尊渡假赌尊渡假赌尊渡假赌

熱工具

VSCode Windows 64位元 下載

VSCode Windows 64位元 下載

微軟推出的免費、功能強大的一款IDE編輯器

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

WebStorm Mac版

WebStorm Mac版

好用的JavaScript開發工具

Safe Exam Browser

Safe Exam Browser

Safe Exam Browser是一個安全的瀏覽器環境,安全地進行線上考試。該軟體將任何電腦變成一個安全的工作站。它控制對任何實用工具的訪問,並防止學生使用未經授權的資源。

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境