`Objeto` String

Este artículo explica el objeto String.

La explicación abarca todo desde lo básico hasta técnicas avanzadas, incluyendo trampas relacionadas con Unicode y expresiones regulares, paso a paso y de manera fácil de entender.

YouTube Video

Objeto String

Las cadenas de texto en JavaScript son uno de los tipos más utilizados en el desarrollo diario.

Diferencia entre cadenas primitivas y objetos String

Las cadenas primitivas (como "hello") se comportan de manera diferente a los objetos envoltorios como new String("hello"). Normalmente, deberías usar tipos primitivos, y rara vez se necesita usar la forma de objeto.

1// Primitive string
2const a = "hello";
3
4// String wrapper object
5const b = new String("hello");
6
7console.log(typeof a); // "string"
8console.log(typeof b); // "object"
9console.log(a === b);  // false — wrapper objects are not strictly equal
  • Este código muestra la diferencia de tipo entre un primitivo y un objeto envoltorio, y cómo se comportan en una comparación estricta. En la mayoría de los casos, evita usar new String() y utiliza los tipos primitivos.

Formas de crear cadenas (literales y literales de plantilla)

Los literales de plantilla son útiles para insertar variables y escribir cadenas de varias líneas. Puedes insertar variables y evaluar expresiones de manera intuitiva.

1const name = "Alice";
2const age = 30;
3
4// Template literal
5const greeting = `Name: ${name}, Age: ${age + 1}`;
6
7console.log(greeting); // "Name: Alice, Age: 31"
  • Los literales de plantilla son muy legibles e ideales para construir cadenas complejas, incluidas las de varias líneas.

Métodos comunes (búsqueda y extracción de subcadenas)

El objeto String tiene muchos métodos básicos.

 1const text = "Hello, world! Hello again.";
 2
 3// search
 4console.log(text.indexOf("Hello"));       // 0
 5console.log(text.indexOf("Hello", 1));    // 13
 6console.log(text.includes("world"));      // true
 7console.log(text.startsWith("Hello"));    // true
 8console.log(text.endsWith("again."));     // true
 9
10// slice / substring
11console.log(text.slice(7, 12));           // "world"
12console.log(text.substring(7, 12));       // "world"
  • slice y substring son similares, pero manejan los índices negativos de manera diferente. slice interpreta los valores negativos como posiciones desde el final. Ten claro cuál utilizar.

Dividir y unir (split / join)

Es común dividir una cadena en un array para procesarla y luego volver a unirla.

1const csv = "red,green,blue";
2const arr = csv.split(","); // ["red","green","blue"]
3
4console.log(arr);
5console.log(arr.join(" | ")); // "red | green | blue"
  • Un patrón común es usar split para dividir una cadena, procesar el array resultante con map o filter, y luego usar join para unirlo de nuevo.

Reemplazar y expresiones regulares

replace solo reemplaza la primera coincidencia. Si quieres reemplazar todas las coincidencias, utiliza la bandera g con una expresión regular. Pasando una función como reemplazo, puedes realizar reemplazos dinámicos también.

 1const s = "foo 1 foo 2";
 2
 3// replace first only
 4console.log(s.replace("foo", "bar")); // "bar 1 foo 2"
 5
 6// replace all using regex
 7console.log(s.replace(/foo/g, "bar")); // "bar 1 bar 2"
 8
 9// replace with function
10const r = s.replace(/\d+/g, (match) => String(Number(match) * 10));
11console.log(r);    // "foo 10 foo 20"
  • Con los reemplazos dinámicos usando una función, puedes escribir de manera concisa código que analiza y transforma las coincidencias.

Conversión de mayúsculas/minúsculas y normalización

Para el soporte multilingüe y la comparación, además de toLowerCase y toUpperCase, la normalización Unicode (normalize) también es importante. Esto es especialmente necesario al comparar caracteres acentuados.

 1// Case conversion example:
 2// "\u00DF" represents the German letter "ß".
 3// In some locales, converting "ß" to uppercase becomes "SS".
 4// JavaScript follows this behavior.
 5console.log("\u00DF");
 6console.log("\u00DF".toUpperCase()); // "SS"
 7
 8// Unicode normalization example:
 9// "e\u0301" is "e" + a combining acute accent.
10// "\u00e9" is the precomposed character "é".
11// These two look the same but are different code point sequences.
12const a = "e\u0301";
13const b = "\u00e9";
14
15console.log(a === b);   // false: different underlying code points
16console.log(a.normalize() === b.normalize()); // true: normalized to the same form
  • Diferentes representaciones Unicode, como ligaduras y caracteres combinados, no serán iguales tal cual, así que usa normalize() antes de comparar.

Unicode y puntos de código (manejo de pares sustitutos)

Las cadenas de JavaScript son secuencias de unidades de código UTF-16, por lo que algunos caracteres como los emojis pueden ocupar dos unidades de código para un solo carácter. Para manejar unidades reales de caracteres, utiliza Array.from, el operador spread, o for...of.

 1// Emoji composed with multiple code points:
 2// "\u{1F469}" = woman, "\u{200D}" = Zero Width Joiner (ZWJ),
 3// "\u{1F4BB}" = laptop. Combined, they form a single emoji: 👩‍💻
 4const s = "\u{1F469}\u{200D}\u{1F4BB}";
 5console.log(s);
 6
 7// Length in UTF-16 code units (not actual Unicode characters):
 8// Because this emoji uses surrogate pairs + ZWJ, the length may be > 1.
 9console.log("Length:", s.length);
