JavaScript - 有效地查找包含大量字符串之一的所有元素

时间:2010-04-23 16:08:47

标签: javascript jquery performance

我有一组字符串,我需要在HTML文档中找到所有出现的内容。字符串出现的位置很重要,因为我需要以不同的方式处理每个案例:

  • 字符串是属性的全部或部分。例如,字符串是foo:<input value="foo"> - &gt;将类ATTR添加到元素。

  • String是元素的全文。例如,<button>foo</button> - &gt;将类TEXT添加到元素。

  • 字符串在元素的文本中是内联的。例如,<p>I love foo</p> - &gt;使用TEXT类在span标记中包装文本。

另外,我需要先匹配最长的字符串。例如,如果我有foo和foobar,那么<p>I love foobar</p>应该变为<p>I love <span class="TEXT">foobar</span></p>,而不是<p>I love <span class="TEXT">foo</span>bar</p>

内联文字很简单:按字母长度降序排序,找到并用document.body.innerHTML替换<span class="TEXT">$1</span>中的每一个,但我不确定这是否是最有效的方法。

对于属性,我可以这样做:

sortedStrings.each(function(it) {
     document.body.innerHTML.replace(new RegExp('(\S+?)="[^"]*'+escapeRegExChars(it)+'[^"]*"','g'),function(s,attr) {
        $('[+attr+'*='+it+']').addClass('ATTR');
     });
});

再次,这似乎效率低下。

最后,对于全文元素,文档的深度优先搜索将innerHTML与每个字符串进行比较将起作用,但对于大量字符串,它似乎非常低效。

任何提供性能改进的答案都会得到支持:)

编辑:我对Bob的回答进行了修改。 delim是字符串周围的可选分隔符(以区别于普通文本),keys是字符串列表。

function dfs(iterator,scope) {
    scope = scope || document.body;
    $(scope).children().each(function() {
        return dfs(iterator,this);
    });
    return iterator.call(scope);
}

var escapeChars = /['\/.*+?|()[\]{}\\]/g;
function safe(text) { 
    return text.replace(escapeChars, '\\$1');
}

function eachKey(iterator) {
    var key, lit, i, len, exp;
    for(i = 0, len = keys.length; i < len; i++) {
        key = keys[i].trim();
        lit = (delim + key + delim);
        exp = new RegExp(delim + '(' + safe(key) + ')' + delim,'g');            
        iterator(key,lit,exp);
    }
}

$(function() {
    keys = keys.sort(function(a,b) {
        return b.length - a.length;
    });

    dfs(function() {
        var a, attr, html, val, el = $(this);
        eachKey(function(key,lit,exp) {
            // check attributes
            for(a in el[0].attributes) {
                attr = el[0].attributes[a].nodeName;
                val = el.attr(attr);
                if(exp.test(val)) {
                    el.addClass(attrClass);
                    el.attr(attr,val.replace(exp,"$1"));
                }
            }
            // check all content
            html = el.html().trim();
            if(html === lit) {
                el.addClass(theClass);
                el.html(key); // remove delims
            } else if(exp.test(html)) {
                // check partial content
                el.html(html.replace(exp,wrapper));
            }
        });
    });
});

假设遍历是最昂贵的操作,这似乎是最佳的,尽管仍然可以进行改进。

2 个答案:

答案 0 :(得分:2)

尝试用正则表达式解析HTML是一个杯子的游戏。它根本无法处理HTML的基本结构,更不用说怪癖了。您的代码段已经存在很多错误。 (不检测未加引号的属性;由于缺少HTML转义,正则表达式转义或CSS转义(*)而导致it中的各种标点符号失败;对于-的属性失败;奇怪的不使用replace ...)

所以,使用DOM。是的,这意味着一次遍历。但是那样的选择器就像你已经使用的[attr*=]一样。

var needle= 'foo';

$('*').each(function() {
    var tag= this.tagName.toLowerCase();
    if (tag==='script' || tag==='style' || tag==='textarea' || tag==='option') return;

    // Find text in attribute values
    //
    for (var attri= this.attributes.length; attri-->0;)
        if (this.attributes[attri].value.indexOf(needle)!==-1)
            $(this).addClass('ATTR');

    // Find text in child text nodes
    //
    for (var childi= this.childNodes.length; childi-->0;) {
        var child= this.childNodes[childi];
        if (child.nodeType!=3) continue;

        // Sole text content of parent: add class directly to parent
        //
        if (child.data==needle && element.childNodes.length===1) {
            $(this).addClass('TEXT');
            break;
        }

        // Else find index of each occurence in text, and wrap each in span
        //
        var parts= child.data.split(needle);
        for (var parti= parts.length; parti-->1;) {
            var span= document.createElement('span');
            span.className= 'TEXT';
            var ix= child.data.length-parts[parti].length;
            var trail= child.splitText(ix);
            span.appendChild(child.splitText(ix-needle.length));
            this.insertBefore(span, trail);
        }
    }
});

(反向循环是必要的,因为这是内容的破坏性迭代。)

(*:escape没有做任何这些事情。它更像是URL编码,但它也不是真的。它几乎总是错误的;避免。)

答案 1 :(得分:1)

真的没有办法做到这一点。你的最后一项要求使你必须遍历整个dom。

对于前2个要求,我将按标签名称选择所有元素,并根据需要对它们进行交互。

只有我能想到的性能改进就是不惜一切代价在服务器端执行此操作,这甚至可能意味着让更快的服务器完成工作的额外帖子,否则这可能会非常慢,比如,IE6