使用 jQuery 和 contains() 进行全词匹配

Whole-word matching with jQuery and contains()

我正在编写一个 Greasemonkey 脚本来有选择地隐藏包含讨厌内容的元素(如果您愿意,可以使用个人网络消毒剂)。

这是我目前得到的:

//custom contains function which is case-insensitive
$.extend($.expr[":"], {
  "containsNC": function(elem, i, match, array) {
    return (elem.textContent || elem.innerText || "").toLowerCase().indexOf((match[3] || "").toLowerCase()) >= 0;
  }
});

//build array of words to filter
var nope = "long list of horrible words".toLowerCase().split(' ');

//start with an empty jQuery object
var nopeEles = $();

//add elements to filter to it
for (var i = 0; i < nope.length; i++) {
  nopeEles = nopeEles.add( $("a:containsNC('" + nope[i] + "')") );
  nopeEles = nopeEles.add( $("p:containsNC('" + nope[i] + "')") );
}

//hide all applicable elements
nopeEles.css("background-color", "white");
nopeEles.css("color", "white");

还算可以,但是做的是部分词匹配,导致短词不能用。我想过滤包含 "die" 和 "gun" 等词的元素,而不过滤包含 "candied" 或 "gung-ho".

等词的元素

需要说明的是,我追求的是整个单词,而不是精确的文本。我希望列表中的 "gun" 不仅匹配 "gun",还匹配 "he fired a gun" 和 "a gun was fired"。而不是 "gunney sergeant".

我在这个主题上看到的所有其他答案都推荐 jQuery 的 filter()。我想我还不够了解。我尝试在循环中使用这一行,但什么也没有:

nopeEles = nopeEles.add( $("a").filter(function() { return $(this).text() === nope[i]; }) );

我想看的另一个角度是摆弄 containsNC 所以它寻找这个词,但两边都有空格或字符串结尾。不过,我真的不明白 containsNC 是如何工作的。

任何指点将不胜感激!

containsNC 只是 this p:containsCI() jQuery extension 的次等版本。
("NC" == "no case" ≈≈ "CI" == "Case insensitive".)

改用链接的 jQuery 扩展名,然后您可以使用正则表达式来匹配整个单词,例如:

nopeEles = nopeEles.add( $("a:containsCI('\b" + nope[i] + "\b')") );

但是,该问题代码效率很低,您会发现它会减慢页面速度,因为它扫描整个页面 2N 次(其中 N 是术语数)乘以 J 次子字符串扫描(其中 J 是<a><p> 个节点数)。

一种更高效的方法是通过合并正则表达式只扫描每个节点一次。请参阅此演示:

jQuery.extend (
    jQuery.expr[':'].containsCI = function (a, i, m) {
        var sText   = (a.textContent || a.innerText || "");
        var zRegExp = new RegExp (m[3], 'i');
        return zRegExp.test (sText);
    }
);

//-- Build array of terms to filter:
var badTerms    = ['die', 'guns?', 'agitators?'];
//-- Build ONE regex string for speed and efficiency:
var cnsrRegEx   = `\b(${badTerms.join ("|")})\b`;  //  \b is word-break regex.

var nopeEles    = $("a, p").filter (":containsCI('" + cnsrRegEx + "')");

//-- Hide all applicable elements:
nopeEles.css ( {
    "background-color": "white",
    "color": "white"
} );
a, p {border: 1px solid lightgray; padding: 0.3ex 1ex;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
<p>All good</p>
<p>All bad agitators</p>
<div>Some bad: <a>die</a> <a>gun</a> <a>candied</a> <a>gung-ho</a> <a>guns</a>
  <a>he fired a gun</a> <a>gunney sergeant</a>
</div>

注:

  1. guns? 这样的正则表达式允许匹配 "gun" 和 "guns"。
  2. 由于我们正在构建将转换为正则表达式的 字符串,因此必须转义 \ 个字符。那就是使用 "\b" 在正则表达式中得到 \b