Pregunta

¿Cómo escribiría el equivalente de C # 's String.StartsWith en JavaScript?

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

haystack.startsWith(needle) == true

Nota: Esta es una vieja pregunta, y como se señaló en los comentarios ECMAScript 2015 (ES6) introdujo el .startsWith método. Sin embargo, al momento de escribir esta actualización (2015) la compatibilidad del navegador está lejos de ser completa .

¿Fue útil?

Solución

Puede usar la String de ECMAScript 6. prototype.startsWith () , pero es aún no se admite en todos los navegadores . Deberá usar una cuña / polyfill para agregarlo en los navegadores que no lo admiten. Crear una implementación que cumpla con todos los detalles establecidos en la especificación es un poco complicada. Si quieres una cuña fiel, usa:

Una vez que haya mejorado el método (o si solo admite navegadores y motores JavaScript que ya lo tienen), puede usarlo así:

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

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

Otros consejos

Otra alternativa con .lastIndexOf :

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

Esto mira hacia atrás a través de haystack para una aparición de aguja a partir del índice 0 de haystack . En otras palabras, solo verifica si haystack comienza con needle .

En principio, esto debería tener ventajas de rendimiento sobre otros enfoques:

  • No busca en todo el haystack .
  • No crea una nueva cadena temporal y luego la descarta inmediatamente.
data.substring(0, input.length) === input

Sin una función auxiliar, solo usando de regex .test método:

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

Para hacer esto con una cadena dinámica en lugar de una codificada (suponiendo que la cadena no contendrá ningún carácter de control regexp):

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

Debería consultar ¿Existe una función RegExp.escape en Javascript? ? si existe la posibilidad de que aparezcan caracteres de control regexp en la cadena.

La mejor solución:

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

Y aquí está endsWith si lo necesita también:

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

Para aquellos que prefieren crear un prototipo en 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;
});

Uso:

"abc".startsWith("ab")
true
"c".ensdWith("c") 
true

Solo quería agregar mi opinión sobre esto.

Creo que podemos usar así:

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

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

Aquí hay una pequeña mejora en la solución de 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

Comprobando si la función ya existe en caso de que un futuro navegador la implemente en código nativo o si es implementada por otra biblioteca. Por ejemplo, la Biblioteca de prototipos ya implementa esta función.

El uso de ! es un poco más rápido y conciso que === 0 , aunque no tan legible.

Consulte también underscore.string.js . Viene con un montón de métodos útiles de prueba y manipulación de cadenas, incluido un método beginWith . De los documentos:

  

comienza con _.startsWith (cadena, comienza)

     

Este método verifica si string comienza con comienza .

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

Recientemente me hice la misma pregunta.
Hay varias soluciones posibles, aquí hay 3 soluciones válidas:

  • s.indexOf (starter) === 0
  • s.substr (0, starter.length) === starter
  • s.lastIndexOf (starter, 0) === 0 (agregado después de ver la respuesta de Mark Byers respuesta )
  • usando un bucle:

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

No he encontrado la última solución que utiliza un bucle.
Sorprendentemente, esta solución supera a los primeros 3 por un margen significativo.
Aquí está la prueba jsperf que realicé para llegar a esta conclusión: http://jsperf.com/startswith2/2

Paz

ps: ecmascript 6 (armonía) introduce un método nativo beginWith para cadenas.
Solo piense cuánto tiempo se habría ahorrado si hubieran pensado incluir este método tan necesario en la versión inicial misma.

Update

Como señaló Steve (el primer comentario sobre esta respuesta), la función personalizada anterior arrojará un error si el prefijo dado es más corto que la cadena completa. Lo arregló y agregó una optimización de bucle que se puede ver en http://jsperf.com/startswith2/4 .

Tenga en cuenta que hay 2 optimizaciones de bucle que Steve incluyó, la primera de las dos mostró un mejor rendimiento, por lo que publicaré ese código a continuación:

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

Dado que esto es tan popular, creo que vale la pena señalar que hay una implementación para este método en ECMA 6 y en preparación para eso se debe usar el polyfill 'oficial' para evitar futuros problemas y lágrimas.

Afortunadamente, los expertos de Mozilla nos proporcionan uno:

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

if (!String.prototype.startsWith) {
    String.prototype.startsWith = function(searchString, position) {
        position = position || 0;
        return this.indexOf(searchString, position) === position;
    };
}

