成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專(zhuān)欄INFORMATION COLUMN

如何實(shí)現(xiàn) virtual-dom

rickchen / 431人閱讀

摘要:但是實(shí)際開(kāi)發(fā)中,整個(gè)文檔樹(shù)中和標(biāo)簽基本不會(huì)有太大的改動(dòng)。在測(cè)試中,不難發(fā)現(xiàn)其實(shí)已經(jīng)很快了,但是速度會(huì)比較慢,所以這里留下了一個(gè)待優(yōu)化的點(diǎn)就是。

如何實(shí)現(xiàn) virtual-dom 0. 什么是 vnode

相信大部分前端同學(xué)之前早已無(wú)數(shù)次聽(tīng)過(guò)或了解過(guò) vnode(虛擬節(jié)點(diǎn)),那么什么是 vnode? vnode 應(yīng)該是什么樣的?
如果不使用前端框架,我們可能會(huì)寫(xiě)出這樣的頁(yè)面:


  
    
  
  
    

不難發(fā)現(xiàn),整個(gè)文檔樹(shù)的根節(jié)點(diǎn)只有一個(gè) html,然后嵌套各種子標(biāo)簽,如果使用某種數(shù)據(jù)結(jié)構(gòu)來(lái)表示這棵樹(shù),那么它可能是這樣。

{
  tagName: "html",
  children: [
    {
      tagName: "head",
      children: [
        {
          tagName: "title"
        }
      ]
    },

    {
      tagName: "body",
      children: [
        {
          tagName: "div"
        },

        {
          tagName: "script"
        }
      ]
    }
  ]
}

但是實(shí)際開(kāi)發(fā)中,整個(gè)文檔樹(shù)中headscript 標(biāo)簽基本不會(huì)有太大的改動(dòng)。頻繁交互可能改動(dòng)的應(yīng)當(dāng)是 body 里面的除 script 的部分,所以構(gòu)建 虛擬節(jié)點(diǎn)樹(shù) 應(yīng)當(dāng)是整個(gè) HTML 文檔樹(shù)的一個(gè)子樹(shù),而這個(gè)子樹(shù)應(yīng)當(dāng)保持和 HTML 文檔樹(shù)一致的數(shù)據(jù)結(jié)構(gòu)。它可能是這樣。


  
    
  
  
    

這里應(yīng)當(dāng)構(gòu)建的 虛擬節(jié)點(diǎn)樹(shù) 應(yīng)當(dāng)是 div#root 這棵子樹(shù):

{
  tagName: "div",
  children: [
    {
      tagName: "div",
    },
    {
      tagName: "div",
    },
    {
      tagName: "div",
    },
  ]
}

到這里,vnode 的概念應(yīng)當(dāng)很清晰了,vnode 是用來(lái)表示實(shí)際 dom 節(jié)點(diǎn)的一種數(shù)據(jù)結(jié)構(gòu),其結(jié)構(gòu)大概長(zhǎng)這樣。

{
  tagName: "div",
  attrs: {
    class: "header"
  },
  children: []
}

一般,我們可能會(huì)這樣定義 vnode。

// vnode.js
export const vnode = function vnode() {}
1. 從 JSX 到 vnode

使用 React 會(huì)經(jīng)常寫(xiě) JSX,那么如何將 JSX 表示成 vnode?這里可以借助 @babel/plugin-transform-react-jsx 這個(gè)插件來(lái)自定義轉(zhuǎn)換函數(shù),
只需要在 .babelrc 中配置:

{
  "plugins": [
    [
      "@babel/plugin-transform-react-jsx",
      {
        "pragma": "window.h"
      }
    ]
  ]
}

然后在 window 對(duì)象上掛載一個(gè) h 函數(shù):

// h.js
const flattern = arr => [].concat.apply([], arr)

window.h = function h(tagName, attrs, ...children) {
  const node = new vnode()
  node.tagName = tagName
  node.attrs = attrs || {}
  node.children = flattern(children)

  return node
}

測(cè)試一下:

2. 渲染 vnode

現(xiàn)在我們已經(jīng)知道了如何構(gòu)建 vnode,接下來(lái)就是將其渲染成真正的 dom 節(jié)點(diǎn)并掛載。

// 將 vnode 創(chuàng)建為真正的 dom 節(jié)點(diǎn)
export function createElement(vnode) {
  if (typeof vnode !== "object") {
    // 文本節(jié)點(diǎn)
    return document.createTextNode(vnode)
  }

  const el = document.createElement(vnode.tagName)
  setAttributes(el, vnode.attrs)
  vnode.children.map(createElement).forEach(el.appendChild.bind(el))
  return el
}

// render.js
export default function render(vnode, parent) {
  parent = typeof parent === "string" ? document.querySelector(parent) : parent
  return parent.appendChild(createElement(vnode))
}