10
11// Iterate by Unicode code points (ES6 for...of iterates code points):
12// Each iteration gives a full Unicode character, not UTF-16 units.
13for (const ch of s) {
14  console.log(ch);
15}
16
17// Convert to an array of Unicode characters:
18console.log(Array.from(s));
  • length devuelve el número de unidades de código, por lo que puedes no obtener el conteo esperado con emojis o ligaduras. for...of y Array.from manejan algo cercano a los caracteres visualizados (clústeres de grafemas), pero si necesitas soporte completo para grafemas, considera usar una biblioteca especializada.

Reemplazo seguro en expresiones regulares (al manejar entrada del usuario)

Si olvidas escapar la entrada del usuario al incorporarla en una expresión regular, puede ocasionar comportamientos inesperados y vulnerabilidades. Siempre escapa la entrada del usuario antes de usarla en un patrón.

1function escapeRegex(s) {
2  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3}
4
5const userInput = "a+b";
6const pattern = new RegExp(escapeRegex(userInput), "g");
7console.log("a+b a+b".replace(pattern, "X")); // "X X"
  • No uses cadenas del usuario directamente en expresiones regulares; siempre escápalas antes de construir la expresión.

Consejos de rendimiento: concatenación y plantillas

Al concatenar muchas cadenas pequeñas en secuencia, colocarlas en un array y usar join puede ser más eficiente. Por otro lado, las plantillas de cadena son muy legibles y lo suficientemente rápidas en la mayoría de los casos.

 1// concatenation in loop (less ideal)
 2let s = "";
 3for (let i = 0; i < 1000; i++) {
 4  s += i + ",";
 5}
 6
 7// using array + join (often faster for many pieces)
 8const parts = [];
 9for (let i = 0; i < 1000; i++) {
10  parts.push(i + ",");
11}
12const s2 = parts.join("");
  • Los motores modernos de JavaScript están altamente optimizados, así que no necesitas preocuparte por el rendimiento en un pequeño número de concatenaciones. Sin embargo, si necesitas concatenar decenas de miles de veces, usar join puede ser más eficiente.

Técnicas prácticas útiles: relleno, recorte y repetición

trim, padStart, padEnd y repeat son métodos convenientes que son especialmente útiles en el procesamiento diario de cadenas. A menudo se utilizan en situaciones prácticas como el formateo de valores de entrada o la estandarización de formatos de salida.

1console.log("  hello  ".trim());       // "hello"
2console.log("5".padStart(3, "0"));     // "005"
3console.log("x".repeat(5));            // "xxxxx"
  • Estos métodos pueden usarse para normalizar la entrada de formularios o generar salida de ancho fijo.

Comparación de cadenas (comparación según la configuración regional)

localeCompare es efectivo para comparar cadenas según el orden de diccionario de diferentes idiomas. Puedes especificar opciones de idioma y sensibilidad (como la sensibilidad a mayúsculas y minúsculas).

1console.log(
2  "\u00E4".localeCompare("z", "de")
3); // may be -1 or other depending on locale
4
5console.log(
6  "a".localeCompare("A", undefined, { sensitivity: "base" })
7); // 0
  • Para comparaciones internacionalizadas, usa localeCompare y especifica la configuración regional y las opciones adecuadas.

Ejemplo práctico: convertir una fila CSV en un objeto (flujo de trabajo práctico)

Un caso de uso común es analizar una sola fila CSV en un objeto utilizando una combinación de split, trim y map. Para campos entre comillas o archivos CSV complejos, utiliza un analizador CSV especializado.

 1// simple CSV parse (no quotes handling)
 2function parseCsvLine(line, headers) {
 3  const values = line.split(",").map(v => v.trim());
 4  const obj = {};
 5  headers.forEach((h, i) => obj[h] = values[i] ?? null);
 6  return obj;
 7}
 8
 9const headers = ["name", "age", "city"];
10const line = " Alice , 30 , New York ";
11console.log(parseCsvLine(line, headers));
12// { name: "Alice", age: "30", city: "New York" }
  • Este método funciona para archivos CSV simples, pero ten en cuenta que no puede manejar casos en los que una coma está dentro de un campo entre comillas.

Errores comunes

Hay algunas especificaciones y comportamientos fácilmente pasados por alto en el manejo de cadenas en JavaScript. Para evitar errores inesperados, es importante tener en cuenta los siguientes puntos.

  • Usar new String() puede conducir a resultados incorrectos al realizar comprobaciones de tipo o comparaciones. En la mayoría de los casos, los tipos de cadena primitivos son suficientes.
  • En Unicode, un solo carácter visible puede estar compuesto por varias unidades de código. Por lo tanto, el valor devuelto por length puede no coincidir con el número real de caracteres visualizados.
  • Al incorporar la entrada del usuario en una expresión regular, siempre escápala primero.
  • String.prototype.replace() reemplaza solo la primera coincidencia por defecto. Si quieres reemplazar todas las apariciones, utiliza el modificador /g en tu expresión regular.
  • Las cadenas son inmutables, por lo que las operaciones siempre devuelven una cadena nueva. Es importante asignar siempre el valor devuelto.

Resumen

Aunque las cadenas de JavaScript pueden parecer simples, es importante comprender sus características en relación con Unicode y su inmutabilidad. Al dominar lo básico, puedes mejorar enormemente la confiabilidad y la legibilidad de tu procesamiento de cadenas.

Puedes seguir el artículo anterior utilizando Visual Studio Code en nuestro canal de YouTube. Por favor, también revisa nuestro canal de YouTube.

YouTube Video