在将jQuery对象(元素)放入DOM之前初始化它

3

我正在做的一个项目需要对用户选择器进行排序,按网络分组。目前我的做法是创建用户:

clone += '<li class="'+ this.gender +'" data-id="'+ this._id +'">';
clone += '<img src="'+ this.image +'" />';
clone += '<span>'+ this.name +'</span>';
clone += '</li>';

然后,拥有一个以网络名称为键,以用户ID数组为值的jQuery对象,例如:

{
  Network 1: [1,2,3,4]
  Network 2: [5,6,7,8]
}

然后,当选择一个网络时,我会遍历所有的li元素并将它们与id数组匹配,如果不包含该id,则隐藏li元素,像这样:

for(var i = 0, n = education_ids[network].length;i < n;i++){
   $('.friends-list li[data-id='+ education_ids[network][i]+']').removeClass('chosen_network');
}

很不幸,这种方法非常低效,因为对于循环的每一个迭代,jQuery选择器都会遍历所有的 li 元素,因此如果有 1000 个朋友和 100 个网络 id,则需要进行 100 x 1000 步迭代。
为了解决这个问题,我的解决方案是将 jQuery 对象添加到数组中而不是添加它们的 id,这样我就可以迭代它们并添加类(减少了 1000 倍的迭代)。以下是我目前尝试这样做的方式:
clone += '<li class="'+ this.gender +'" data-id="'+ this._id +'">';
clone += '<img src="'+ this.image +'" />';
clone += '<span>'+ this.name +'</span>';
clone += '</li>';

if(this.education){
    for(var i = 0, n = this.education.length; i < n ; i++){
    if(education[this.education[i].school.name]){
       education[this.education[i].school.name]++;
       education_ids[this.education[i].school.name].push($(clone));
    } else {
        education[this.education[i].school.name] = 1;
        education_ids[this.education[i].school.name] = [$(clone)];
    }
}
}

相关部分是当我:
.push($(clone)

或通过以下方式初始化数组:

[$(clone)]

很遗憾,这种技术行不通,因为在程序的这一点上,元素实际上还没有添加到DOM中。所以,我的问题是:

是否有可能在它们被放入DOM之前初始化这些jQuery引用,如果可以,我该如何做呢?

如果我做不到这一点,你能否想到更有效的方法来解决这个问题。

仅供参考,我试图让这个过程更快,因为目前的策略会使循环在1到2秒之间冻结DOM,这对用户来说并不是理想的体验。

非常感谢您能提供的任何帮助!

编辑:取得进展,但出现错误!

好的,所以我已经取得了一些进展。使用jQuery选择器确实有效,所以现在我正在进行以下操作:

    clone += '<li class="'+ this.gender +'" data-id="'+ this._id +'">';
    clone += '<img src="'+ this.image +'" />';
    clone += '<span>'+ this.name +'</span>';
    clone += '</li>';

    clone = $(clone)

    if(this.education){
        for(var i = 0, n = this.education.length; i < n ; i++){
            if(education[this.education[i].school.name]){
                education[this.education[i].school.name]++;
                education_ids[this.education[i].school.name].push(clone);
            } else {
                education[this.education[i].school.name] = 1;
                education_ids[this.education[i].school.name] = [clone];
            }
        }
    }

placeholder.push(clone);

然后循环遍历所有的朋友之后,

$('.friends-list').append(placeholder).fadeIn();

我认为那应该是可以工作的,但现在我收到了以下信息:

Uncaught Error: NOT_FOUND_ERR: DOM Exception 8

您的想法如何?

编辑:更多进展,已解决错误,但排序无效。

好的,我像 @Artimuz 一样将占位符切换为空 div 来修复错误,但现在排序不起作用了。所以,基本上我是这样排序的:

if("network is clicked and it was already selected") {
    for(var person in education_ids[network]){
        person.removeClass('chosen_network');
    }
} else //if it wasn't already selected {
     for(var person in education_ids[network]){
        person.addClass('chosen_network').removeClass('disabled');
}
}

很遗憾,这并不起作用。我得到了以下信息:
Uncaught TypeError: Object 0 has no method 'addClass'

所以,我再次问:你的想法是什么?

我刚在我的Chrome控制台中尝试了一下:创建一个jQuery对象(o = $('<div>').addClass('test')),将其存储在数组中(a = [o]),然后将其添加到DOM中($('body').append(a[0]))没有任何问题。因此,我认为这不是“相关部分”。 - Thomas Guillory
在一个基本的例子中,这对我有效:http://jsfiddle.net/3xqx5/1/。也许你可以在jsfiddle上分享更多的代码?我认为我们没有足够的数据来解决这个问题。 - Thomas Guillory
异常抛出在哪一行? - Thomas Guillory
嘿,你确实帮忙修复了错误,但它仍然无法正常工作。我会在我的问题中发布相关代码。 - Jesse Pollak
对于最后一次编辑:使用索引进行 for 循环 (for (i;i<count;i++))。http://jsfiddle.net/3xqx5/2/ - Thomas Guillory
3个回答

