¿Cuál es la diferencia entre las primitivas de cadena y los objetos de cadena en JavaScript?

116

Tomado de MDN

Los literales de cadena (indicados por comillas dobles o simples) y las cadenas devueltas de llamadas de cadena en un contexto que no es de constructor (es decir, sin usar la nueva palabra clave) son cadenas primitivas. JavaScript convierte automáticamente primitivas en objetos String, por lo que es posible utilizar métodos de objetos String para cadenas primitivas. En contextos donde se va a invocar un método en una cadena primitiva o se produce una búsqueda de propiedad, JavaScript ajustará automáticamente la cadena primitiva y llamará al método o realizará la búsqueda de propiedad.

Entonces, pensé (lógicamente) que las operaciones (llamadas a métodos) en primitivas de cadena deberían ser más lentas que las operaciones en Objetos de cadena porque cualquier primitiva de cadena se convierte en Objeto de cadena (trabajo adicional) antes del method se aplique en la cadena.

Pero en este caso de prueba , el resultado es opuesto. El bloque de código 1 se ejecuta más rápido que el bloque de código 2 , ambos bloques de código se dan a continuación:

bloque de código 1:

var s = '0123456789';
for (var i = 0; i < s.length; i++) {
  s.charAt(i);
}

bloque de código 2:

var s = new String('0123456789');
for (var i = 0; i < s.length; i++) {
    s.charAt(i);
}

Los resultados varían en los navegadores, pero el bloque de código 1 siempre es más rápido. ¿Alguien puede explicar esto, por qué el bloque de código 1 es más rápido que el bloque de código 2 ?

El alfa
fuente
6
El uso new Stringintroduce otra capa transparente de envoltura de objetos . typeof new String(); //"object"
Paul S.
¿qué pasa '0123456789'.charAt(i)?
Yuriy Galanter
@YuriyGalanter, no es un problema, pero pregunto por qué code block-1es más rápido.
The Alpha
2
Los objetos de cadena son relativamente raros de ver en el contexto de la vida real, por lo que no es sorprendente que los intérpretes optimicen los literales de cadena. Hoy en día, su código no se interpreta simplemente , hay muchas capas de optimización que ocurren detrás de escena.
Fabrício Matté
2
Esto es extraño: revisión 2
hjpotter92

Respuestas:

149

JavaScript tiene dos categorías principales de tipos, primivitas y objetos.

var s = 'test';
var ss = new String('test');

Los patrones de comillas simples / dobles son idénticos en términos de funcionalidad. Aparte de eso, el comportamiento que está tratando de nombrar se llama auto-boxing. Entonces, lo que realmente sucede es que una primitiva se convierte a su tipo de contenedor cuando se invoca un método del tipo de contenedor. En pocas palabras:

var s = 'test';

Es un tipo de datos primitivo. No tiene métodos, no es más que un puntero a una referencia de memoria de datos sin procesar, lo que explica la velocidad de acceso aleatorio mucho más rápida.

Entonces que pasa cuando lo haces s.charAt(i) por ejemplo?

Dado sque no es una instancia de String, JavaScript se auto-box s, que tiene typeof stringsu tipo de contenedor,, Stringcon typeof objecto más precisamentes.valueOf(s).prototype.toString.call = [object String] .

El comportamiento de auto-boxing se proyecta shacia adelante y hacia atrás a su tipo de contenedor según sea necesario, pero las operaciones estándar son increíblemente rápidas ya que se trata de un tipo de datos más simple. Sin embargo, el auto-boxing yObject.prototype.valueOf tiene diferentes efectos.

Si desea forzar el auto-boxing o lanzar una primitiva a su tipo de envoltura, puede usar Object.prototype.valueOf, pero el comportamiento es diferente. Basado en una amplia variedad de escenarios de prueba, el auto-boxing solo aplica los métodos 'requeridos', sin alterar la naturaleza primitiva de la variable. Por eso obtienes una mejor velocidad.

flavio
fuente
33

Esto depende bastante de la implementación, pero intentaré. Lo ejemplificaré con V8, pero supongo que otros motores usan enfoques similares.

Una cadena primitiva se analiza en un v8::Stringobjeto. Por lo tanto, los métodos se pueden invocar directamente en él como lo menciona jfriend00 .

