4

他の多くの要素を接続したシェルフにドキュメントフラグメント/要素を配置したいと思います。次に、これらの要素システムの1つをDOMに追加するときはいつでも、フラグメントをコピーし、一意のDOM IDを追加して、それを添付します。

したがって、たとえば:

var doc = document,
    prototype = doc.createElement(),  // or fragment
    ra = doc.createElement("div"),
    rp = doc.createElement("div"),
    rp1 = doc.createElement("a"), 
    rp2 = doc.createElement("a"),
    rp3 = doc.createElement("a");

ra.appendChild(rp);
rp.appendChild(rp1);
rp.appendChild(rp2);
rp.appendChild(rp3);
rp1.className = "rp1";
rp2.className = "rp2";
rp3.className = "rp3";

prototype.appendChild(ra);

これにより、プロトタイプが作成されます。次に、プロトタイプをコピーし、IDを追加して、添付できるようにします。そのようです:

var fr = doc.createDocumentFragment(),
    to_use = prototype; // This step is illegal, but what I want!
                        // I want prototype to remain to be copied again.

to_use.id = "unique_id75";
fr.appendChild(to_use);
doc.getElementById("container").appendChild(fr);

現状では合法ではないことを私は知っています。私はフィドルや調査などを行いましたが、機能していません。あるSOの投稿はel = doc.appendChild(el);返品を提案しましたがel、それは私を遠ざけませんでした。

それで...それは可能ですか?再利用できる既製の要素を作成できますか?または、毎回最初から追加するDOM構造を構築する必要がありますか?

基本的に私はパフォーマンスの向上を探しています'cos私はこれらの吸盤を何千も作成しています:)

ありがとう。

4

2 に答える 2

22

使用Node.cloneNode

var container = document.getElementById('container');

var prototype = document.createElement('div');
prototype.innerHTML = "<p>Adding some <strong>arbitrary</strong> HTML in"
 +" here just to illustrate.</p> <p>Some <span>nesting</span> too.</p>"
 +"<p>CloneNode doesn't care how the initial nodes are created.</p>";

var prototype_copy = prototype.cloneNode(true);

prototype_copy.id = 'whatever'; //note--must be an Element!

container.appendChild(prototype_copy);

スピードのヒント

最小化する操作は3つあります。

文字列の解析

これは、を使用すると発生しますinnerHTMLinnerHTML単独で使用すると高速です。多くの場合、これらすべてのDOMメソッド呼び出しのオーバーヘッドのため、同等の手動DOM構築よりも高速です。ただし、innerHTML内部ループを避けたいので、追加に使用したくありません。 element.innerHTML += 'more html'特に、要素のコンテンツがどんどん大きくなるにつれて、壊滅的な実行時の動作が発生します。また、これらのノードはすべて破棄されて再作成されるため、イベントまたはデータバインディングも破棄されます。

したがってinnerHTML、便宜上、を使用して「プロトタイプ」ノードを作成しますが、内部ループの場合はDOM操作を使用します。プロトタイプのクローンを作成するprototype.cloneNode(true)には、パーサーを呼び出さないを使用します。(複製されたプロトタイプのid属性には注意してください。ドキュメントに追加するときは、それらが一意であることを確認する必要があります!)

ドキュメントツリーの変更(繰り返しのappendChild呼び出し)

ドキュメントツリーを変更するたびに、ドキュメントウィンドウの再描画がトリガーされ、ドキュメントのDOMノードの関係が更新される可能性があります。これは遅くなる可能性があります。代わりに、追加をaにバッチ処理し、DocumentFragmentそれをドキュメントDOMに1回だけ追加します。

ノードルックアップ

getElement*すでにメモリ内のプロトタイプオブジェクトがあり、その一部を変更する場合は、DOMトラバーサル、、、またはを使用するかどうかに関係なく、DOMをナビゲートしてそれらの部分を見つけて変更する必要がありますquerySelector*

プロトタイプを作成するときに変更するノードへの参照を保持することにより、これらの検索を内部ループから遠ざけます。次に、プロトタイプのほぼ同一のコピーを複製する場合は常に、すでに参照しているノードを変更してから、変更したプロトタイプを複製します。

サンプルテンプレートオブジェクト

