你不知道的Virtual DOM(一):Virtual Dom 介紹

1、前言

目前最流行的兩大前端框架,React 和 Vue,都不約而同的藉助 Virtual DOM 技術提升頁面的渲染效率。那麼,什麼是 Virtual DOM ?它是經過什麼方式去提高頁面渲染效率的呢?本系列文章會詳細講解 Virtual DOM 的建立過程,並實現一個簡單的 Diff 算法來更新頁面。本文的內容脫離於任何的前端框架,只講最純粹的 Virtual DOM 。敲單詞太累了,下文 Virtual DOM 一概用 VD 表示。javascript

這是 VD 系列文章的開篇,後續還會有更多的文章帶你深刻了解 VD 的奧祕。前端

2、VD 是什麼

本質上來講,VD 只是一個簡單的JS對象,而且最少包含tagpropschildren三個屬性。不一樣的框架對這三個屬性的命名會有點差異,但表達的意思是一致的。它們分別是標籤名( tag )、屬性( props )和子元素對象( children )。下面是一個典型的 VD 對象例子:java

{
    tag: "div",
    props: {},
    children: [
        "Hello World", 
        {
            tag: "ul",
            props: {},
            children: [{
                tag: "li",
                props: {
                    id: 1,
                    class: "li-1"
                },
                children: ["第", 1]
            }]
        }
    ]
}
複製代碼

VD 跟 dom 對象有一一對應的關係,上面的 VD 是由如下的 HTML 生成的react

<div>
    Hello World
    <ul>
        <li id="1" class="li-1">
            第1
        </li>
    </ul>
</div>
複製代碼

一個 dom 對象,好比li,由tag(li), props({id: 1, class: "li-1"})children(["第", 1])三個屬性來描述。git

3、爲何須要 VD

藉助 VD,能夠達到有效減小頁面渲染次數的目的,從而提升渲染效率。咱們先來看下頁面的更新通常會通過幾個階段。github

從上面的例子中,能夠看出頁面的呈現會分如下3個階段:算法

  • JS計算
  • 生成渲染樹
  • 繪製頁面

這個例子裏面,JS 計算用了691毫秒,生成渲染樹578毫秒,繪製73毫秒。若是能有效的減小生成渲染樹和繪製所花的時間,更新頁面的效率也會隨之提升。 經過 VD 的比較,咱們能夠將多個操做合併成一個批量的操做,從而減小 dom 重排的次數,進而縮短了生成渲染樹和繪製所花的時間。至於如何基於 VD 更有效率的更新 dom,是一個頗有趣的話題,往後有機會將另寫一篇文章介紹。數組

4、如何實現 VD 與真實 DOM 的映射

咱們先從如何生成 VD 提及。藉助 JSX 編譯器,能夠將文件中的 HTML 轉化成函數的形式,而後再利用這個函數生成 VD。看下面這個例子:前端框架

function render() {
    return (
        <div> Hello World <ul> <li id="1" class="li-1"> 第1 </li> </ul> </div>
    );
}
複製代碼

這個函數通過 JSX 編譯後,會輸出下面的內容:babel

function render() {
    return h(
        'div',
        null,
        'Hello World',
        h(
            'ul',
            null,
            h(
                'li',
                { id: '1', 'class': 'li-1' },
                '\u7B2C1'
            )
        )
    );
}
複製代碼

這裏的h是一個函數,能夠起任意的名字。這個名字經過 babel 進行配置:

// .babelrc 文件
{
  "plugins": [
    ["transform-react-jsx", {
      "pragma": "h"    // 這裏可配置任意的名稱
    }]
  ]
}
複製代碼

接下來,咱們只須要定義 h 函數,就能構造出 VD

function flatten(arr) {
    return [].concat.apply([], arr);
}

function h(tag, props, ...children) {
    return {
        tag, 
        props: props || {}, 
        children: flatten(children) || []
    };
}
複製代碼

h 函數會傳入三個或以上的參數,前兩個參數一個是標籤名,一個是屬性對象,從第三個參數開始的其它參數都是 children。children 元素有多是數組的形式,須要將數組解構一層。好比:

function render() {
    return (
        <ul> <li>0</li> { [1, 2, 3].map( i => ( <li>{i}</li> )) } </ul>
    );
}

// JSX 編譯後
function render() {
    return h(
        'ul',
        null,
        h(
            'li',
            null,
            '0'
        ),
        /* * 須要將下面這個數組解構出來再放到 children 數組中 */
        [1, 2, 3].map(i => h(
            'li',
            null,
            i
        ))
    );
}
複製代碼

繼續以前的例子。執行 h 函數後,最終會獲得以下的 VD 對象:

{
    tag: "div",
    props: {},
    children: [
        "Hello World", 
        {
            tag: "ul",
            props: {},
            children: [{
                tag: "li",
                props: {
                    id: 1,
                    class: "li-1"
                },
                children: ["第", 1]
            }]
        }
    ]
}
複製代碼

下一步,經過遍歷 VD 對象,生成真實的 dom

// 建立 dom 元素
function createElement(vdom) {
    // 若是 vdom 是字符串或者數字類型,則建立文本節點,好比「Hello World」
    if (typeof vdom === 'string' || typeof vdom === 'number') {
        return doc.createTextNode(vdom);
    }

    const {tag, props, children} = vdom;

    // 1. 建立元素
    const element = doc.createElement(tag);

    // 2. 屬性賦值
    setProps(element, props);

    // 3. 建立子元素
    // appendChild 在執行的時候,會檢查當前的 this 是否是 dom 對象,所以要 bind 一下
    children.map(createElement)
            .forEach(element.appendChild.bind(element));

    return element;
}

// 屬性賦值
function setProps(element, props) {
    for (let key in props) {
        element.setAttribute(key, props[key]);
    }
}
複製代碼

createElement函數執行完後,dom元素就建立完並展現到頁面上了(頁面比較醜,不要介意...)。

5、總結

本文介紹了 VD 的基本概念,並講解了如何利用 JSX 編譯 HTML 標籤,而後生成 VD,進而建立真實 dom 的過程。下一篇文章將會實現一個簡單的 VD Diff 算法,找出 2 個 VD 的差別並將更新的元素映射到 dom 中去。

P.S.: 想看完整代碼見這裏:代碼

參考連接:

相關文章
相關標籤/搜索