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

1803

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

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

haystack.startsWith(needle) == true

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

19个回答

5
最佳性能解决方案是停止使用库函数,并认识到你正在处理两个数组。手写的实现既简短又比我在这里看到的其他解决方案更快。
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。(确保您检查了可能超过我的版本的后续版本。)


3
  1. 虽然这个问题有点老,但我想写下这个答案来展示一些基于这里提供的所有答案和Jim Buck分享的jsperf的基准测试。

我基本上需要一种快速的方法来查找一个长的针是否在一个长的干草堆中,并且它们非常相似,除了最后的字符。

以下是我编写的代码,对于每个函数(splice、substring、startsWith等),它会针对一个包含10000001个字符的干草堆字符串(nestedString)以及一个包含1000000个字符的假或真针字符串(分别为testParentStringFalsetestParentStringTrue)进行测试,测试它们返回false和true的情况:

// nestedString is made of 1.000.001 '1' repeated characters.
var nestedString = '...'

// testParentStringFalse is made of 1.000.000 characters,
// all characters are repeated '1', but the last one is '2',
// so for this string the test should return false.
var testParentStringFalse = '...'

// testParentStringTrue is made of 1.000.000 '1' repeated characters,
// so for this string the test should return true.
var testParentStringTrue = '...'

// You can make these very long strings by running the following bash command
// and edit each one as needed in your editor
// (NOTE: on OS X, `pbcopy` copies the string to the clipboard buffer,
//        on Linux, you would probably need to replace it with `xclip`):
// 
//     printf '1%.0s' {1..1000000} | pbcopy
// 

function testString() {
    let dateStart
    let dateEnd
    let avg
    let count = 100000
    const falseResults = []
    const trueResults = []

    /* slice */
    console.log('========> slice')
    dateStart = +new Date()
    var res
    for (let j = 0; j < count; j++) {
        res = nestedString.slice(0, testParentStringFalse.length) === testParentStringFalse
    }
    dateEnd = +new Date()
    avg = (dateEnd - dateStart)/count
    falseResults[falseResults.length] = {
        label: 'slice',
        avg
    }
    console.log(`testString() slice = false`, res, 'avg: ' + avg + 'ms')

    dateStart = +new Date()
    var res
    for (let j = 0; j < count; j++) {
        res = nestedString.slice(0, testParentStringTrue.length) === testParentStringTrue
    }
    dateEnd = +new Date()
    avg = (dateEnd - dateStart)/count
    trueResults[trueResults.length] = {
        label: 'slice',
        avg
    }
    console.log(`testString() slice = true`, res, 'avg: ' + avg + 'ms')
    console.log('<======== slice')
    console.log('')
    /* slice END */

    /* lastIndexOf */
    console.log('========> lastIndexOf')
    dateStart = +new Date()
    var res
    for (let j = 0; j < count; j++) {
        res = nestedString.lastIndexOf(testParentStringFalse, 0) === 0
    }
    dateEnd = +new Date()
    avg = (dateEnd - dateStart)/count
    falseResults[falseResults.length] = {
        label: 'lastIndexOf',
        avg
    }
    console.log(`testString() lastIndexOf = false`, res, 'avg: ' + avg + 'ms')

    dateStart = +new Date()
    var res
    for (let j = 0; j < count; j++) {
        res = nestedString.lastIndexOf(testParentStringTrue, 0) === 0
    }
    dateEnd = +new Date()
    avg = (dateEnd - dateStart)/count
    trueResults[trueResults.length] = {
        label: 'lastIndexOf',
        avg
    }
    console.log(`testString() lastIndexOf = true`, res, 'avg: ' + avg + 'ms')
    console.log('<======== lastIndexOf')
    console.log('')
    /* lastIndexOf END */

    /* indexOf */
    console.log('========> indexOf')
    dateStart = +new Date()
    var res
    for (let j = 0; j < count; j++) {
        res = nestedString.indexOf(testParentStringFalse) === 0
    }
    dateEnd = +new Date()
    avg = (dateEnd - dateStart)/count
    falseResults[falseResults.length] = {
        label: 'indexOf',
        avg
    }
    console.log(`testString() indexOf = false`, res, 'avg: ' + avg + 'ms')

    dateStart = +new Date()
    var res
    for (let j = 0; j < count; j++) {
        res = nestedString.indexOf(testParentStringTrue) === 0
    }
    dateEnd = +new Date()
    avg = (dateEnd - dateStart)/count
    trueResults[trueResults.length] = {
        label: 'indexOf',
        avg
    }
    console.log(`testString() indexOf = true`, res, 'avg: ' + avg + 'ms')
    console.log('<======== indexOf')
    console.log('')
    /* indexOf END */

    /* substring */
    console.log('========> substring')
    dateStart = +new Date()
    var res
    for (let j = 0; j < count; j++) {
        res = nestedString.substring(0, testParentStringFalse.length) === testParentStringFalse
    }
    dateEnd = +new Date()
    avg = (dateEnd - dateStart)/count
    falseResults[falseResults.length] = {
        label: 'substring',
        avg
    }
    console.log(`testString() substring = false`, res, 'avg: ' + avg + 'ms')

    dateStart = +new Date()
    var res
    for (let j = 0; j < count; j++) {
        res = nestedString.substring(0, testParentStringTrue.length) === testParentStringTrue
    }
    dateEnd = +new Date()
    avg = (dateEnd - dateStart)/count
    trueResults[trueResults.length] = {
        label: 'substring',
        avg
    }
    console.log(`testString() substring = true`, res, 'avg: ' + avg + 'ms')
    console.log('<======== substring')
    console.log('')
    /* substring END */

    /* startsWith */
    console.log('========> startsWith')
    dateStart = +new Date()
    var res
    for (let j = 0; j < count; j++) {
        res = nestedString.startsWith(testParentStringFalse)
    }
    dateEnd = +new Date()
    avg = (dateEnd - dateStart)/count
    falseResults[falseResults.length] = {
        label: 'startsWith',
        avg
    }
    console.log(`testString() startsWith = false`, res, 'avg: ' + avg + 'ms')

    dateStart = +new Date()
    var res
    for (let j = 0; j < count; j++) {
        res = nestedString.startsWith(testParentStringTrue)
    }
    dateEnd = +new Date()
    avg = (dateEnd - dateStart)/count
    trueResults[trueResults.length] = {
        label: 'startsWith',
        avg
    }
    console.log(`testString() startsWith = true`, res, 'avg: ' + avg + 'ms')
    console.log('<======== startsWith')
    console.log('')
    /* startsWith END */

    falseResults.sort((a, b) => a.avg - b.avg)
    trueResults.sort((a, b) => a.avg - b.avg)

    console.log('false results from fastest to slowest avg:', falseResults)
    console.log('true results from fastest to slowest avg:', trueResults)
}


