Как проверить, “начинается” ли строка с другой строки?

StackOverflow https://stackoverflow.com/questions/646628

  •  22-07-2019
  •  | 
  •  

Вопрос

Как бы я написал эквивалент C # 's String.StartsWith в JavaScript?

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

haystack.startsWith(needle) == true

Примечание:Это старый вопрос, и, как указано в комментариях, ECMAScript 2015 (ES6) представил .startsWith способ.Однако на момент написания этого обновления (2015) поддержка браузера далека от завершения.

Это было полезно?

Решение

Вы можете использовать ECMAScript 6 String.prototype.startsWith() метод, но это пока поддерживается не во всех браузерах.Вы захотите использовать прокладку / polyfill, чтобы добавить ее в браузеры, которые ее не поддерживают.Создание реализации, соответствующей все детали изложены в спецификации это немного сложно.Если вам нужна надежная прокладка, используйте либо:

После того, как вы настроили метод (или если вы поддерживаете только браузеры и движки JavaScript, в которых он уже есть), вы можете использовать его следующим образом:

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

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

Другие советы

Другая альтернатива с .lastIndexOf:

haystack.lastIndexOf(needle, 0) === 0

Это выглядит задом наперед haystack для возникновения needle начиная с индекса 0 из haystack.Другими словами, он проверяет только то, является ли haystack начинается с needle.

В принципе, это должно иметь преимущества в производительности по сравнению с некоторыми другими подходами:

  • Он не выполняет поиск по всему haystack.
  • Он не создает новую временную строку, а затем немедленно отбрасывает ее.
data.substring(0, input.length) === input

Без вспомогательной функции, просто используйте reax .test метод:

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

Чтобы сделать это с динамической строкой, а не жестко закодированной (при условии, что строка не будет содержать управляющих символов регулярного выражения):

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

Вы должны проверить Есть ли функция RegExp.escape в Javascript ? если существует вероятность появления в строке управляющих символов регулярного выражения.

Лучшее решение:

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

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

А вот заканчивается с , если вам это тоже нужно:

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

Для тех, кто предпочитает создавать прототип в String:

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

Я просто хотел добавить свое мнение по этому поводу.

Я думаю, что мы можем просто использовать это так:

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

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

Вот небольшое улучшение решения 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

Проверка, существует ли функция в случае, если будущий браузер реализует ее в собственном коде или если она будет реализована другой библиотекой. Например, библиотека прототипов уже реализует эту функцию.

Использование ! немного быстрее и более кратко, чем === 0 , но не так читабельно.

Также ознакомьтесь с underscore.string.js . Он поставляется с набором полезных методов тестирования и манипуляции со строками, включая метод startWith . Из документов:

  

opensWith _. startWith (строка, начинается)

     

Этот метод проверяет, начинается ли string с start .

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

Недавно я задал себе тот же вопрос.
Существует несколько возможных решений, вот 3 допустимых из них:

  • s.indexOf(starter) === 0
  • s.substr(0,starter.length) === starter
  • s.lastIndexOf(starter, 0) === 0 (добавлено после просмотра работы Марка Байерса ответ)
  • использование цикла:

    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;
    }
    

Я не сталкивался с последним решением, которое использует цикл.
Удивительно, но это решение значительно превосходит первые 3 с большим отрывом.
Вот тест jsperf, который я выполнил, чтобы прийти к такому выводу: http://jsperf.com/startswith2/2

Мир

ps:ecmascript 6 (harmony) вводит нативный startsWith метод для строк.
Только подумайте, сколько времени было бы сэкономлено, если бы они додумались включить этот столь необходимый метод в саму первоначальную версию.

Обновить

Как указал Стив (первый комментарий к этому ответу), приведенная выше пользовательская функция выдаст ошибку, если заданный префикс короче, чем вся строка целиком.Он исправил это и добавил оптимизацию цикла, которую можно посмотреть по адресу http://jsperf.com/startswith2/4.

Обратите внимание, что есть 2 оптимизации цикла, которые включил Стив, первая из двух показала лучшую производительность, поэтому я опубликую этот код ниже:

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;
}

Поскольку это настолько популярно, я думаю, что стоит отметить, что в ECMA 6 есть реализация этого метода, и при подготовке к нему следует использовать «официальный» полифилл, чтобы предотвратить будущие проблемы и разрывы.

К счастью, эксперты Mozilla предоставляют нам один:

https://developer.mozilla.org/ де / Docs / Web / JavaScript / Справочник / 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.

Лучшее решение - перестать использовать библиотечные вызовы и просто признать, что вы работаете с двумя массивами. Ручная реализация является одновременно короткой и более быстрой, чем любое другое решение, которое я видел здесь.

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://stringjs.com/

Включите файл js, а затем используйте переменную S следующим образом:

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

Его также можно использовать в NodeJS, установив его:

npm install string

Затем требуется в качестве переменной S :

var S = require('string');

На веб-странице также есть ссылки на библиотеки альтернативных строк, если она вам не по вкусу.

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

Исходя из ответов, приведенных здесь, это версия, которую я сейчас использую, так как она, кажется, дает лучшую производительность на основе тестирования 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 . Я добавил небольшой твик для незначительного улучшения производительности, а также добавил проверку на то, что строка сравнения имеет значение null или undefined, и преобразовал ее, чтобы добавить в прототип String, используя технику ответа CMS.

Обратите внимание, что эта реализация не поддерживает " position " параметр, который упоминается в этой сети разработчиков Mozilla страницу, но, похоже, это не является частью предложения ECMAScript.

Я не уверен, что для javascript, но в машинописи я сделал что-то вроде

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

Я думаю, это должно работать и на js. Надеюсь, это поможет!

  1. Вопрос немного устарел, но я хотел написать этот ответ, чтобы показать вам некоторые тесты, которые я сделал на основе всех ответов, представленных здесь, и jsperf, предоставленного Джимом Баком.
  2. Мне в основном нужен был быстрый способ определить, находится ли длинная игла в длинном стоге сена, и они очень похожи, за исключением последних символов.

    Вот код, который я написал, который для каждой функции (сращивания, подстроки, startWith и т. д.) проверяет, когда они возвращают false и true, против строки сена ( nestedString ) из 1.000.0001 символов. и ложная или правдивая строка иглы в 1 000 000 символов ( testParentStringFalse и testParentStringTrue , соответственно):

    // 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 ( подстрока выигрывает):

    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 выигрывает для ложных результатов, startWith выигрывает для истинных результатов, также 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}
    

    Оказывается, у каждого браузера есть свои особенности реализации (кроме Opera, основанной на Chrome Chrome и Blink).

    Конечно, можно и нужно проводить дополнительный тест с различными вариантами использования (например, когда игла действительно короткая по сравнению со стогом сена, когда стог сена короче иглы и т. д.), но в моем случае мне нужно было очень сравнить длинные строки и хотел бы поделиться этим здесь.

Если вы работаете с startWith () и endWith () , вам следует быть осторожным с начальными пробелами. Вот полный пример:

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

Вы также можете вернуть все члены массива, которые начинаются со строки, создав собственный прототип / расширение для прототипа массива, иначе

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
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top