如何检查一个字符串是否以另一个字符串开头?

1803

我该如何在JavaScript中编写与C#的String.StartsWith功能相同的代码?

var haystack = 'hello world';
var needle = 'he';

haystack.startsWith(needle) == true

注意:这是一个旧问题,正如评论中指出的那样,ECMAScript 2015(ES6)引入了 .startsWith 方法。然而,在撰写本次更新(2015年)时,浏览器支持还远未完整

19个回答

1856
您可以使用 ECMAScript 6 的 String.prototype.startsWith() 方法。它已经得到了所有主流浏览器的支持。但是,如果您想在不支持该方法的浏览器中使用它,则需要使用一个 shim/polyfill 在这些浏览器上添加它。创建一个符合规范中的所有细节的实现有点复杂。如果您想要一个忠实的 shim,请使用以下之一:

一旦您使用 shim(或者仅支持已经拥有该方法的浏览器和 JavaScript 引擎),您可以像这样使用它:

console.log("Hello World!".startsWith("He")); // true

var haystack = "Hello world";
var prefix = 'orl';
console.log(haystack.startsWith(prefix)); // false


1
警告!这些 jsperf 测试在那些擅长 JIT 编译的浏览器中不起作用。像 Firefox 和 Chrome 这样的浏览器有时会在操作结果被丢弃时识别它,因此不执行该操作。除此之外,现代 JavaScript 引擎使用分支预测,因此测试字符串应在每次迭代中都不同。 - Aloso
请注意:如果在构建项目时 TypeScript 报错,请确保在 tsconfig.json 的 lib 数组中至少包含 "es2015.core"。 - baywet

1315

使用.lastIndexOf的另一种替代方法:

haystack.lastIndexOf(needle) === 0

该方法从字符串haystack的长度开始向后检查needle的出现情况,直到第0个索引位置。换句话说,它只检查haystack是否以needle开头。

lastIndexOf方法提供了一个可选参数'fromIndex'。如果给定,则向后搜索从此给定的索引位置开始,并向后遍历到索引零。但我们必须不指定任何除最后一个索引之外的fromIndex,否则搜索可能会忽略某些内容。

原则上,该方法应比某些其他方法具有性能优势:

  • 它不搜索整个haystack
  • 它不创建新的临时字符串,然后立即丢弃它。

1
不确定@rfcoder89在说哪个案例-https://jsfiddle.net/jkzjw3w2/1/ - Gulfaraz Rahman
8
请注意lastIndexOf的第二个参数:"aba".lastIndexOf("a")返回2,正如你所指出的,但是"aba".lastIndexOf("a", 0)返回0,这是正确的结果。请注意第二个参数表示搜索的起始位置。 - maxpolk
2
非常感谢。String.startsWith 在 Android 棒棒糖 WebView 上无法工作,但是这个 lastIndexOf 代码片段可以! - Hermandroid
9
不,如果startIndex为0,则从0位置开始搜索,并且这是唯一的检查。仅当fromIndex >= str.length时,才会搜索整个字符串。 - greene
1
这个想法很好,但在我看来将开始设置为零是错误的。因为我们想要一个startswith。当省略第二个参数时,默认值为字符串长度。因为它从字符串的末尾向前遍历,所以我们当然应该从最后开始(=字符串长度)。如果你从零开始,搜索只会检查第一个字符,什么都不会发生(正如@greene已经评论过的那样)。修改这个答案会很好。 - Kaspatoo
显示剩余4条评论

601
data.substring(0, input.length) === input

