从DOM创建可重用的文档片段 [英] Create reusable document fragment from the DOM

查看:85
本文介绍了从DOM创建可重用的文档片段的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想在架子上安装一个文档片段/元素,我连接了一堆其他元素。然后每当我想将这些元素系统中的一个添加到DOM中时,我复制该片段,添加唯一的DOM ID并附加它。



所以,例如:

  var doc = document,
prototype = doc.createElement(),//或片段
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; //这一步是非法的,但是我想要的!
//我想要原型再次被复制。

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

我知道这是不合法的。我已经做了小费,研究了等等,但没有工作。一个SO帖子建议 el = doc.appendChild(el); 返回 el ,但是没有让我远。



所以...有可能吗?你可以创建一个可以重用的现成元素吗?还是你每次都要从头开始构建你要添加的DOM结构?



本质上我正在寻找一个性能提升'cos我创造了数千个这样的吸盘:)



谢谢。

解决方案

使用 Node.cloneNode

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

var prototype = document.createElement('div');
prototype.innerHTML =< p>在这里添加
+中的一些< strong>任意< / strong> HTML仅仅是为了说明。< / p>< p>跨度>嵌套< / span>也。< / p>
+< p> CloneNode不关心如何创建初始节点。< / p>;

var prototype_copy = prototype.cloneNode(true);

prototype_copy.id ='whatever'; //注意 - 必须是元素!

container.appendChild(prototype_copy);



速度提示



有三个操作您想要最小化:



字符串解析



当您使用 innerHTML 。当您单独使用它时, innerHTML 是快速的。由于所有这些DOM方法调用的开销,它通常比等效的手动DOM构建更快。但是,您希望将内部循环中的 innerHTML 保留,并且不想将其用于附加。元素的内容变得越来越大,特别是具有emast灾难性的运行时行为,因此元素的内容越来越多。它还会破坏任何事件或数据绑定,因为所有这些节点都被销毁并重新创建。



所以使用 innerHTML 创建您的原型节点方便,但对于内循环使用DOM操作。要克隆原型,请使用不调用解析器的 prototype.cloneNode(true)。 (在克隆原型中注意id属性 - 您需要确保自己在将其附加到文档时是唯一的!)



文档树修改(重复 c code code code code code code code code code code code code code $ c记录DOM节点的关系,这可能很慢。相反,将您的追加批处理为 DocumentFragment ,并将其附加到文档DOM一次。



节点查找



如果你已经有一个内存中的原型对象并且想要修改它,你将需要导航DOM来查找和修改这些片段,无论你使用DOM遍历, getElement * querySelector *



保留这些通过在创建原型时保留对要修改的节点的引用来搜索内部循环。然后每当你想克隆原型的几乎相同的副本,修改已经引用的节点,然后克隆修改的原型。



< h2>样本模板对象

对于它,这里是一个基本的(可能很快)的模板对象,说明了使用 cloneNode 和缓存的节点引用(减少字符串解析和节点查找的使用)。



为类提供一个原型节点(或字符串)名称和 data-attr =slotname attributename属性。类名称成为文本内容替换的插槽;具有 data-attr 的元素成为属性名称设置/替换的插槽。然后,您可以向 render()方法中的一个对象提供您定义的插槽的新值,并且您将获得已完成替换的节点的克隆。



用例示例在底部。

  ){
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,函数(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');
});
返回槽;
};
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]));
}
} );
return this.proto.cloneNode(true);
};
Template.prototype.setText =(function(){
var d = document.createElement('div');
var txtprop =(d.textContent ==='')? textContent':'innerText';
d = null;
返回函数(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>这是克隆编号< span class =clonenumber>一个< /跨度>!< / p为H.');

var tpl_data = {
cloneid:0,
clonenumber:0
};
var df = document.createDocumentFragment(); (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);


I would like to have a document fragment/element on the shelf to which I've connected a bunch of other elements. Then whenever I want to add one of these element-systems to the DOM, I copy the fragment, add the 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 the prototype. Then I want to be able to copy the prototype, add an id, and attach. Like so:

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 it's not legal as it stands. I've done fiddles and researched and so on, but it ain't working. One SO post suggested el = doc.appendChild(el); returns el, but that didn't get me far.

So... is it possible? Can you create an on-the-shelf element which can be reused? Or do you have to build the DOM structure you want to add from scratch each time?

Essentially I'm looking for a performance boost 'cos I'm creating thousands of these suckers :)

Thanks.

解决方案

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

Speed Tips

There are three operations you want to minimize:

String Parsing

This occurs when you use innerHTML. innerHTML is fast when you use it in isolation. It's often faster than the equivalent manual-DOM construction because of the overhead of all those DOM method calls. However, you want to keep innerHTML out of inner loops and you don't want to use it for appending. element.innerHTML += 'more html' in particular has catastrophic run-time behavior as the element's contents get bigger and bigger. It also destroys any event or data binding because all those nodes are destroyed and recreated.

So use innerHTML to create your "prototype" nodes for convenience, but for inner loops use DOM manipulation. To clone your prototypes, use prototype.cloneNode(true) which does not invoke the parser. (Be careful with id attributes in cloned prototypes--you need to make sure yourself that they are unique when you append them to the document!)

Document tree modification (repeated appendChild calls)

Every time you modify the document tree you might trigger a repaint of the document window and update the document DOM node relationships, which can be slow. Instead, batch your appends up into a DocumentFragment and append that to the document DOM only once.

Node lookup

If you already have an in-memory prototype object and want to modify pieces of it, you will need to navigate the DOM to find and modify those pieces whether you use DOM traversal, getElement*, or querySelector*.

Keep these searches out of your inner loops by keeping a reference to the nodes you want to modify when you create the prototype. Then whenever you want to clone a near-identical copy of the prototype, modify the nodes you have references to already and then clone the modified prototype.

Sample Template object

For the heck of it, here is a basic (and probably fast) template object illustrating the use of cloneNode and cached node references (reducing the use of string parsing and Node lookups).

Supply it with a "prototype" node (or string) with class names and data-attr="slotname attributename" attributes. The class names become "slots" for text-content replacement; the elements with data-attr become slots for attribute name setting/replacement. You can then supply an object to the render() method with new values for the slots you have defined, and you will get back a clone of the node with the replacements done.

Example usage is at the bottom.

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

这篇关于从DOM创建可重用的文档片段的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