String vs string: el asesino silencioso del rendimiento de tu código
\n\n¿Alguna vez te has preguntado por qué en un lugar TypeScript se queja de String y en otro de string? ¿O quizás has visto cómo los desarrolladores experimentados se estremecen al ver new String("hola")? Esta pregunta es un verdadero éxito en Stack Overflow, con miles de visitas. Y no es casualidad. La diferencia entre String y string no es solo una peculiaridad del lenguaje, sino una diferencia fundamental que afecta el rendimiento, la seguridad y la legibilidad de tu código.
En este artículo no solo daremos una definición seca. Analizaremos qué hay detrás de cada uno de estos tipos, cómo se comportan en tiempo de ejecución y por qué la elección correcta puede acelerar tu aplicación en decenas de porcentaje. ¡Prepárate, será interesante!
\n\nPrimitivos vs Objetos: conceptos básicos
\n\nPara entender la diferencia, debemos recordar cómo JavaScript (y por lo tanto, TypeScript) trabaja con los datos. El lenguaje tiene dos tipos de valores:
\n\n- \n
- Primitivos:
string,number,boolean,null,undefined,symbol,bigint. Son inmutables y se almacenan por valor. \n - Objetos:
Object,Array,Function, así comoString(con mayúscula),Number,Boolean. Son mutables y se almacenan por referencia. \n
Cuando escribes let name: string = "Alice", creas un primitivo. Cuando escribes let name: String = new String("Alice"), creas un objeto envoltorio. Y aquí es donde comienza lo interesante.
Tipo string (en minúscula)
\nEste es un tipo primitivo. Se usa en el 99.9% de los casos. Es ligero, rápido y predecible. La comparación de dos primitivos se realiza por valor:
\n\nlet a: string = "hola";\nlet b: string = "hola";\nconsole.log(a === b); // true\n\nTypeScript recomienda usar este. Es el estándar de facto para declarar variables de cadena, parámetros de funciones y valores de retorno.
\n\nTipo String (con mayúscula)
\nEste es un tipo objeto, un constructor envoltorio. Existe desde los tiempos de JavaScript para trabajar con métodos de cadenas. Sin embargo, su uso directo es más bien un antipatrón, a menos que tengas una tarea muy específica. La comparación de dos objetos String se realiza por referencia:
let a: String = new String("hola");\nlet b: String = new String("hola");\nconsole.log(a === b); // false! Son objetos diferentes en memoria\n\nEsto puede provocar errores difíciles de detectar. Imagina que verificas si el valor de la cadena es "hola" y obtienes false solo porque alguien usó new String().
Rendimiento: ¿por qué gana string?
\n\nAhora pasemos a lo más jugoso: los números y la optimización. Escribamos una prueba de rendimiento simple (puedes ejecutarla en tu entorno, por ejemplo, en Node.js o en el navegador).
\n\n// Prueba de rendimiento\nconst iterations = 1_000_000;\n\nconsole.time("string (primitivo)");\nlet sum = "";\nfor (let i = 0; i < iterations; i++) {\n sum += "a";\n}\nconsole.timeEnd("string (primitivo)");\n\nconsole.time("String (objeto)");\nlet sumObj = new String("");\nfor (let i = 0; i < iterations; i++) {\n sumObj += "a"; // Aquí ocurre autoempaquetado y desempaquetado\n}\nconsole.timeEnd("String (objeto)");\n\nEl resultado en una máquina típica será radicalmente diferente (a favor del primitivo de 2 a 5 veces). ¿Por qué? Porque:
\n- \n
- El primitivo
stringes simplemente una secuencia de caracteres en memoria. Las operaciones con él están optimizadas a nivel del motor.