3
我猜这很大程度上取决于使用的浏览器和数据。请参考Ben Weaver的回答以获取实际测量结果。在我目前使用的浏览器(Windows上的Chrome 12.0.742)中,子字符串在成功时胜出,而准备好的正则表达式在失败时胜出。 - cobbal
4
@cobbal 可能吧。但是.lastIndexOf(input, 0)只比较前N个字符,而.substring(0, input.length) === input会计算N的值,将数据切割成N长度的子串,然后再比较这N个字符。除非代码进行了优化,否则第二种方法不能比第一种更快。不过别误解,我自己也不可能想出比你建议的更好的方法。 :) - ANeves
2
@ANeves 但是在一个很长的字符串上执行 .lastIndexOf 如果返回 false,那么它将遍历整个字符串(O(N)),而 .substring 的情况则只会遍历可能更小的字符串。如果你预计大多数情况都是成功的或者只有小输入,那么 .lastIndexOf 可能更快 - 否则 .substring 可能更快。.substring 还存在风险,如果输入比被检查的字符串还要长,则会出现异常。 - Chris Moschini
14
@ChrisMoschini,请注意,Mark Byers的解决方案使用的是lastIndexOf从索引0开始计算,而不是从结尾开始计算。这也曾经让我困扰过。尽管有各种巧妙的方法和替代方案,但检查一个字符串以什么开头还是一个很常见的任务,JavaScript应该有一个合适的API来完成它。 - Randall Cook
5
我更喜欢 Cobbal 的解决方案,即使 Mark 的方法更快,而且使用参数是一个令人印象深刻的技巧,但与 substring 相比,它非常难以阅读。 - ThinkBonobo
在我看来,Mark的代码更易读且更容易理解,因为它使用了“startsWith”测试。虽然这个解决方案有些棘手,但很难理解其中的逻辑。实际上,我更喜欢momomo的代码,也是我使用的代码。 - Neal Davis

186

不使用帮助函数,只使用正则表达式的.test方法:

/^He/.test('Hello world')

如果要使用动态字符串而不是硬编码的字符串来实现这一点(假设该字符串不包含任何正则表达式控制字符):

new RegExp('^' + needle).test(haystack)

如果在字符串中可能出现正则表达式控制字符,那么你应该查看JavaScript 中是否有 RegExp.escape 函数?


1
为了使表达式区分大小写,请使用/^he/i - kaizer1v

78

最佳解决方案:

function startsWith(str, word) {
    return str.lastIndexOf(word, 0) === 0;
}

如果您需要,这里还有一个endsWith方法:

function endsWith(str, word) {
    return str.indexOf(word, str.length - word.length) !== -1;
}

如果您更喜欢将其原型化为字符串:

String.prototype.startsWith || (String.prototype.startsWith = function(word) {
    return this.lastIndexOf(word, 0) === 0;
});

String.prototype.endsWith   || (String.prototype.endsWith = function(word) {
    return this.indexOf(word, this.length - word.length) !== -1;
});
使用方法:
"abc".startsWith("ab")
true
"c".ensdWith("c") 
true

使用方法:

startsWith("aaa", "a")
true
startsWith("aaa", "ab")
false
startsWith("abc", "abc")
true
startsWith("abc", "c")
false
startsWith("abc", "a")
true
startsWith("abc", "ba")
false
startsWith("abc", "ab")
true

我认为你在函数中混淆了lastIndexOf和indexOf的概念 - startsWith应该返回str.indexOf(word, 0) === 0; - Richard Matheson
6
使用indexOf的问题在于,如果它在开头没有匹配成功,它将继续搜索整个字符串,而lastIndexOf则从单词的长度开始向后搜索,直到0。明白了吗? - mjs
2
啊,现在明白了——我没有注意到你使用的索引。非常棒的技巧! - Richard Matheson

55

我只是想表达一下我的观点。

我认为我们可以这样使用:

var haystack = 'hello world';
var needle = 'he';

if (haystack.indexOf(needle) == 0) {
  // Code if string starts with this substring
}

3
@relfor对三种不同的正确方法的性能进行了比较,其中包括Mark Byers的答案。但是这种正确方法并不受青睐,因为它需要搜索整个字符串。 - maxpolk
@maxpolk 我认为 indexOf 会在找到第一个匹配项后停止搜索整个字符串。我已经验证过了。 - Mr.D
9
如果第一个出现的位置不在开头,这种方法会变得越来越低效,可能一直搜索到末尾而不是早早放弃。由于有潜在的低效性,它不是三种正确方法中最受青睐的一种。 - maxpolk
2
@Mr.D 如果没有匹配项呢? - mjs
如果整个“干草堆”都被搜索完了,那么使用else语句会更好:https://dev59.com/DXRB5IYBdhLWcg3wXWO2#36876507 .. 只搜索到单词长度。 - mjs
你可能还想将输入强制转换为字符串,以防万一,否则它将没有indexOf运算符:(String(haystack))。indexOf(needle) - eyal_katz