Un objeto String, por otro lado, se analiza a un objeto v8::StringObjectque se extiende Objecty, además de ser un objeto completo, sirve como envoltorio para v8::String.

Ahora es lógico, una llamada a new String('').method()tiene que desempacar este v8::StringObject's v8::Stringantes de ejecutar el método, por lo tanto es más lento.


En muchos otros lenguajes, los valores primitivos no tienen métodos.

La forma en que MDN lo expresa parece ser la forma más sencilla de explicar cómo funciona el auto-boxing de los primitivos (como también se menciona en la respuesta de flav ), es decir, cómo JavaScript's primitive-y valores de pueden invocar métodos.

Sin embargo, un motor inteligente no convertirá una cadena primitiva-y en un objeto String cada vez que necesite llamar a un método. Esto también se menciona de forma informativa en la especificación ES5 anotada. con respecto a la resolución de propiedades (y "métodos" ¹) de valores primitivos:

NOTA El objeto que se puede crear en el paso 1 no es accesible fuera del método anterior. Una implementación puede optar por evitar la creación real del objeto. [...]

A un nivel muy bajo, las cadenas se implementan con mayor frecuencia como valores escalares inmutables. Ejemplo de estructura de envoltura:

StringObject > String (> ...) > char[]

Cuanto más lejos estés del primitivo, más tiempo tardarás en llegar a él. En la práctica, las Stringprimitivas son mucho más frecuentes que StringObjects, por lo que no es una sorpresa que los motores agreguen métodos a la Clase de los objetos (interpretados) correspondientes de las primitivas de cadena en lugar de realizar conversiones de ida y vuelta entre Stringy StringObjectcomo sugiere la explicación de MDN.


¹ En JavaScript, "método" es solo una convención de nomenclatura para una propiedad que se resuelve en un valor de tipo función.

Fabrício Matté
fuente
1
De nada. =]Ahora me pregunto si la explicación de MDN está ahí solo porque parece ser la forma más fácil de entender el auto-boxing o si hay alguna referencia a él en la especificación ES. Al leer toda la especificación en el momento de verificar, recordaré actualice la respuesta si alguna vez encuentro una referencia.
Fabrício Matté
Gran conocimiento de la implementación del V8. Debo agregar que el boxeo no solo está ahí para resolver la función. También está ahí para pasar la referencia this al método. Ahora no estoy seguro de si V8 omite esto para los métodos integrados, pero si agrega su propia extensión para decir String.prototype, obtendrá una versión en caja del objeto string cada vez que se llame.
Ben
17

En el caso de una cadena literal, no podemos asignar propiedades.

var x = "hello" ;
x.y = "world";
console.log(x.y); // this will print undefined

Mientras que en el caso de String Object podemos asignar propiedades

var x = new String("hello");
x.y = "world";
console.log(x.y); // this will print world
refactorizar
fuente
1
Finalmente, alguien motiva por qué también necesitamos Stringobjetos. ¡Gracias!
Ciprian Tomoiagă
1
¿Por qué alguien tendría la necesidad de hacer esto?
Aditya
11

Literal de cadena:

Los literales de cadena son inmutables, lo que significa que una vez que se crean, su estado no se puede cambiar, lo que también los hace seguros para subprocesos.

var a = 's';
var b = 's';

a==b el resultado será 'verdadero' ambas cadenas refieren el mismo objeto.

Objeto de cadena:

Aquí, se crean dos objetos diferentes y tienen diferentes referencias:

var a = new String("s");
var b = new String("s");

a==b El resultado será falso, porque tienen referencias diferentes.

Wajahat Ali Qureshi
fuente
1
¿El objeto de cadena también es inmutable?
Yang Wang
@YangWang eso es un lenguaje tonto, para ambos ay bintenta asignar a[0] = 'X'que se puede ejecutar con éxito pero no funcionará como se podría esperar
Rux
Escribiste "var a = 's'; var b = 's'; a == b el resultado será 'verdadero' ambas cadenas refieren al mismo objeto". Eso no es correcto: ayb no se refieren a ningún mismo objeto, el resultado es verdadero porque tienen el mismo valor. Esos valores se almacenan en diferentes ubicaciones de memoria, por eso si cambia uno, ¡el otro no cambia!
SC1000
9