これは、基本的な(そしておそらく高速な)テンプレートオブジェクトであり、ノード参照の使用cloneNodeとキャッシュされたノード参照を示しています(文字列解析とノードルックアップの使用を減らしています)。

data-attr="slotname attributename"クラス名と属性を持つ「プロトタイプ」ノード(または文字列)を提供します。クラス名は、テキストコンテンツを置き換えるための「スロット」になります。data-attr属性名の設定/置換用のスロットとなる要素。render()次に、定義したスロットの新しい値を使用してオブジェクトをメソッドに提供できます。これにより、置換が行われたノードのクローンが返されます。

使用例は下部にあります。

function Template(proto) {
    if (typeof proto === 'string') {
        this.proto = this.fromString(proto);
    } else {
        this.proto = proto.cloneNode(true);
    }
    this.slots = this.findSlots(this.proto);
}
Template.prototype.fromString = function(str) {
    var d = document.createDocumentFragment();
    var temp = document.createElement('div');
    temp.innerHTML = str;
    while (temp.firstChild) {
        d.appendChild(temp.firstChild);
    }
    return d;
};
Template.prototype.findSlots = function(proto) {
    // textContent slots
    var slots = {};
    var tokens = /^\s*(\w+)\s+(\w+)\s*$/;
    var classes = proto.querySelectorAll('[class]');
    Array.prototype.forEach.call(classes, function(e) {
        var command = ['setText', e];
        Array.prototype.forEach.call(e.classList, function(c) {
            slots[c] = command;
        });
    });
    var attributes = proto.querySelectorAll('[data-attr]');
    Array.prototype.forEach.call(attributes, function(e) {
        var matches = e.getAttribute('data-attr').match(tokens);
        if (matches) {
            slots[matches[1]] = ['setAttr', e, matches[2]];
        }
        e.removeAttribute('data-attr');
    });
    return slots;
};
Template.prototype.render = function(data) {
    Object.getOwnPropertyNames(data).forEach(function(name) {
        var cmd = this.slots[name];
        if (cmd) {
            this[cmd[0]].apply(this, cmd.slice(1).concat(data[name]));
        }
    }, this);
    return this.proto.cloneNode(true);
};
Template.prototype.setText = (function() {
    var d = document.createElement('div');
    var txtprop = (d.textContent === '') ? 'textContent' : 'innerText';
    d = null;
    return function(elem, val) {
        elem[txtprop] = val;
    };
}());
Template.prototype.setAttr = function(elem, attrname, val) {
    elem.setAttribute(attrname, val);
};



var tpl = new Template('<p data-attr="cloneid id">This is clone number <span class="clonenumber">one</span>!</p>');

var tpl_data = {
    cloneid: 0,
    clonenumber: 0
};
var df = document.createDocumentFragment();
for (var i = 0; i < 100; i++) {
    tpl_data.cloneid = 'id' + i;
    tpl_data.clonenumber = i;
    df.appendChild(tpl.render(tpl_data));
}
document.body.appendChild(df);
于 2012-12-27T04:15:13.160 に答える
2

innerHTMLが速くなかったら、私はショックを受けるでしょう。lo-dashやdoTで提供されているようなコンパイル済みのテンプレートは、最適な方法のようです。

この簡単な例を確認してください:http: //jsperf.com/lodash-template

これは、lo-dashのコンパイル済みテンプレートを使用して、ループを含むかなり複雑なテンプレートで300,000 ops/secを取得できることを示しています。私にはかなり速いようで、JSはずっとクリーンです。

明らかに、これは問題の一部にすぎません。これによりHTMLが生成され、実際にHTMLを挿入することは別の問題ですが、ここでも、innerHTMLはcloneNodeやその他のDOMベースのアプローチに勝っているようで、一般的にコードははるかにクリーンです。 http://jsperf.com/clonenode-vs-innerhtml-redo/2

明らかに、あなたは一粒の塩の価値があるこれらのベンチマークを取ることができます。本当に重要なのは実際のアプリです。ただし、決心する前に、複数のアプローチを試して、自分でベンチマークすることをお勧めします。

注:JSPerfのテンプレートに関する多くのベンチマークは、それを間違って行っています。彼らはすべての反復でテンプレートを再コンパイルしていますが、これは明らかに非常に遅くなります。

于 2012-12-27T01:58:41.153 に答える