JavaScript—高效地查找包含一大组字符串之一的所有元素

JavaScript—高效地查找包含一大组字符串之一的所有元素,javascript,jquery,performance,Javascript,Jquery,Performance,我有一组字符串,我需要找到HTML文档中出现的所有字符串。字符串出现的位置很重要,因为我需要以不同的方式处理每种情况: 字符串是属性的全部或部分。e、 例如,字符串是foo:->将类ATTR添加到元素中 字符串是元素的全文。e、 例如,foo->将类文本添加到元素中 字符串内嵌在元素的文本中。e、 例如,I love foo->用class text将文本包装在span标记中 另外,我需要先匹配最长的字符串。e、 例如,如果我有foo和foobar,那么我爱foobar应该变成我爱fooba

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

  • 字符串是属性的全部或部分。e、 例如,字符串是foo:
    ->将类ATTR添加到元素中

  • 字符串是元素的全文。e、 例如,
    foo
    ->将类文本添加到元素中

  • 字符串内嵌在元素的文本中。e、 例如,
    I love foo

    ->用class text将文本包装在span标记中

另外,我需要先匹配最长的字符串。e、 例如,如果我有foo和foobar,那么
我爱foobar

应该变成
我爱foobar

,而不是
我爱foobar

内联文本非常简单:按长度降序排列字符串,然后在
document.body.innerHTML
中查找并替换为
$1
,尽管我不确定这是否是最有效的方法

对于属性,我可以执行以下操作:

sortedStrings.each(function(it) {
     document.body.innerHTML.replace(new RegExp('(\S+?)="[^"]*'+escapeRegExChars(it)+'[^"]*"','g'),function(s,attr) {
        $('[+attr+'*='+it+']').addClass('ATTR');
     });
});
同样,这似乎效率低下

最后,对于全文元素,可以对文档进行深度优先搜索,将
innerHTML
与每个字符串进行比较,但对于大量字符串,这似乎效率很低

任何提供性能改进的答案都会获得一票:)

编辑:我修改了鲍勃的答案
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));
            }
        });
    });
});
函数dfs(迭代器,作用域){
scope=scope | | document.body;
$(作用域).children().each(函数()){
返回dfs(迭代器,this);
});
返回iterator.call(作用域);
}
var-escapeChars=/['\/.+?|()[\]{}\\]/g;
函数安全(文本){
返回文本。替换(转义字符,\\$1');
}
函数eachKey(迭代器){
var键、lit、i、len、exp;
对于(i=0,len=keys.length;i

在假设遍历是最昂贵的操作的情况下,这似乎是最优的,尽管改进仍然是受欢迎的。

确实没有好的方法来做到这一点。最后一个要求是必须遍历整个dom

对于前2个需求,我将根据标记名选择所有元素,并根据需要插入内容


我能想到的唯一性能改进是不惜一切代价在服务器端完成这项工作,这甚至可能意味着需要额外的帖子让更快的服务器完成这项工作,否则这可能会非常缓慢,比如说,IE6确实没有好的方法来完成这项工作。最后一个要求是必须遍历整个dom

对于前2个需求,我将根据标记名选择所有元素,并根据需要插入内容


我能想到的唯一性能改进就是不惜一切代价在服务器端完成这项工作,这甚至可能意味着需要额外的帖子来让更快的服务器完成这项工作,否则这可能会非常缓慢,比如说,IE6试图用正则表达式解析HTML是一件轻而易举的事。它甚至不能处理HTML的基本结构,更不用说它的怪癖了。你的代码片段已经有太多错误了。(不检测不带引号的属性;由于缺少HTML转义、正则表达式转义或CSS转义(*),在
it
中出现大量标点符号时失败;在
-
中出现属性失败;奇怪的是没有使用
替换
。)

所以,使用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编码,但实际上也不是这样。这几乎总是错误的;避免。)

尝试用正则表达式解析HTML是一个愚蠢的游戏。它甚至不能处理HTML的基本结构,更不用说它的怪癖了。你的代码片段已经有太多错误了。(不检测不带引号的属性;由于缺少HTML转义、正则表达式转义或CSS转义(*),在
it
中出现大量标点符号时失败;在
-
中出现属性失败;奇怪的是没有使用
替换
。)

所以,使用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编码,但实际上也不是。它几乎总是错误的;避免。)

这是众所周知的事。;-)(嗯……从其他地方改编的代码,真的。)我实际上是为了