這里的邏輯主要為:

根據(jù) vnode.tagName 創(chuàng)建元素

根據(jù) vnode.attrs 設(shè)置元素的 attributes

遍歷 vnode.children 并將其創(chuàng)建為真正的元素,然后將真實(shí)子元素節(jié)點(diǎn) append 到第 1 步創(chuàng)建的元素

3. diff vnode

第 2 步已經(jīng)實(shí)現(xiàn)了 vnodedom 節(jié)點(diǎn)的轉(zhuǎn)換與掛載,那么接下來(lái)某一個(gè)時(shí)刻 dom 節(jié)點(diǎn)發(fā)生了變化,如何更新 dom樹(shù)?顯然不能無(wú)腦卸載整棵樹(shù),然后掛載新的樹(shù),最好的辦法還是找出兩棵樹(shù)之間的差異,然后應(yīng)用這些差異。

在寫(xiě) diff 之前,首先要定義好,要 diff 什么,明確 diff 的返回值。比較上圖兩個(gè) vnode,可以得出:

更換第 1、2、3 個(gè) li 的內(nèi)容

ul 下創(chuàng)建兩個(gè) li,這兩個(gè) li 為 第 4 個(gè)第 5 個(gè)子節(jié)點(diǎn)

那么可能得返回值為:

{
  "type": "UPDATE",
  "children": [
    {
      "type": "UPDATE",
      "children": [
        {
          "type": "REPLACE",
          "newVNode": 0
        }
      ],
      "attrs": []
    },
    {
      "type": "UPDATE",
      "children": [
        {
          "type": "REPLACE",
          "newVNode": 1
        }
      ],
      "attrs": []
    },
    {
      "type": "UPDATE",
      "children": [
        {
          "type": "REPLACE",
          "newVNode": 2
        }
      ],
      "attrs": []
    },
    {
      "type": "CREATE",
      "newVNode": {
        "tagName": "li",
        "attrs": {},
        "children": [
          3
        ]
      }
    },
    {
      "type": "CREATE",
      "newVNode": {
        "tagName": "li",
        "attrs": {},
        "children": [
          4
        ]
      }
    }
  ],
  "attrs": []
}

diff 的過(guò)程中,要保證節(jié)點(diǎn)的父節(jié)點(diǎn)正確,并要保證該節(jié)點(diǎn)在父節(jié)點(diǎn) 的子節(jié)點(diǎn)中的索引正確(保證節(jié)點(diǎn)內(nèi)容正確,位置正確)。diff 的核心流程:

case CREATE: 舊節(jié)點(diǎn)不存在,則應(yīng)當(dāng)新建新節(jié)點(diǎn)

case REMOVE: 新節(jié)點(diǎn)不存在,則移出舊節(jié)點(diǎn)

case REPLACE: 只比較新舊節(jié)點(diǎn),不比較其子元素,新舊節(jié)點(diǎn)標(biāo)簽名或文本內(nèi)容不一致,則應(yīng)當(dāng)替換舊節(jié)點(diǎn)

case UPDATE: 到這里,新舊節(jié)點(diǎn)可能只剩下 attrs 和 子節(jié)點(diǎn)未進(jìn)行 diff,所以直接循環(huán) diffAttrs 和 diffChildren 即可

/**
 * diff 新舊節(jié)點(diǎn)差異
 * @param {*} oldVNode
 * @param {*} newVNode
 */
export default function diff(oldVNode, newVNode) {
  if (isNull(oldVNode)) {
    return { type: CREATE, newVNode }
  }

  if (isNull(newVNode)) {
    return { type: REMOVE }
  }

  if (isDiffrentVNode(oldVNode, newVNode)) {
    return { type: REPLACE, newVNode }
  }

  if (newVNode.tagName) {
    return {
      type: UPDATE,
      children: diffVNodeChildren(oldVNode, newVNode),
      attrs: diffVNodeAttrs(oldVNode, newVNode)
    }
  }
}
4. patch 應(yīng)用更新

知道了兩棵樹(shù)之前的差異,接下來(lái)如何應(yīng)用這些更新?在文章開(kāi)頭部分我們提到 dom 節(jié)點(diǎn)樹(shù)應(yīng)當(dāng)只有一個(gè)根節(jié)點(diǎn),同時(shí) diff 算法是保證了虛擬節(jié)點(diǎn)的位置和父節(jié)點(diǎn)是與 dom 樹(shù)保持一致的,那么 patch 的入口也就很簡(jiǎn)單了,從 虛擬節(jié)點(diǎn)的掛載點(diǎn)開(kāi)始遞歸應(yīng)用更新即可。

/**
 * 根據(jù) diff 結(jié)果更新 dom 樹(shù)
 * 這里為什么從 index = 0 開(kāi)始?
 * 因?yàn)槲覀兪鞘褂脴?shù)去表示整個(gè) dom 樹(shù)的,傳入的 parent 即為 dom 掛載點(diǎn)
 * 從根節(jié)點(diǎn)的第一個(gè)節(jié)點(diǎn)開(kāi)始應(yīng)用更新,這是與整個(gè)dom樹(shù)的結(jié)構(gòu)保持一致的
 * @param {*} parent
 * @param {*} patches
 * @param {*} index
 */
export default function patch(parent, patches, index = 0) {
  if (!patches) {
    return
  }

  parent = typeof parent === "string" ? document.querySelector(parent) : parent
  const el = parent.childNodes[index]

  /* eslint-disable indent */
  switch (patches.type) {
    case CREATE: {
      const { newVNode } = patches
      const newEl = createElement(newVNode)
      parent.appendChild(newEl)
      break
    }

    case REPLACE: {
      const { newVNode } = patches
      const newEl = createElement(newVNode)
      parent.replaceChild(newEl, el)
      break
    }

    case REMOVE: {
      parent.removeChild(el)
      break
    }

    case UPDATE: {
      const { attrs, children } = patches

      patchAttrs(el, attrs)

      for (let i = 0, len = children.length; i < len; i++) {
        patch(el, children[i], i)
      }

      break
    }
  }
}
總結(jié)

至此,vdom 的核心 diffpatch 都已基本實(shí)現(xiàn)。在測(cè)試 demo 中,不難發(fā)現(xiàn) diff 其實(shí)已經(jīng)很快了,但是 patch 速度會(huì)比較慢,所以這里留下了一個(gè)待優(yōu)化的點(diǎn)就是 patch。

本文完整代碼均在這個(gè)倉(cāng)庫(kù)。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/100075.html

相關(guān)文章

  • 構(gòu)建一個(gè)使用 Virtual-DOM 的前端模版引擎

    摘要:目錄前言問(wèn)題的提出模板引擎和結(jié)合的實(shí)現(xiàn)編譯原理相關(guān)模版引擎的詞法分析語(yǔ)法分析與抽象語(yǔ)法樹(shù)代碼生成完整的結(jié)語(yǔ)前言本文嘗試構(gòu)建一個(gè)前端模板引擎,并且把這個(gè)引擎和進(jìn)行結(jié)合。于是就構(gòu)思了一個(gè)方案,在前端模板引擎上做手腳。 作者:戴嘉華 轉(zhuǎn)載請(qǐng)注明出處并保留原文鏈接( https://github.com/livoras/blog/issues/14 )和作者信息。 目錄 前言 問(wèn)題的提出...

    imccl 評(píng)論0 收藏0
  • 如何實(shí)現(xiàn)一個(gè)虛擬 DOM——virtual-dom 源碼分析

    摘要:具體代碼如下,,下面,我們來(lái)簡(jiǎn)單介紹下這個(gè)排序算法檢查和中的是否擁有字段,如果沒(méi)有,直接返回的數(shù)組。通過(guò)上面這個(gè)排序算法,我們可以得到一個(gè)新的的數(shù)組。 概述 本文通過(guò)對(duì)virtual-dom的源碼進(jìn)行閱讀和分析,針對(duì)Virtual DOM的結(jié)構(gòu)和相關(guān)的Diff算法進(jìn)行講解,讓讀者能夠?qū)φ麄€(gè)數(shù)據(jù)結(jié)構(gòu)以及相關(guān)的Diff算法有一定的了解。 Virtual DOM中Diff算法得到的結(jié)果如何映...

    qieangel2013 評(píng)論0 收藏0
  • Vue源碼解析(5)-virtual-dom 實(shí)現(xiàn)簡(jiǎn)析

    傳送門(mén)vdom原理

    darcrand 評(píng)論0 收藏0
  • vitual-dom原理與簡(jiǎn)單實(shí)現(xiàn)

    摘要:記錄當(dāng)前節(jié)點(diǎn)的標(biāo)志這是當(dāng)前節(jié)點(diǎn)的差異深度遍歷子節(jié)點(diǎn)對(duì)當(dāng)前節(jié)點(diǎn)進(jìn)行操作將差異的部分應(yīng)用到中這次的粗糙的基本已經(jīng)實(shí)現(xiàn)了,具體的情況更加復(fù)雜。 前言 目前廣為人知的React和Vue都采用了virtual-dom,Virtual DOM憑借其高效的diff算法,讓我們不再關(guān)心性能問(wèn)題,可以隨心所欲的修改數(shù)據(jù)狀態(tài)。在實(shí)際開(kāi)發(fā)中,我們并不需要關(guān)心Virtual DOM是如何實(shí)現(xiàn)的,但是理解Vir...

    Yangder 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

rickchen

|高級(jí)講師

TA的文章

閱讀更多
最新活動(dòng)
閱讀需要支付1元查看
<