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

歡迎關注個人公衆號睿Talk,獲取我最新的文章:
clipboard.pngjavascript

1、前言

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

這是VD系列文章的開篇,如下是本系列其它文章的傳送門:
你不知道的Virtual DOM(一):Virtual Dom介紹
你不知道的Virtual DOM(二):Virtual Dom的更新
你不知道的Virtual DOM(三):Virtual Dom更新優化
你不知道的Virtual DOM(四):key的做用
你不知道的Virtual DOM(五):自定義組件
你不知道的Virtual DOM(六):事件處理&異步更新java

2、VD是什麼

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

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

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

<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])三個屬性來描述。github

3、爲何須要VD

VD 最大的特色是將頁面的狀態抽象爲 JS 對象的形式,配合不一樣的渲染工具,使跨平臺渲染成爲可能。如 React 就藉助 VD 實現了服務端渲染、瀏覽器渲染和移動端渲染等功能。算法

此外,在進行頁面更新的時候,藉助VD,DOM 元素的改變能夠在內存中進行比較,再結合框架的事務機制將屢次比較的結果合併後一次性更新到頁面,從而有效地減小頁面渲染的次數,提升渲染效率。咱們先來看下頁面的更新通常會通過幾個階段。
clipboard.pngsegmentfault

從上面的例子中,能夠看出頁面的呈現會分如下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編譯後,會輸出下面的內容:

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元素就建立完並展現到頁面上了(頁面比較醜,不要介意...)。

clipboard.png

5、總結

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

P.S.: 想看完整代碼見這裏,若是有必要建一個倉庫的話請留言給我:代碼

相關文章
相關標籤/搜索