Case-insensitive 字符串 replace-all in JavaScript 没有正则表达式

Case-insensitive string replace-all in JavaScript without a regex

我想在 JavaScript 中执行 case-insensitive 字符串 replace-all 而不使用正则表达式(或调用 replace 方法时的 regex-style 字符串)。我找不到这方面的问题或答案,但如果我错过了,请link它。

例如,将 'abc' 替换为 'x' in:

Find aBc&def stuff ABCabc 变为 Find x&def stuff xx

结果中没有替换的部分应该保留原来的大小写。

字符串中可能包含特殊字符,所以这就是我避免使用正则表达式的原因。我的特殊问题可能可以用正则表达式解决,但我有兴趣完全避免它。

有几个使用正则表达式的问答,包括对特殊字符的处理。特别是,bobince 在这里的回答 描述了在不知道原始字符串中的特定条件或不根据特定条件采取行动的情况下如何不可能。

我认为它会涉及一个循环和 indexOf,遍历原始字符串,构建一个结果。

为了这个问题,假设性能不是主要问题。例如,循环字符是可以的。

有一些现有的问题包括所有答案的正则表达式:

编辑:
从一些答案中,一些澄清 — 我最初没有指定这些,但它们是典型的 search/replace 行为:

可以替换为相同的字符串,例如,将 'abc' 替换为 'Abc',比如修复名称的标题大小写。

替换不应是 re-checked,例如,将 'ab' 替换为 'abc' 应该可以。例如,在 abcc 中用 'ab' 替换 'abc' 变成 abc 而不是 ab.

我觉得这些归结起来应该做替换,然后在字符串中继续前进,不用"looking back"。

编辑: 这里有一些测试用例,仅供记录。我没有进入空字符串等,可能也应该进行测试。 https://jsfiddle.net/k364st09/1/

("Find aBc&def abc", "abc", "xy")   - Find xy&def xy - general test
("Find aBc&def abc", "abc", "ABC")  - Find ABC&def ABC - replace same test, avoid infinite loop
("Find aBcc&def abc", "abc", "ab")  - Find abc&def ab - "move on" avoid double checking (fails if abcc becomes ab)
("abc def", "abc", "xy")            - xy def - Don't drop last characters.
("abcc def", "abc", "xy")           - xyc def  - Just a mix of "move on" and "don't drop last".
var s="aBc&def stuff ABCabc"
var idx = s.toUpperCase().indexOf("ABC");
while(idx!==-1){
  s = s.substr(0,idx)+"x"+s.substr(idx+2);
  idx = s.toUpperCase().indexOf("ABC");
}
function replace(s, q, r) {
  var result = '';
  for (var i = 0; i < s.length; i++) {
    var j = 0;
    for (; j < q.length; j++) {
      if (s[i + j].toLowerCase() != q[j].toLowerCase()) break;
    }
    if (j == q.length) {
      i += q.length - 1;
      result += r;
    } else {
      result += s[i];
    }
  }
  return result;
}

函数接受参数:

  • s - 原始字符串
  • q - 搜索查询
  • r - 替换字符串(针对每个搜索查询实例)

    1. 它通过遍历每个位置来工作。

    2. 在每个位置,它将尝试检查匹配(通过 .toLowerCase() 不区分大小写)。

    3. 它找到的每个匹配项,都会将替换字符串插入到结果中。否则,它只是将不匹配项复制到结果中。

嗯,如果性能不是问题,您可能希望遍历字符串的字符以找到所需的字符串进行替换。像这样的东西,也许...

for (var x = 0; x < inputString.length-3; x++) {
    if (inputString.toLowerCase.substring(x, x+2) == 'abc') {
        inputString = inputString.substring(0, x-1) + 'x' + inputString.substring(x+3);
        x = x - 2 //because your replacement is shorter you need to back up where the index is
    }
}
  1. 从空字符串开始并复制原始字符串。
  2. 在副本中找到要替换的字符串的索引(将它们都设置为小写会使搜索不区分大小写)。
  3. 如果副本中没有,请跳至第 7 步。
  4. 添加从副本到索引的所有内容,加上替换。
  5. Trim 复制到您要替换的部分之后的所有内容。
  6. 返回第 2 步。
  7. 添加副本的剩余内容。

