使用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つあります。
文字列の解析
これは、を使用すると発生しますinnerHTML
。innerHTML
単独で使用すると高速です。多くの場合、これらすべての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);