从字符串中删除特定字符

224

JavaScript中与此 C# 方法相等的是什么JavaScript?

var x = "|f|oo||"; 
var y = x.Trim('|'); //  "f|oo"

C# 仅会在字符串开头结尾修剪所选字符!


2
我感到困惑的是,在ECMAScript中trim函数不接受参数。 - Martin Braun
22个回答

280

一行足矣:

var x = '|f|oo||';
var y = x.replace(/^\|+|\|+$/g, '');
document.write(x + '<br />' + y);

^     beginning of the string
\|+   pipe, one or more times
|     or
\|+   pipe, one or more times
$     end of the string

一个通用解决方案:

function trim (s, c) {
  if (c === "]") c = "\\]";
  if (c === "^") c = "\\^";
  if (c === "\\") c = "\\\\";
  return s.replace(new RegExp(
    "^[" + c + "]+|[" + c + "]+$", "g"
  ), "");
}

chars = ".|]\\^";
for (c of chars) {
  s = c + "foo" + c + c + "oo" + c + c + c;
  console.log(s, "->", trim(s, c));
}

参数 c 要求为字符(长度为1的字符串)。

如评论所述,支持多个字符可能会很有用,例如常见的修剪多个空格字符。要实现这一点,MightyPork建议将if语句替换为以下代码行:

c = c.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');

这部分内容 [-/\\^$*+?.()|[\]{}] 是正则表达式语法中的一组特殊字符,$& 是一个占位符,代表匹配的字符,意味着 replace 函数会转义特殊字符。在您的浏览器控制台中尝试:

> "{[hello]}".replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&')
"\{\[hello\]\}"

1
将“通用解决方案”中的两个if替换为正则表达式转义,以安全地支持多个字符:c = c.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&') - MightyPork
@MightyPork 只有 ^ 漏掉了。谢谢。 - user1636522
我认为你最好进行完全转义以确保安全,另一个可能会引起问题的字符是“-”。 - MightyPork
1
哦,它仍然不支持多个字符。想象一下,你想修剪制表符和空格,所以你使用 "\t ",这种用例。 - MightyPork
@MightyPork 我已经尝试使用 -(trim("-foo--oo---", '-')),并没有引起任何问题。无论如何,修剪多种类型的空格字符的想法听起来很有趣,我将在原始答案下面总结您的想法。 - user1636522
正则表达式的解释就像你的回答一样美妙!足以让糟糕的一天变得美好!谢谢 ✌️ - Davey

78

更新:为了了解不同解决方案的性能,因此我在这里更新了一个基本的基准测试: https://www.measurethat.net/Benchmarks/Show/12738/0/trimming-leadingtrailing-characters

在Chrome下运行时有一些有趣且意外的结果。 https://www.measurethat.net/Benchmarks/ShowResult/182877

+-----------------------------------+-----------------------+
| Test name                         | Executions per second |
+-----------------------------------+-----------------------+
| Index Version (Jason Larke)       | 949979.7 Ops/sec      |
| Substring Version (Pho3niX83)     | 197548.9 Ops/sec      |
| Regex Version (leaf)              | 107357.2 Ops/sec      |
| Boolean Filter Version (mbaer3000)| 94162.3 Ops/sec       |
| Spread Version (Robin F.)         | 4242.8 Ops/sec        |
+-----------------------------------+-----------------------+
请注意;测试仅针对单个测试字符串进行(需要修剪前导和尾随字符)。此外,此基准仅给出原始速度的指示;还要考虑其他因素,如内存使用情况。
如果您正在处理更长的字符串,我认为这应该通过将分配的字符串数量减少到零或一个来优化大部分其他选项:
function trim(str, ch) {
    var start = 0, 
        end = str.length;

    while(start < end && str[start] === ch)
        ++start;

    while(end > start && str[end - 1] === ch)
        --end;

    return (start > 0 || end < str.length) ? str.substring(start, end) : str;
}

// Usage:
trim('|hello|world|', '|'); // => 'hello|world'

或者如果你想从一组多个字符中剪切:

function trimAny(str, chars) {
    var start = 0, 
        end = str.length;

    while(start < end && chars.indexOf(str[start]) >= 0)
        ++start;

    while(end > start && chars.indexOf(str[end - 1]) >= 0)
        --end;

    return (start > 0 || end < str.length) ? str.substring(start, end) : str;
}

// Usage:
trimAny('|hello|world   ', [ '|', ' ' ]); // => 'hello|world'
// because '.indexOf' is used, you could also pass a string for the 2nd parameter:
trimAny('|hello| world  ', '| '); // => 'hello|world'

编辑:为了好玩,修剪单词(而不是单独字符)

// Helper function to detect if a string contains another string
//     at a specific position. 
// Equivalent to using `str.indexOf(substr, pos) === pos` but *should* be more efficient on longer strings as it can exit early (needs benchmarks to back this up).
function hasSubstringAt(str, substr, pos) {
    var idx = 0, len = substr.length;

    for (var max = str.length; idx < len; ++idx) {
        if ((pos + idx) >= max || str[pos + idx] != substr[idx])
            break;
    }

    return idx === len;
}