2

好的,我会总结一下你的一些错误:

第一个错误

我不知道你的问题是什么。对我来说它是可行的。

jsfiddle.net/3xqx5

NOT_FOUND_ERR异常

这只是因为您试图添加一个array,一个纯JavaScript对象到DOM中。JQuery.append需要一个jQuery对象或DOM元素!所以你可以把你的项目放在一个占位符<div>中,或者你可以遍历你的数组并逐个添加项目。

jsfiddle.net/3xqx5/1

最后一个错误:Object 0 has no method 'addClass'

你做的for语句不一致,至少因为它在每次迭代时都会复制每个项目。通过使用for(var i=0; i < a.length; i++)直接处理数组元素

jsfiddle.net/3xqx5/2


2
任何JavaScript代码中最慢的部分都是操作DOM。将用户存储起来:
var users = [
    {
        name : "dave",
        gender : "male",
        image : "omg.jpg"
    }
];

存储您的网络(假设您的网络少于用户):

var networks = [
    [2,5,8]
];

需要时适当绘制:

var html = "";
for(var i = networks[current].length; i--;){
    var id = networks[current][i];
    html += '<li class="'+ users[id].gender +'" data-id="'+ id +'">';
    html += '    <img src="'+ users[id].image +'" />';
    html += '    <span>'+ users[id].name +'</span>';
    html += '</li>';
}
$(container).html(html);

这种方式可以一次迭代每个用户,处理的是纯数据,即用户。只需一次操作DOM以绘制所需内容。
这种方法的缺点是需要重新绘制当前显示的所有用户,但如果这是一个问题(例如大多数人都在所有网络中),则可以修改解决方案以缓存已经绘制的用户,并更加离散地添加新用户。

请不要使用字符串拼接来构建HTML。那太糟糕了。 - Raynos
首先,字符串拼接是不好的,因为会出现“hello XSS注入”的问题。其次,字符串拼接很丑陋,影响可读性。第三,它鼓励你添加额外的语句,随着时间的推移,可维护性变得越来越差。第四,您没有对使用的字符串进行任何实体的净化或HTML编码。当然,使用jQuery的HTML解析器构建DOM比直接使用DOM方法要慢。但等等!我已经在我的博客文章中说过所有这些了。 - Raynos
我很想看看你在基于JavaScript的XSS方面的开创性工作。你知道,在XSS中,操作的关键是服务器。你是否担心用户输入的信息会动态地显示在他的屏幕上,以便欺骗自己?因为我确信你会在将数据存储到你的服务器之前,对来自客户端的所有数据进行清理。XSS有两个部分:一是用户传递危险信息,二是未受保护的服务器接受这些信息。你如何编写你的JavaScript与XSS预防毫无关系。如果有关系的话,那么Firebug就会成为互联网的终结者。 - Sinetheta
是的,jQuery的HTML解析器肯定比直接使用DOM方法慢。事实上,任何东西都比直接使用DOM方法慢。这里重要的是,并不会慢很多。我在这里使用字符串拼接的目的是为了向初学者提供一个易于理解且性能足够好的答案。我再次强调,甚至你也没有建议他打开DOM方法来做这件事。为什么?因为在SO的jQuery部分发布这样的帖子是愚蠢的。 - Sinetheta
@Sinetheta 顺带一提,我建议你使用 DOM 方法。如果可以使用 HTML 模板系统而不是构建文档片段,那就不要构建文档片段,但一个人应该使用 DOM 而不是 jQuery 操作 DOM。至于基于 JavaScript 的 XSS 攻击向量,在你的特定代码片段中实际上没有任何可以调用的向量(据我所知),但通常字符串拼接会为这些攻击向量打开大门。 - Raynos
显示剩余2条评论

1

使用字符串拼接构建HTML是可怕的,请避免这样做。

根据@Sinetheta的数据结构

var template = [
    "<li>",
        "<img></img>",
        "<span></span>",
    "</li>"
].join("");

var nodes = [];
for(var i = networks[current].length; i--;){
    var id = networks[current][i],
        user = users[id];

    var li = $(template);
    li.addClass(user.gender);
    li.data("id", id);

    var children = node.children();
    children.eq(0).prop("src", user.image);
    children.eq(1).text(user.name);

    nodes.push(li);
}

$.each(nodes, function (_, node) {
    $(container).append(node);
});

这个解决方案的一个问题是最后一行:$(container).append(nodes); 会抛出像我问题中提到的那样的错误。 - Jesse Pollak
@JessePollak 你说得对,jQuery不支持数组(这太愚蠢了)。 - Raynos
同意。你可以按照你的方式解决它,或者你可以将节点设置为div并附加,而不是每次都推送li。我认为这可能是更高效的方式,因为这样你只需要循环遍历所有的li一次。 - Jesse Pollak
@JessePollak 最有效的方法是将节点作为文档片段并将其附加到其中。 - Raynos

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接