为了好玩,我创建了一个交互式版本,您可以在其中查看正则表达式和 indexOf 的结果,看看转义正则表达式是否会破坏任何内容。我从 jQuery UI 中获取的用于转义正则表达式的方法。如果您将其包含在页面上,则可以通过 $.ui.autocomplete.escapeRegex 找到它。否则,它是一个非常小的函数。

这是非正则表达式函数,但由于交互部分添加了更多代码,我默认隐藏了完整的代码片段。

function insensitiveReplaceAll(original, find, replace) {
  var str = "",
    remainder = original,
    lowFind = find.toLowerCase(),
    idx;

  while ((idx = remainder.toLowerCase().indexOf(lowFind)) !== -1) {
    str += remainder.substr(0, idx) + replace;

    remainder = remainder.substr(idx + find.length);
  }

  return str + remainder;
}

// example call:
insensitiveReplaceAll("Find aBcc&def stuff ABCabc", "abc", "ab");

function insensitiveReplaceAll(original, find, replace) {
  var str = "",
    remainder = original,
    lowFind = find.toLowerCase(),
    idx;

  while ((idx = remainder.toLowerCase().indexOf(lowFind)) !== -1) {
    str += remainder.substr(0, idx) + replace;

    remainder = remainder.substr(idx + find.length);
  }

  return str + remainder;
}

function escapeRegex(value) {
  return value.replace(/[\-\[\]{}()*+?.,\\^$|#\s]/g, "\$&");
}

function updateResult() {
  var original = document.getElementById("original").value || "",
    find = document.getElementById("find").value || "",
    replace = document.getElementById("replace").value || "",
    resultEl = document.getElementById("result"),
    regexEl = document.getElementById("regex");

  if (original && find && replace) {
    regexEl.value = original.replace(new RegExp(escapeRegex(find), "gi"), replace);
    resultEl.value = insensitiveReplaceAll(original, find, replace);
  } else {
    regexEl.value = "";
    resultEl.value = "";
  }


}

document.addEventListener("input", updateResult);
window.addEventListener("load", updateResult);
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css" rel="stylesheet" />

<div class="input-group input-group-sm">
  <span class="input-group-addon">Original</span>
  <input class="form-control" id="original" value="Find aBcc&def stuff ABCabc" />
</div>

<div class="input-group input-group-sm">
  <span class="input-group-addon">Find</span>
  <input class="form-control" id="find" value="abc" />
</div>

<div class="input-group input-group-sm">
  <span class="input-group-addon">Replace</span>
  <input class="form-control" id="replace" value="ab" />
</div>

<div class="input-group input-group-sm">
  <span class="input-group-addon">Result w/o regex</span>
  <input disabled class="form-control" id="result" />
</div>

<div class="input-group input-group-sm">
  <span class="input-group-addon">Result w/ regex</span>
  <input disabled class="form-control" id="regex" />
</div>

已批准的解决方案在循环内调用 toLowerCase,效率不高。

以下是改进版:

function insensitiveReplaceAll(s, f, r) {
  const lcs=s.toLowerCase(), lcf = f.toLowerCase(), flen=f.length;
  let res='', pos=0, next=lcs.indexOf(lcf, pos);
  if (next===-1) return s;
  
  do {
    res+=s.substring(pos, next)+r;
    pos=next+flen;
  } while ((next=lcs.indexOf(lcf, pos)) !== -1);
  
  return res+s.substring(pos);
}

console.log(insensitiveReplaceAll("Find aBc&deF abcX", "abc", "xy"));
console.log(insensitiveReplaceAll("hello", "abc", "xy"));

使用 jsPerf 进行测试 - https://jsperf.com/replace-case-insensitive-2/1 - 显示速度提高了 37%。