Si lo usa new, está indicando explícitamente que desea crear una instancia de un objeto . Por lo tanto, new Stringestá produciendo un Objeto que envuelve la primitiva Cadena , lo que significa que cualquier acción sobre él implica una capa adicional de trabajo.

typeof new String(); // "object"
typeof '';           // "string"

Como son de diferentes tipos, su intérprete de JavaScript también puede optimizarlos de manera diferente, como se menciona en los comentarios .

Paul S.
fuente
5

Cuando declaras:

var s = '0123456789';

creas una cadena primitiva. Esa primitiva de cadena tiene métodos que le permiten llamar a métodos sin convertir la primitiva en un objeto de primera clase. Entonces, su suposición de que esto sería más lento porque la cadena debe convertirse en un objeto no es correcta. No es necesario convertirlo en un objeto. La propia primitiva puede invocar los métodos.

Convertirlo en un objeto completo (que le permite agregarle nuevas propiedades) es un paso adicional y no acelera las operaciones de cadena (de hecho, su prueba muestra que las hace más lentas).

jfriend00
fuente
¿Cómo es que la cadena primitiva hereda todas las propiedades del prototipo, incluidas las personalizadas String.prototype?
Yuriy Galanter
1
var s = '0123456789';es un valor primitivo, ¿cómo puede este valor tener métodos? ¡Estoy confundido!
The Alpha
2
@SheikhHeera: las primitivas están integradas en la implementación del lenguaje para que el intérprete pueda otorgarles poderes especiales.
jfriend00
1
@SheikhHeera - No entiendo su último comentario / pregunta. Una cadena primitiva por sí sola no le permite agregarle sus propias propiedades. Para permitir eso, JavaScript también tiene un objeto String que tiene todos los mismos métodos que una cadena primitiva, pero es un objeto completo que puede tratar como un objeto en todos los sentidos. Esta forma dual parece ser un poco desordenada, pero sospecho que se hizo como un compromiso de rendimiento ya que el caso del 99% es el uso de primitivas y probablemente pueden ser más rápidas y más eficientes en memoria que los objetos de cadena.
jfriend00
1
@SheikhHeera "convertido a objeto de cadena" es la forma en que MDN lo expresa para explicar cómo las primitivas pueden invocar métodos. No se convierten literalmente en objetos de cadena.
Fabrício Matté
4

Puedo ver que esta pregunta se ha resuelto hace mucho tiempo, hay otra distinción sutil entre los literales de cadena y los objetos de cadena, ya que nadie parece haberlo tocado, pensé en escribirlo para completarlo.

Básicamente, otra distinción entre los dos es cuando se usa eval. eval ('1 + 1') da 2, mientras que eval (new String ('1 + 1')) da '1 + 1', por lo que si cierto bloque de código se puede ejecutar tanto 'normalmente' o con eval, podría conducir a resultados extraños

luanped
fuente
Gracias por tu aporte :-)
The Alpha
Vaya, ese es un comportamiento realmente extraño. Debería agregar una pequeña demostración en línea en su comentario para mostrar este comportamiento; es extremadamente revelador.
EyuelDK
esto es normal, si lo piensas. new String("")devuelve un objeto, y eval solo evalúa la cadena, y devuelve todo lo demás como está
Félix Brunet
3

La existencia de un objeto tiene poco que ver con el comportamiento real de una cadena en los motores ECMAScript / JavaScript, ya que el alcance raíz simplemente contendrá objetos de función para esto. Por lo tanto, se buscará y ejecutará la función charAt (int) en el caso de una cadena literal.

Con un objeto real, agrega una capa más donde el método charAt (int) también se busca en el objeto mismo antes de que se active el comportamiento estándar (igual que el anterior). Aparentemente, se ha realizado una cantidad sorprendentemente grande de trabajo en este caso.

Por cierto, no creo que las primitivas se conviertan en objetos, pero el motor de secuencia de comandos simplemente marcará esta variable como tipo de cadena y, por lo tanto, puede encontrar todas las funciones proporcionadas para que parezca que invocas un objeto. No olvide que este es un tiempo de ejecución de script que funciona con principios diferentes a los de un tiempo de ejecución OO.

agua clara
fuente
3

La mayor diferencia entre una primitiva de cadena y un objeto de cadena es que los objetos deben seguir esta regla para el ==operador :

