Create reusable document fragment from DOM

I would like to have a fragment / document element on the shelf, which I hooked up a bunch of other elements. Then, when I want to add one of these systems of elements in the DOM, I copy the snippet adds a unique DOM ID and attach it.

So for example:

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); 

This creates a prototype. Then I want to be able to copy the prototype, add the ID and attach. For instance:

 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); 

I know that this is not legal because it costs. I did the violin and studied and so on, but it does not work. One of SO posts proposed el = doc.appendChild(el); Returns el , but that does not make me far.

So ... is this possible? Can you create an item on a shelf that can be reused? Or do you need to build a DOM structure that you want to add from scratch every time?

Essentially, I'm looking for a performance boost because I create thousands of these suction cups :)

Thanks.

+4
source share
2 answers

Use 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); ); 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); <strong> arbitrary </ strong> HTML in" 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); care how the initial nodes are created </ p>."; 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); 

Tips to speed

There are three operations that you want to minimize:

Parsing strings

This occurs when you use innerHTML . innerHTML is fast when you use it in isolation. This is often faster than the equivalent manual-DOM construct due to the overhead of all calls to the DOM methods. However, you want to leave innerHTML out of inner loops, and you do not want to use it to add. element.innerHTML += 'more html' , in particular, has a catastrophic behavior at runtime, since the element content is becoming more and more. It also destroys any event or data binding, because all of these components are destroyed and recreated.

Therefore, use innerHTML to create nodes "prototype" for convenience, but is used DOM-manipulation for internal cycles. To clone your prototypes using prototype.cloneNode(true) , which does not cause a parser. (Be careful with the id attribute in the cloned prototypes - you need to make sure that they are unique, when you add them to the document!)

Change the document tree (Recall appendChild )

Every time you change the document tree, you can call repaint the document window and update the relationship of a document DOM node, which can be slow. Instead, the package adds to the DocumentFragment and adds it to a DOM document only once.

Node search

If you already have a prototype of an object in memory and you want to change part of it, you will need to navigate through the DOM, to find and change these parts, regardless of whether you use bypass the DOM, getElement* or querySelector* .

Keep these searches is their internal cycles, while maintaining a link to the nodes that you want to change with the creation of a prototype. Then, whenever you want to clone an almost identical copy of the prototype, change the nodes you have links to, and then clone the modified prototype.

EXAMPLE template object

For this purpose, it is a basic (and probably fastest) template object illustrating the use cloneNode and cached reference node (which reduces the use of lines and parsing node).

Put it "prototype" node (or row) from the class names and attributes data-attr="slotname attributename" . Class names are "slots" to replace the text content; elements with data-attr are slots for installing / replacing the attribute name. Then you can give the object a method render() with the new values for the slots that you have defined, and you return the node clone made substitutions.

Example usage below.

 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); ) { 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); ]'); 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); e]; 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); -attr]'); 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); { 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); e, matches [ 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); ; 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); ); 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); ); 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); val) { 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); 
+17
source

I would be shocked if the innerHTML was not faster. Precompiled templates, such as those provided by lo-dash or DoT, seem to be a great way!

Look at this simple example: http://jsperf.com/lodash-template

This shows that you can get 300,000 op / s for a rather complex template with a loop, using ready-made templates using de-features. It seems pretty fast for me, and for a more pure JS.

Obviously, this is only one part of the problem. It generates the HTML, actually insert HTML - is another problem, but again, the innerHTML, seems to be winning cloneNode and other approaches based on the DOM, and, as a rule, the code becomes more clear. http://jsperf.com/clonenode-vs-innerhtml-redo/2

Obviously, you can take these landmarks that deserve salt. What really matters is your application. But I would recommend trying several approaches and comparing them yourself before you decide.

Note. Many tests of templates on JSPerf doing it wrong. They recompile the template at each iteration, which will obviously be slow.

+2
source

All Articles