將HTML字元轉換為DOM節點並動態新增到文件中

玄魂發表於2018-08-18

將HTML字元轉換為DOM節點並動態新增到文件中

將字串動態轉換為DOM節點,在開發中經常遇到,尤其在模板引擎中更是不可或缺的技術。 字串轉換為DOM節點本身並不難,本篇文章主要涉及兩個主題:

1 字串轉換為HTML DOM節點的基本方法及效能測試
2 動態生成的DOM節點新增到文件中的方法及效能測試

本文的示例: 有如下程式碼段

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <div id='container'>
<!-- 動態新增div 
    <div class='child'> XXX</div>
 -->
    </div>
</body>
</html>
複製程式碼

任務是編寫一個JavaScript函式,接收一個文字內容,動態生成一個包含該文字的div,返回該Node。

1.1 動態建立Node

1.1.1 innerHTML

第一種方法,我們使用document.createElement方法建立新的元素,然後利用innerHTML將字串注入進去,最後返回firstChild,得到動態建立的Node。

  <script>
        function createNode(txt) {
            const template = `<div class='child'>${txt}</div>`;
            let tempNode = document.createElement('div');
            tempNode.innerHTML = template;
            return tempNode.firstChild;
        }
        const container = document.getElementById('container');
        container.appendChild(createNode('hello'));

    </script>
複製程式碼

下面我們看第二種方法

1.1.2 DOMParser

DOMParser 例項的parseFromString方法可以用來直接將字串轉換為document 文件物件。有了document之後,我們就可以利用各種DOM Api來進行操作了。

  function createDocument(txt) {
            const template = `<div class='child'>${txt}</div>`;
            let doc = new DOMParser().parseFromString(template, 'text/html');
            let div = doc.querySelector('.child');
            return div;
        }
        
        const container = document.getElementById('container');
        container.appendChild(createDocument('hello'));
複製程式碼

1.1.2 DocumentFragment

DocumentFragment 物件表示一個沒有父級檔案的最小文件物件。它被當做一個輕量版的 Document 使用,用於儲存已排好版的或尚未打理好格式的XML片段。最大的區別是因為DocumentFragment不是真實DOM樹的一部分,它的變化不會引起DOM樹的重新渲染的操作(reflow) ,且不會導致效能等問題。

利用document.createRange().createContextualFragment方法,我們可以直接將字串轉化為DocumentFragment物件。

 function createDocumentFragment(txt) {
            const template = `<div class='child'>${txt}</div>`;
            let frag = document.createRange().createContextualFragment(template);
            return frag;
        }

        const container = document.getElementById('container');
        container.appendChild(createDocumentFragment('hello'));
複製程式碼

這裡要注意的是我們直接將生成的DocumentFragment物件插入到目標節點中,這會將其所有自己點插入到目標節點中,不包含自身。我們也可以使用

frag.firstChild
複製程式碼

來獲取生成的div。

1.1.3 效能測試

下面我們來簡單比對下上面三種方法的效能,只是測試生成單個節點,在實際使用中並不一定有實際意義。

先測試createNode。

  function createNode(txt) {
            const template = `<div class='child'>${txt}</div>`;

            let start = Date.now();
            for (let i = 0; i < 1000000; i++) {
                let tempNode = document.createElement('div');
                tempNode.innerHTML = template;
                let node = tempNode.firstChild;
            }
            console.log(Date.now() - start);

        }
        createNode('hello');
複製程式碼

測試100萬個Node生成,用時 6322

再來測試createDocument。

    function createDocument(txt) {
            const template = `<div class='child'>${txt}</div>`;
            let start = Date.now();
            for (let i = 0; i < 1000000; i++) {
                let doc = new DOMParser().parseFromString(template, 'text/html');
                let div = doc.firstChild;
            }
            console.log(Date.now() - start);
        }
    createDocument('hello');
複製程式碼

測試100萬個Node生成,用時 55188

最後來測試createDocumentFragment.

 function createDocumentFragment(txt) {
            const template = `<div class='child'>${txt}</div>`;
            let start = Date.now();
            for (let i = 0; i < 1000000; i++) {
            let frag = document.createRange().createContextualFragment(template);
            }
            console.log(Date.now() - start);
        }
        createDocumentFragment();
複製程式碼

測試100萬個Node生成,用時 6210

createDocumentFragment方法和createNode方法,在這輪測試中不相上下。下面我們看看將生成的DOM元素動態新增到文件中的方法。

1.2.0 批量新增節點

被動態建立出來的節點大多數情況都是要新增到文件中,顯示出來的。下面我們來介紹並對比幾種常用的方案。 下面我們批量新增的方法都採用createDocumentFragment方法。

1.2.1 直接append

直接append方法,就是生成一個節點就新增到文件中,當然這會引起佈局變化,被普遍認為是效能最差的方法。

 const template = "<div class='child'>hello</div>";

        function createDocumentFragment() {


            let frag = document.createRange().createContextualFragment(template);
            return frag;
        }
        // createDocumentFragment();
        const container = document.getElementById('container');
        let start = Date.now();
        for (let i = 0; i < 100000; i++) {
            container.appendChild(createDocumentFragment());
        }
        console.log(Date.now() - start);
複製程式碼

上面的程式碼我們測算動態新增10萬個節點。結果如下:

1.png

測試1000個節點耗時20毫秒,測試10000個節點耗時10001毫秒,測試100000個節點耗時46549毫秒。

1.2.2 DocumentFragment

上面我們已經介紹過DocumentFragment了,利用它轉換字串。下面我們利用該物件來作為臨時容器,一次性新增多個節點。

利用document.createDocumentFragment()方法可以建立一個空的DocumentFragment物件。


        const template = "<div class='child'>hello</div>";

        function createDocumentFragment() {


            let frag = document.createRange().createContextualFragment(template);
            return frag;
        }
        // createDocumentFragment();
        const container = document.getElementById('container');
        let fragContainer = document.createDocumentFragment();
        let start = Date.now();
        for (let i = 0; i < 1000; i++) {
            fragContainer.appendChild(createDocumentFragment());
        }
        container.appendChild(fragContainer);
        console.log(Date.now() - start);
複製程式碼

測試1000個節點耗時25毫秒,10000個節點耗時2877毫秒,100000個節點瀏覽器卡死。

1.3 小結

簡單了介紹了幾種方法,並沒有什麼技術含量。但是從動態新增節點來看,網上說的DocumentFragment方法效能遠遠好於直接append的說法在我的測試場景中並不成立。

DocumentFragment正確的應用場景應該是作為虛擬DOM容器,在頻繁修改查詢但是並不需要直接渲染的場景中。

更多精彩內容,請關注 微信訂閱號“玄說前端”

qrcode_for_gh_f819d888c500_258.jpg

相關文章