Tenga en cuenta que esto tiene la ventaja de ser ignorado con gracia en la transición a ECMA 6.

La mejor solución de rendimiento es dejar de usar las llamadas de la biblioteca y simplemente reconocer que está trabajando con dos matrices. Una implementación manual es breve y también más rápida que cualquier otra solución que haya visto aquí.

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

Para ver comparaciones de rendimiento (éxito y fracaso), consulte http://jsperf.com/startswith2/4. (Asegúrese de verificar las versiones posteriores que pueden haber superado la mía).

Acabo de enterarme de esta biblioteca de cadenas:

http://stringjs.com/

Incluya el archivo js y luego use la variable S como esta:

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

También se puede usar en NodeJS instalándolo:

npm install string

Luego se requiere como la variable S :

var S = require('string');

La página web también tiene enlaces a bibliotecas de cadenas alternativas, si esta no te gusta.

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

Basado en las respuestas aquí, esta es la versión que estoy usando ahora, ya que parece ofrecer el mejor rendimiento basado en las pruebas JSPerf (y es funcionalmente completo hasta donde puedo decir).

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

Esto se basó en beginWith2 desde aquí: http://jsperf.com/startswith2/6 . Agregué un pequeño ajuste para una pequeña mejora en el rendimiento, y desde entonces también agregué una verificación para que la cadena de comparación sea nula o indefinida, y la convertí para agregarla al prototipo de cadena usando la técnica en la respuesta de CMS.

Tenga en cuenta que esta implementación no es compatible con la " posición " parámetro que se menciona en este Mozilla Developer Network , pero eso no parece ser parte de la propuesta ECMAScript de todos modos.

No estoy seguro de javascript pero en mecanografiado hice algo como

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

Supongo que también debería funcionar en js. ¡Espero que ayude!

  1. La pregunta es un poco antigua, pero quería escribir esta respuesta para mostrarle algunos puntos de referencia que hice en base a todas las respuestas proporcionadas aquí y el jsperf compartido por Jim Buck.

Básicamente necesitaba una forma rápida de encontrar si una aguja larga está dentro de un pajar largo y son muy similares, excepto por los últimos caracteres.

Aquí está el código que he escrito que para cada función (empalme, subcadena, comienza con, etc.) prueba tanto cuando devuelven falso como verdadero contra una cadena de pajar ( nestedString ) de 1,000,0001 caracteres y un hilo de aguja falso o verdadero de 1.000.000 caracteres ( testParentStringFalse y testParentStringTrue , respectivamente):

// 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)
}

Ejecuté esta prueba de referencia en Chrome 75 , Firefox 67 , Safari 12 y Opera 62 .

No he incluido Edge e IE porque no los tengo en esta máquina, pero si alguien de ustedes quiere ejecutar el script contra Edge y al menos IE 9 y compartir el resultado aquí, me daría mucha curiosidad ver los resultados.

Solo recuerde que necesita volver a crear las 3 cadenas largas y guardar el script en un archivo que luego abre en su navegador, ya que copiar / pegar en la consola del navegador lo bloqueará ya que la longitud de cada cadena es > = 1,000,000 ).

Aquí están los resultados:

Chrome 75 ( subcadena gana):

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 gana):

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 gana por resultados falsos, comienza con gana por resultados verdaderos, también Safari es el más rápido en términos de tiempo total para ejecutar la prueba completa):

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 gana. Los resultados son similares a los de Chrome y no me sorprende, ya que Opera se basa en Chromium y 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}

Resulta que cada navegador tiene sus propios detalles de implementación (aparte de Opera, que se basa en Chrome y Blink de Chrome).

Por supuesto, podría y debería realizarse una prueba adicional con diferentes casos de uso (por ejemplo, cuando la aguja es realmente corta en comparación con el pajar, cuando el pajar es más corto que la aguja, etc.), pero en mi caso necesitaba comparar muy cadenas largas y quería compartirlo aquí.

Si está trabajando con beginWith () y endsWith () , debe tener cuidado con los espacios iniciales. Aquí hay un ejemplo completo:

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

También puede devolver todos los miembros de una matriz que comienzan con una cadena creando su propio prototipo / extensión para el prototipo de matriz, también conocido como

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

Y para usarlo:

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