function trimWord(str, word) {
    var start = 0,
        end = str.length,
        len = word.length;

    while (start < end && hasSubstringAt(str, word, start))
        start += word.length;

    while (end > start && hasSubstringAt(str, word, end - len))
        end -= word.length

    return (start > 0 || end < str.length) ? str.substring(start, end) : str;
}

// Usage:
trimWord('blahrealmessageblah', 'blah');

6
我更倾向于这个解决方案,因为它实际上是高效的,而不仅仅是简单明了。 - tekHedd
1
我同意应该优先使用它。取代了我之前给出的答案。 - TamusJRoyce
如果你想要完全使用C#的trim替换,你可以将这个答案作为一个扩展方法(原型扩展?)呈现... String.prototype.trimChar = String.prototype.trimChar || function (chr) { // ... 如果你不想将其作为扩展,你也可以嗅探trim(undefined, "|") - ruffin

51

如果我理解正确,您希望仅在字符串的开头或结尾删除特定字符(例如:||fo||oo|||| 应变为 foo||oo)。您可以创建一个如下的临时函数:

function trimChar(string, charToRemove) {
    while(string.charAt(0)==charToRemove) {
        string = string.substring(1);
    }

    while(string.charAt(string.length-1)==charToRemove) {
        string = string.substring(0,string.length-1);
    }

    return string;
}

我使用以下代码测试了这个函数:

var str = "|f|oo||";
$( "#original" ).html( "Original String: '" + str + "'" );
$( "#trimmed" ).html( "Trimmed: '" + trimChar(str, "|") + "'" );

10
这会是对垃圾收集器的一项有趣测试,但我不建议将其应用于您的客户。 - Sorensen
1
我最喜欢这个答案的原因是:我可以阅读它并且理解它。垃圾收集器将正常工作。使用这个解决方案,我可以应用字符串而不是字符,并且我仍然可以阅读和理解它所做的事情,而无需深入了解正则表达式语法。 - Nasenbaer
@Nasenbaer 至少你应该处理边缘情况,当字符串没有更多字符可移除时。不确定在浏览器的所有当前和未来实现中,string.charAt(-1)是否都能起作用。 - SwissCoder
一个可能需要处理的边缘情况是传入的字符串未定义。处理它的两种方式可以是在顶部添加:string=String(string) ...或者... if (!string) string='',每种方式都有稍微不同的行为... - Mikael Lindqvist

26

您可以使用正则表达式,例如:

var x = "|f|oo||";
var y = x.replace(/^\|+|\|+$/g, "");
alert(y); // f|oo

更新:

如果你希望将这个操作通用化成为一个函数,你可以按照以下步骤进行:

var escapeRegExp = function(strToEscape) {
    // Escape special characters for use in a regular expression
    return strToEscape.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
};

var trimChar = function(origString, charToTrim) {
    charToTrim = escapeRegExp(charToTrim);
    var regEx = new RegExp("^[" + charToTrim + "]+|[" + charToTrim + "]+$", "g");
    return origString.replace(regEx, "");
};

var x = "|f|oo||";
var y = trimChar(x, "|");
alert(y); // f|oo

24

一种不使用正则表达式且易于阅读的版本:

const trim = (str, chars) => str.split(chars).filter(Boolean).join(chars);

对于使用情况,我们确定边缘字符不会重复。


非常有趣...所以split对于每个分割符相等的部分返回未定义的元素 `const trim = (str, chars) => str.split(chars).filter(x => { Boolean(x); console.log(typeof(x), x, Boolean(x)); }).join(chars);const str = "#//#//abc#//test#//end#//"; console.log(trim(str, '#//'));` - TamusJRoyce
如果字符串中间有重复的字符,则此方法无法正常工作。 trim("|first||last|", "|") 返回 first|last(仅在中间有一个管道符号)。 - Cornelius
你说得对,这就是我想表达的“边缘字符不重复”的意思。 - mbaer3000
很棒的解决方案!我需要清除路径中的前导和尾随斜杠path.split('/').filter(Boolean).join('/')这对我来说非常有效 :-)非常感谢! - Alexander Gavriliuk
1
这不是一个修剪操作,而是一个字符替换。 - albanx
@AlexanderGavriliuk 那正是我的使用情况,太棒了 :-) - mbaer3000

18
为了保持这个问题的最新状态:
这里是一种我会选择使用 ES6 扩展运算符而不是正则表达式函数的方法。
function trimByChar(string, character) {
  const first = [...string].findIndex(char => char !== character);
  const last = [...string].reverse().findIndex(char => char !== character);
  return string.substring(first, string.length - last);
}

@fabian提出评论后的改进版(可以处理仅包含相同字符的字符串)