40

这里是对CMS解决方案的一个小改进:

if(!String.prototype.startsWith){
    String.prototype.startsWith = function (str) {
        return !this.indexOf(str);
    }
}

"Hello World!".startsWith("He"); // true

 var data = "Hello world";
 var input = 'He';
 data.startsWith(input); // true

检查函数是否已存在,以防将来的浏览器在本地代码中实现它或者由另一个库实现。例如,Prototype库已经实现了这个函数。

使用=== 0稍微快一些,更加简洁,但不太易读。


1
这可能会成为一个问题:如果已经实现的代码行为与我的代码不同,那么这将破坏我的应用程序。 - Christoph Wurm
2
这里讨论了O(N)问题,链接为https://dev59.com/DXRB5IYBdhLWcg3wXWO2#36SeEYcBWogLw_1bYSGy。 - Chris Moschini
1
使用!会非常混乱。 - JonnyRaa
-1;将其添加到String.prototype是一个坏主意,因为它与String.prototype.startsWith规范几乎没有任何关系。如果您这样做,任何试图使用ES6方法的代码都有可能失败;它可能会查看该方法是否已经定义,看到它已经被您糟糕地定义了,而不添加符合规范的shim,从而导致以后的行为不正确。 - Mark Amery

21

还可以查看underscore.string.js。 它附带了许多有用的字符串测试和操作方法,包括一个startsWith方法。 从文档中了解:

startsWith _.startsWith(string, starts)

该方法检查string是否以starts开头。

_("image.gif").startsWith("image")
=> true

2
我需要 _.string.startsWith - Colonel Panic

16

我最近也问了自己同样的问题。
有多种可能的解决方案,以下是3个有效的方法:

  • s.indexOf(starter) === 0
  • s.substr(0,starter.length) === starter
  • s.lastIndexOf(starter, 0) === 0(在查看Mark Byers的答案后添加)
  • 使用循环:

function startsWith(s,starter) {
  for (var i = 0,cur_c; i < starter.length; i++) {
    cur_c = starter[i];
    if (s[i] !== starter[i]) {
      return false;
    }
  }
  return true;
}

我还没有遇到最后一种使用循环的解决方案。
令人惊讶的是,这种解决方案比前三种性能表现要好得多。
以下是我进行的jsperf测试,以得出这个结论:http://jsperf.com/startswith2/2

祝好

ps:ecmascript 6(harmony)为字符串引入了一个本地startsWith方法。
如果他们在初始版本中想到包含这个非常需要的方法,将节省多少时间。

更新

正如史蒂夫在本答案的第一条评论中指出的那样,上面的自定义函数会抛出错误,如果给定的prefix短于整个字符串。他已经修复了这个问题,并添加了一个循环优化,可以在http://jsperf.com/startswith2/4中查看。

请注意,史蒂夫包括了两个循环优化,其中第一个表现更好,因此我将在下面发布该代码:

function startsWith2(str, prefix) {
  if (str.length < prefix.length)
    return false;
  for (var i = prefix.length - 1; (i >= 0) && (str[i] === prefix[i]); --i)
    continue;
  return i < 0;
}

请看最新版本。除了上一个版本中的错误(如果字符串比前缀短,它将抛出异常)外,它也比更优化的版本慢。请参见 http://jsperf.com/startswith2/4 和 http://jsperf.com/js-startswith/35。 - Steve Hollasch
感谢指出字符串长度小于前缀的情况。 - Raj Nathani
http://jsperf.com/startswith2/29 => startsWith5 简洁且性能表现非常好 =) - gtournie

11

由于此方法非常受欢迎,我认为值得指出的是,在ECMA 6中有这种方法的实现,为了避免未来出现问题和泪水,应该使用“官方”的polyfill。

幸运的是,Mozilla的专家为我们提供了一个:

https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith

if (!String.prototype.startsWith) {
    String.prototype.startsWith = function(searchString, position) {
        position = position || 0;
        return this.indexOf(searchString, position) === position;
    };
}
请注意,这样做的好处是在转换为ECMA 6时会被优雅地忽略。

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