replace 替換全部的正確姿勢


本文同步自我的個人博客:http://www.52cik.com/2015/11/06/replace-all.html

關於字符串替換問題,其實是個很簡單的問題,但卻也不那么簡單,至少對於很多新手而言,全局替換一直是個坑。

簡單而強大的正則

可能你覺得要替換全局,就改成正則,然后加個 g 全局匹配就好了,例如:

var str = "test-test-test";
str = "test-test-test".replace("test", "ok");
console.log(str);

改成正則:

var str = "test-test-test";
str = "test-test-test".replace(/test/g, "ok");
console.log(str);

確實非常簡單,但是如果出現需要轉義的字符呢?
當然也難不倒大家,但是對於新手而言,正則就是一座無形的大山,完全無法越過。

無奈的正則

例如表情標簽的替換呢?難道全部改成正則?那也要會正則的人花不少體力才能搞定吧。
今天群里的朋友就遇到這么個問題,表情標簽如下:

var faces = {
  "/::)": "weixiao",
  "/::~": "pizui",
  "/::B": "se",
  "/::|": "fadai",
  "/:8-)": "deyi",
  "/::<":"liulei",
  "/::$": "haixiu",
  "/::'(": "daku",
  "/::-|": "gangga"
};

當然我只是截取了部分,好像有好幾十個,如果全部改成正則,需要不少體力呢。

循環替換

這種情況,我們需要正常的字符串替換,例如結合 while + indexOf 實現。

var faces = {
  "/::)": "weixiao",
  "/::~": "pizui",
  "/::B": "se",
  "/::|": "fadai",
  "/:8-)": "deyi",
  "/::<":"liulei",
  "/::$": "haixiu",
  "/::'(": "daku",
  "/::-|": "gangga"
};

var str = "/::)-/::B-/::)-/:8-)-/:8-)";

for (var k in faces) {
  while(str.indexOf(k) > -1) {
    str = str.replace(k, faces[k]);
  }
}

console.log(str);

這樣,基本功能實現,不過這是有問題的,如果有一個鍵值相同的,就會死循環例如:

var faces = {
  "/::)": "weixiao",
  "/:hehe": "/:hehe"
};

var str = "/::)-/::B-/:hehe-/:8-)-/:8-)";

for (var k in faces) {
  while(str.indexOf(k) > -1) {
    str = str.replace(k, faces[k]);
  }
}

console.log(str);

這樣,就呵呵了,當然不一定會有這樣的情況,但也不能肯定一定沒有這樣的情況。

改進循環

我們需要用到 indexOf 的第二個參數來規避這種情況,改進后的代碼如下:

var faces = {
  "/::)": "weixiao",
  "/:hehe": "/:hehe"
};

var str = "/::)-/::B-/:hehe-/:8-)-/:8-)";

for (var k in faces) {
  var p = -1; // 字符出現位置
  var s = 0; // 下一次起始位置
  while((p = str.indexOf(k, s)) > -1) {
    s = p + faces[k].length; // 位置 + 值的長度
    str = str.replace(k, faces[k]);
  }
}

console.log(str);

好了,現在這樣就沒問題了,也不用擔心死循環問題,而且替換大段文字的時候,性能比正則要好。 經過測試,確實正則快。
在大段正則匹配的時候,回溯會導致匹配性能問題,所以才一直認為正則慢,而這種情況的正則,不需要回溯,性能自然也極佳。

為了方便新手朋友使用,下面寫個函數吧。

/**
 * 字符串替換
 * @param  {string} str    要被替換的字符串
 * @param  {string} substr 要替換的字符串
 * @param  {string} newstr 用於替換的字符串
 * @return {string}        替換后的新字符串
 */
function replace(str, substr, newstr) {
  var p = -1; // 字符出現位置
  var s = 0; // 下一次起始位置

  while((p = str.indexOf(substr, s)) > -1) {
    s = p + newstr.length; // 位置 + 值的長度
    str = str.replace(substr, newstr);
  }

  return str;
}

console.log( replace("ssssss", "ss", "s") ); // sss

總結

我發現,我也只能寫寫這些小東西,唉。。

后記

在 4樓 Antineutrino 的測試中得知,正則方法比 while + indexOf 性能更佳。
之前一直認為正則就是慢的代名詞,所以直接忽略了正則方法,現在又重新認識了正則,正則果然犀利。

抽空寫了個正則版本的,把元字符種的幾個符號轉義下,就可以生成正則了。
網上那些正則版本中沒有轉義直接 new RegExp 的會導致各種bug,所以要處理下元字符的那些符號。

/**
 * 字符串替換
 * @param  {string} str    要被替換的字符串
 * @param  {string} substr 要替換的字符串
 * @param  {string} newstr 用於替換的字符串
 * @return {string}        替換后的新字符串
 */
function replace(str, substr, newstr) {
  substr = substr.replace(/[.\\[\]{}()|^$?*+]/g, "\\$&"); // 轉義字符串中的元字符
  var re = new RegExp(substr, "g"); // 生成正則
  return str.replace(re, newstr);
}

console.log( replace("ssssss", "ss", "s") ); // sss


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM