我想在书架上放置一个文档片段/元素,我已将其连接到一堆其他元素。然后,每当我想将这些元素系统之一添加到 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);
这将创建原型(prototype)。然后我希望能够复制原型(prototype),添加一个 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);
我知道这不合法。我做过 fiddle 和研究等等,但它没有用。一篇 SO 帖子建议 el = doc.appendChild(el);
返回 el
,但这并没有让我走得太远。
那么……有可能吗?你能创造一个可以重复使用的现成元素吗?或者每次都必须从头开始构建要添加的 DOM 结构?
本质上,我正在寻找性能提升,因为我正在创建成千上万个这样的傻瓜 :)
谢谢。
最佳答案
使用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);
速度提示
您希望最小化三个操作:
字符串解析
当您使用 innerHTML
时会发生这种情况。 innerHTML
单独使用时速度很快。由于所有这些 DOM 方法调用的开销,它通常比等效的手动 DOM 构造更快。但是,您希望将 innerHTML
置于内部循环之外,并且您不想将其用于追加。当元素的内容变得越来越大时,element.innerHTML += 'more html'
尤其具有灾难性 运行时行为。它还会破坏任何事件或数据绑定(bind),因为所有这些节点都会被破坏并重新创建。
因此,为方便起见,请使用 innerHTML
创建您的“原型(prototype)”节点,但对于内部循环,请使用 DOM 操作。要克隆您的原型(prototype),请使用不调用解析器的 prototype.cloneNode(true)
。 (小心克隆原型(prototype)中的 id 属性——当您将它们附加到文档时,您需要确保它们是唯一的!)
文档树修改(重复appendChild
调用)
每次修改文档树时,您可能会触发文档窗口的重绘并更新文档 DOM 节点关系,这可能会很慢。取而代之的是,将您的附加内容分批放入 DocumentFragment
中,并将其仅附加到文档 DOM 一次。
节点查找
如果您已经有一个内存中的原型(prototype)对象并想要修改它的部分,则无论您是否使用 DOM 遍历,getElement*
,都需要导航 DOM 以查找和修改这些部分,或 querySelector*
。
通过在创建原型(prototype)时保留对要修改的节点的引用,将这些搜索排除在内部循环之外。然后,每当您想克隆原型(prototype)的近乎相同的副本时,修改您已经引用的节点,然后克隆修改后的原型(prototype)。
示例模板对象
说到底,这里有一个基本的(可能很快的)模板对象,说明了 cloneNode
和缓存节点引用的使用(减少了字符串解析和节点查找的使用)。
为它提供带有类名和 data-attr="slotname attributename"
属性的“原型(prototype)”节点(或字符串)。类名成为文本内容替换的“插槽”;带有 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);
关于javascript - 从 DOM 创建可重用的文档片段,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14048432/