【问题标题】:Optimize regular expression for filtering thousands of HTML select options优化正则表达式以过滤数千个 HTML 选择选项
【发布时间】:2012-11-13 11:30:55
【问题描述】:

背景

我为 HTML select 元素开发了一个基于 jQuery 的 shuttle widget,因为我找不到一个经过最低限度编码并提供了一个正则表达式过滤器来补偿 diacritics。

问题

当向select 添加几千个条目时,正则表达式过滤器会变慢到爬行。可以看到问题如下:

  1. 浏览至:http://jsfiddle.net/U8Xre/2/
  2. 单击结果面板中的输入字段。
  3. 键入任何正则表达式(例如,^a.*ai)。

代码

我相信罪魁祸首潜伏在这里:

var options = $src.empty().scrollTop( 0 ).data( "options" );
var search = $.trim( $input.val() );
var regex = new RegExp( search, 'gi' );
var len = options.length;
var $html = $(document.createElement( 'option' ));
for( var i = 0; i < len; i++ ) {
  var o = options[ i ];
  if( o.text.dediacritics().match( regex ) !== null ) {
    $src.append( $html.clone().text( o.text ).val( o.value ) );
  }
}
$src.css( 'width', $input.width() + 4 );

其中$src 是源$('#select') 和String.prototype.dediacritics 在小提琴中定义。上面的代码针对每个按键运行。还有一个相关的sn-p:

// Create a copy of the source options to use when matching the regex.
var $options = [];
$src.find( "option" ).each( function() {
  $options.push( { value: $(this).val(), text: $(this).text() } );
});
$src.data( "options", $options );

这会复制源列表中的选项,但只运行一次。 (这会导致在穿梭选项时出现重复错误,但将上述代码添加到input 事件处理程序会进一步减慢过滤器。)

问题

如何使代码几乎实时地对多达 5,000 个单词的列表执行正则表达式过滤?

谢谢!

【问题讨论】:

  • 永远不要向&lt;select&gt; 元素添加数千个条目。高于 7 的所有内容都不是用户友好的,高于 20 的所有内容都需要有理由的陈述来说明理由。
  • 这是少数人使用的维护画面。否则,他们必须直接在数据库中手动对项目进行分类。我不是在寻找不同的用户体验解决方案:一切正常,尽管速度很慢。
  • 我的猜测是瓶颈不是正则表达式。你能把瓶颈缩小一点吗?
  • 请问:您在哪里找到了那个花哨的dediacritics 函数,它在某个地方维护过吗?

标签: javascript jquery regex optimization diacritics


【解决方案1】:

我猜想更难的工作是重复调用dediacritics()(带有许多正则表达式替换)而不是进行搜索(尽管我没有进行任何分析)。因此,您应该缓存这些去变音符号的字符串并仅通过它们进行搜索。顺便说一句,test 通常比 match 快。

此外,您应该尽可能避免 DOM 操作 - 在清空和重新附加整个选项列表 onkeypress 时,您有很多操作。

// once:
var options = [],
    src = $src[0]; // or whatever to get the DOM element
$.each( src.options, function() {
    options.push( { el: this, text: $(this).text().dediacritics(), hidden:false } );
});
// you might put it on the element via .data(), but need not

// onkeypress:
var regex = new RegExp( $.trim($input.val()), 'i' );
var curEl = src.firstChild;
for (var i=0; i<options.length; i++) {
    var option = options[i];
    if (regex.test( option.text )) {
        if (option.hidden)
            src.insertBefore(option.el, curEl);
        curEl = option.el.nextSibling;
        option.hidden = false;
    } else {
        if (!option.hidden) {
            curEl = option.el.nextSibling;
            src.removeChild(option.el);
        }
        option.hidden = true;
    }
}

Demo:这非常快(“实时”),但是当调用dediacritics() 5000 次时,您可以感觉到构造options 数组所需的时间。

【讨论】:

  • 非常好;可能需要对此进行更多优化以修复穿梭错误。看起来超级快。
  • 对,我错过了。我建议只存储另一个二进制属性来存储option 当前所在的列表,然后只存储continue for 循环。
  • 还有另一个错误:这将排除与 RegEx 匹配的所有其他项目。如果您有Toy Box、Toy Story 和Toy Zebra,那么^Toy 将不会在列表中显示Toy Story。
  • 感谢您的提示。问题的原因是(不必要的)global 正则表达式上的lastIndex 标志,请参阅stackoverflow.com/q/1520800/1048572。现已修复。
  • @ridgerunner:删除global 标志(就像我所做的那样)也有效
【解决方案2】:

我建议你

  • 创建一个多行字符串,其中包含所有选项名称的列表,每个选项名称位于单独的一行中
  • 对这个多行字符串应用正则表达式,通过删除不匹配的行来过滤其内容
  • 使用匹配行作为选择元素的选项更新 html

【讨论】:

  • 我是这么想的。 $src.append 看起来效率很低。谢谢!
【解决方案3】:

一个小评论,如果您没有使用正则表达式匹配的结果,那么您应该使用正则表达式测试:

  if( o.text.dediacritics().match( regex ) !== null ) {

使用测试:

  if( regex.test(o.text.dediacritics()) ) {

【讨论】:

  • 不,这个答案缺少关键的初始化,按原样运行会导致错误行为。与RegExp.exec() 一样,RegExp.test() 方法使用正则表达式实例对象的lastIndex 属性来确定从何处开始搜索任何字符串,test() 在成功匹配后NOT 重置此属性(它确实会在匹配失败时重置它)。成功匹配后的后续测试(在其他字符串上)将从非零位置开始 - 错误!为了正常工作,这个答案需要在之前的某处添加:regex.lastIndex=0;:regex.test()。
  • 另外,正如@Bergi 正确指出的那样,您可以交替地在全局g 标志关闭的情况下构建正则表达式——这也会在每次运行时强制重置:RegExp.lastIndex。跨度>
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-06-18
  • 1970-01-01
  • 1970-01-01
  • 2011-11-10
  • 1970-01-01
  • 1970-01-01
  • 2019-05-18
相关资源
最近更新 更多