Una expresión que compara objetos solo es verdadera si los operandos hacen referencia al mismo objeto.

Entonces, mientras que las primitivas de cadena tienen una conveniencia ==que compara el valor, no tiene suerte cuando se trata de hacer que cualquier otro tipo de objeto inmutable (incluido un objeto de cadena) se comporte como un tipo de valor.

"hello" == "hello"
-> true
new String("hello") == new String("hello") // beware!
-> false

(Otros han notado que un objeto de cadena es técnicamente mutable porque puede agregarle propiedades. Pero no está claro para qué es útil; el valor de cadena en sí no es mutable).

personal_cloud
fuente
Gracias por agregar valor a la pregunta después de bastante tiempo :-)
The Alpha
1

El código se optimiza antes de ejecutarlo mediante el motor javascript. En general, los micro benchmarks pueden ser engañosos porque los compiladores e intérpretes reorganizan, modifican, eliminan y realizan otros trucos en partes de su código para que se ejecute más rápido. En otras palabras, el código escrito indica cuál es el objetivo, pero el compilador y / o el tiempo de ejecución decidirán cómo lograrlo.

El bloque 1 es más rápido principalmente debido a: var s = '0123456789'; siempre es más rápido que var s = new String ('0123456789'); debido a la sobrecarga de la creación de objetos.

La parte del bucle no es la que causa la desaceleración porque el intérprete puede insertar chartAt (). Intente quitar el bucle y vuelva a ejecutar la prueba, verá que la relación de velocidad será la misma que si no se hubiera eliminado el bucle. En otras palabras, para estas pruebas, los bloques de bucle en el momento de la ejecución tienen exactamente el mismo código de bytes / código de máquina.

Para estos tipos de micro evaluaciones comparativas, mirar el código de bytes o el código de máquina proporcionará una imagen más clara.

Arco
fuente
1
Gracias por tu respuesta.
The Alpha
0

En Javascript, los tipos de datos primitivos como la cadena son un bloque de construcción no compuesto. Esto significa que son solo valores, nada más: let a = "string value"; forma predeterminada no hay métodos integrados como toUpperCase, toLowerCase, etc.

Pero, si intentas escribir:

console.log( a.toUpperCase() ); or console.log( a.toLowerCase() );

Esto no arrojará ningún error, sino que funcionarán como deberían.

Que pasó ? Bueno, cuando intenta acceder a una propiedad de una cadena, aJavascript coacciona la cadena a un objeto new String(a);conocido como objeto contenedor .

Este proceso está vinculado al concepto denominado constructores de funciones en Javascript, donde las funciones se utilizan para crear nuevos objetos.

Cuando escribe new String('String value');aquí String es un constructor de funciones, que toma un argumento y crea un objeto vacío dentro del alcance de la función, este objeto vacío se asigna a este y, en este caso, String proporciona todas las funciones integradas conocidas que mencionamos antes. y tan pronto como se completa la operación, por ejemplo, si se realiza una operación en mayúsculas, el objeto contenedor se descarta.

Para probar eso, hagamos esto:

let justString = 'Hello From String Value';
justString.addNewProperty = 'Added New Property';
console.log( justString );

Aquí la salida no estará definida. Por qué ? En este caso, Javascript crea un objeto String contenedor, establece una nueva propiedad addNewProperty y descarta el objeto contenedor inmediatamente. es por eso que no se define. El pseudocódigo se vería así:

let justString = 'Hello From String Value';
let wrapperObject = new String( justString );
wrapperObject.addNewProperty = 'Added New Property'; //Do operation and discard
Alexander Gharibashvili
fuente
0

podemos definir String de 3 formas

  1. var a = "primera vía";
  2. var b = String ("segunda vía");
  3. var c = new String ("tercera vía");

// también podemos crear usando 4. var d = a + '';

Verifique el tipo de cadenas creadas usando el operador typeof

  • tipo de una // "cadena"
  • typeof b // "cadena"
  • typeof c // "objeto"


cuando comparas a y b var a==b ( // yes)


cuando comparas el objeto String

var StringObj = new String("third way")
var StringObj2 = new String("third way")
StringObj  == StringObj2 // no result will be false, because they have different references
SouMitya chauhan
fuente