我在Chrome 75,Firefox 67,Safari 12和Opera 62上运行了此基准测试。 我没有包含Edge和IE,因为我在这台机器上没有它们,但如果你们中的某个人想针对Edge和至少IE 9运行脚本并在此处共享输出,我会非常好奇地看到结果。请记住,您需要重新创建3个长字符串并将脚本保存在文件中,然后在浏览器中打开该文件,因为将字符串的长度都大于等于1,000,000 的复制/粘贴到浏览器控制台将会被阻止。以下是输出:在Chrome 75中(substring获胜):
false results from fastest to slowest avg:
1)  {"label":"substring","avg":0.08271}
2)  {"label":"slice","avg":0.08615}
3)  {"label":"lastIndexOf","avg":0.77025}
4)  {"label":"indexOf","avg":1.64375}
5)  {"label":"startsWith","avg":3.5454}

true results from fastest to slowest avg:
1)  {"label":"substring","avg":0.08213}
2)  {"label":"slice","avg":0.08342}
3)  {"label":"lastIndexOf","avg":0.7831}
4)  {"label":"indexOf","avg":0.88988}
5)  {"label":"startsWith","avg":3.55448}

Firefox 67 (indexOf 获胜):

false results from fastest to slowest avg
1)  {"label":"indexOf","avg":0.1807}
2)  {"label":"startsWith","avg":0.74621}
3)  {"label":"substring","avg":0.74898}
4)  {"label":"slice","avg":0.78584}
5)  {"label":"lastIndexOf","avg":0.79668}

true results from fastest to slowest avg:
1)  {"label":"indexOf","avg":0.09528}
2)  {"label":"substring","avg":0.75468}
3)  {"label":"startsWith","avg":0.76717}
4)  {"label":"slice","avg":0.77222}
5)  {"label":"lastIndexOf","avg":0.80527}

Safari 12(slice在错误结果方面表现最佳,startsWith在正确结果方面表现最佳,并且Safari在执行整个测试的总时间方面是最快的):

false results from fastest to slowest avg:
1) "{\"label\":\"slice\",\"avg\":0.0362}"
2) "{\"label\":\"startsWith\",\"avg\":0.1141}"
3) "{\"label\":\"lastIndexOf\",\"avg\":0.11512}"
4) "{\"label\":\"substring\",\"avg\":0.14751}"
5) "{\"label\":\"indexOf\",\"avg\":0.23109}"

true results from fastest to slowest avg:
1) "{\"label\":\"startsWith\",\"avg\":0.11207}"
2) "{\"label\":\"lastIndexOf\",\"avg\":0.12196}"
3) "{\"label\":\"substring\",\"avg\":0.12495}"
4) "{\"label\":\"indexOf\",\"avg\":0.33667}"
5) "{\"label\":\"slice\",\"avg\":0.49923}"