function trimByChar1(string, character) {
  const arr = Array.from(string);
  const first = arr.findIndex(char => char !== character);
  const last = arr.reverse().findIndex(char => char !== character);
  return (first === -1 && last === -1) ? '' : string.substring(first, string.length - last);
}


3
我知道在这里使用正则表达式有些过度,但你为什么会选择这种具体的实现方式呢? - Nicholas Shanks
2
我选择这个实现是因为我个人认为它易于阅读。不使用正则表达式是因为正则表达式引擎内的决策“树”更大。特别是用于修剪的正则表达式包含查询字符,这会导致在正则表达式引擎内进行回溯。这种引擎通常将模式编译成类似机器指令的字节码。然后引擎执行代码,从一条指令跳到另一条指令。当一条指令失败时,它会回溯以找到匹配输入的另一种方式。因此,比必要的操作要多得多。 - Robin F.
1
@RobinF. 你认为 findIndex() 和 reverse() 不包含循环吗?再想想。 - Andrew
1
两个注释:仅包含要修剪字符的字符串将不会被修剪。另一个要点是:使用展开运算符将字符串分解为数组会使Babel混淆并将其转换为[].concat(string),这不是期望的结果。使用Array.from(string)将起作用。 - Fabian
1
我们为什么需要整个“reverse”过程呢?arr.lastIndexOf(character) 不够吗? - Andrii M4n0w4R
显示剩余11条评论

14

这可以一次修剪多个字符:

function trimChars (str, c) {
  var re = new RegExp("^[" + c + "]+|[" + c + "]+$", "g");
  return str.replace(re,"");
}

var x = "|f|oo||"; 
x =  trimChars(x, '|'); // f|oo

var y = "..++|f|oo||++..";
y = trimChars(y, '|.+'); // f|oo

var z = "\\f|oo\\"; // \f|oo\

// For backslash, remember to double-escape:
z = trimChars(z, "\\\\"); // f|oo

如果您需要在自己的脚本中使用,并且不介意更改原型,那么这可以是一个方便的“技巧”:

String.prototype.trimChars = function (c) {
  var re = new RegExp("^[" + c + "]+|[" + c + "]+$", "g");
  return this.replace(re,"");
}

var x = "|f|oo||"; 
x =  x.trimChars('|'); // f|oo

由于我在一个脚本中频繁使用trimChars函数,所以我更喜欢这个解决方案。但是修改对象的原型可能会带来潜在问题。


1
var trimChars = (x, y) => x.replace(new RegExp(\^[${y}]+|[${y}]+$`, "g"),"");` - 一行代码 - Alex Szücs

4
如果你在你的程序中定义这些函数,那么你的字符串将拥有一个升级版的trim函数,可以修剪所有指定的字符:

String.prototype.trimLeft = function(charlist) {
 if (charlist === undefined)
 charlist = "\s";

 return this.replace(new RegExp("^[" + charlist + "]+"), "");
};

String.prototype.trim = function(charlist) {
 return this.trimLeft(charlist).trimRight(charlist);
};

String.prototype.trimRight = function(charlist) {
 if (charlist === undefined)
 charlist = "\s";

 return this.replace(new RegExp("[" + charlist + "]+$"), "");
};

var withChars = "/-center-/"
var withoutChars = withChars.trim("/-")
document.write(withoutChars)

Source

https://www.sitepoint.com/trimming-strings-in-javascript/


不错的解决方案,只是人们要小心覆盖原生函数,比如trim。为避免意外问题,您可以使用不同的命名约定(String.prototype.MyOwn_trim),或者只需在String.prototype之外创建简单的函数即可。 - gsubiran

4
const trim = (str, char) => {
    let i = 0;
    let j = str.length-1;
    while (str[i] === char) i++;
    while (str[j] === char) j--;
    return str.slice(i,j+1);
}
console.log(trim('|f|oo|', '|')); // f|oo

非正则表达式解决方案。 双指针:i(起始)和j(结束)。 仅当指针匹配字符时移动,不匹配时停止。 返回剩余的字符串。


2
我建议您查看lodash库以及他们如何实现trim函数。
请参考Lodash Trim获取文档,或者查看源代码以了解确切的剪切代码。
虽然这并没有直接回答您的问题,但我认为在这样的问题上引用库是有益的,因为其他人可能会发现它很有用。

1
@TamusJRoyce 不一样 - gdbdable
@devi 我完全同意。感谢您的评论。好答案,正在寻找社区支持的工具。 - TamusJRoyce
lodash是MIT许可证下的。为了使答案更好,我建议引入源代码,并将对你有用的内容呈现出来。然后,如果该链接发生变化,好的信息仍然存在于你的答案中。 - ruffin
lodash中当前的修剪实现使用了6个其他的lodash函数,这些函数又使用了其他的lodash函数... 如果你选择这条路,那么你可以在你的项目中直接导入lodash。 - SwissCoder

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接