Opera 62中substring获胜。这个结果与Chrome类似,这并不让人感到惊讶,因为Opera基于Chromium和Blink。

false results from fastest to slowest avg:
{"label":"substring","avg":0.09321}
{"label":"slice","avg":0.09463}
{"label":"lastIndexOf","avg":0.95347}
{"label":"indexOf","avg":1.6337}
{"label":"startsWith","avg":3.61454}

true results from fastest to slowest avg:
1)  {"label":"substring","avg":0.08855}
2)  {"label":"slice","avg":0.12227}
3)  {"label":"indexOf","avg":0.79914}
4)  {"label":"lastIndexOf","avg":1.05086}
5)  {"label":"startsWith","avg":3.70808}

原来每个浏览器都有自己的实现细节(除了基于Chrome的Chromium和Blink的Opera)。

当然,应该进行更多不同用例的测试(例如当needle相对于haystack非常短时,当haystack比needle短时等等),但在我的情况下我需要比较非常长的字符串,并想在这里分享它。


2

我刚刚了解到这个字符串库:

http://stringjs.com/

引入js文件,然后像这样使用S变量:

S('hi there').endsWith('hi there')

它也可以通过安装在NodeJS中使用:

npm install string

然后将其作为S变量要求:

var S = require('string');

网页还提供了其他字符串库的链接,如果这个不符合你的需求。

1
var str = 'hol';
var data = 'hola mundo';
if (data.length >= str.length && data.substring(0, str.length) == str)
    return true;
else
    return false;

0

我不确定JavaScript,但在TypeScript中我做了类似的事情

var str = "something";
(<String>str).startsWith("some");

我猜它也应该在js上运行。希望能帮到你!


0
根据这里的答案,这是我现在正在使用的版本,因为它似乎在JSPerf测试中提供了最佳性能(并且在我看来已经完全实现了功能)。
if(typeof String.prototype.startsWith != 'function'){
    String.prototype.startsWith = function(str){
        if(str == null) return false;
        var i = str.length;
        if(this.length < i) return false;
        for(--i; (i >= 0) && (this[i] === str[i]); --i) continue;
        return i < 0;
    }
}

这是基于来自http://jsperf.com/startswith2/6的startsWith2,我添加了一个小调整以提高性能,并且后来还添加了一个检查比较字符串是否为null或undefined的功能,并使用CMS答案中的技术将其转换为添加到String原型中。

请注意,此实现不支持在Mozilla开发者网络页面中提到的“position”参数,但那似乎也不是ECMAScript建议的一部分。


0
对于两个字母的针头来说,这仍然是性能最佳的选择。
var haystack = 'hello world';

if(haystak[0] === 'h' && haystak[1] === 'e'){
  // Needle found
}

不然就是,对照其他答案来做。

-1

如果您正在使用 startsWith()endsWith() 函数,那么您需要注意前导空格。以下是一个完整的示例:

var str1 = " Your String Value Here.!! "; // Starts & ends with spaces    
if (str1.startsWith("Your")) { }  // returns FALSE due to the leading spaces…
if (str1.endsWith("Here.!!")) { } // returns FALSE due to trailing spaces…

var str2 = str1.trim(); // Removes all spaces (and other white-space) from start and end of `str1`.
if (str2.startsWith("Your")) { }  // returns TRUE
if (str2.endsWith("Here.!!")) { } // returns TRUE

3
这是非常不标准的行为:字符串“ abc”并不以“abc”开头。具体来说,ECMA 6没有做任何字符串修剪的假设,因此空格必须完全匹配才能得到startsWith匹配。 - Steve Hollasch
3
这个回答是怎么回事? - DCShannon
1
@DCShannon 这不是。这是无法理解的胡言乱语。 - Mark Amery
2
@SteveHollasch 我的意图是让面临相同问题的任何人都能意识到,当使用 startsWith()endsWith() 函数时,我们需要注意前导空格。仅此而已! - immayankmodi

-3
你也可以通过创建自己的原型/扩展到数组原型来返回以某个字符串开头的数组成员,也称为:
Array.prototype.mySearch = function (target) {
    if (typeof String.prototype.startsWith != 'function') {
        String.prototype.startsWith = function (str){
        return this.slice(0, str.length) == str;
      };
    }
    var retValues = [];
    for (var i = 0; i < this.length; i++) {
        if (this[i].startsWith(target)) { retValues.push(this[i]); }
    }
    return retValues;
};

使用它的方法:

var myArray = ['Hello', 'Helium', 'Hideout', 'Hamster'];
var myResult = myArray.mySearch('Hel');
// result -> Hello, Helium

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