Qual é a diferença entre primitivos de string e objetos String em JavaScript?

116

Retirado do MDN

Literais de string (denotados por aspas duplas ou simples) e strings retornadas de chamadas de String em um contexto não construtor (ou seja, sem usar a nova palavra-chave) são strings primitivas. JavaScript converte automaticamente primitivos em objetos String, de forma que seja possível usar métodos de objeto String para strings primitivas. Em contextos em que um método deve ser invocado em uma string primitiva ou ocorre uma pesquisa de propriedade, o JavaScript encapsula automaticamente a primitiva de string e chama o método ou executa a pesquisa de propriedade.

Portanto, pensei (logicamente) que as operações (chamadas de método) em primitivos de string deveriam ser mais lentas do que as operações em Objetos de string porque qualquer primitivo de string é convertido em Object de string (trabalho extra) antes de methodser aplicado na string.

Mas, neste caso de teste , o resultado é o oposto. O bloco de código 1 é executado mais rápido do que o bloco de código 2 , ambos os blocos de código são fornecidos abaixo:

bloco de código-1:

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

bloco de código 2:

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

Os resultados variam em navegadores, mas o bloco de código-1 é sempre mais rápido. Alguém pode explicar por que o bloco de código 1 é mais rápido do que o bloco 2 .

O alfa
fonte
6
Usar new Stringintroduz outra camada transparente de quebra automática de objeto . typeof new String(); //"object"
Paul S.
o que dizer '0123456789'.charAt(i)?
Yuriy Galanter
@YuriyGalanter, não é um problema, mas estou perguntando por que code block-1é mais rápido?
The Alpha de
2
Objetos de string são relativamente raros de serem vistos no contexto da vida real, portanto, não é surpreendente que os intérpretes otimizem literais de string. Hoje em dia, seu código não é simplesmente interpretado , existem muitas camadas de otimização que acontecem nos bastidores.
Fabrício Matté
2
Isso é estranho: revisão 2
hjpotter92

Respostas:

149

JavaScript tem duas categorias de tipo principais, primivites e objetos.

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

Os padrões de aspas simples / aspas duplas são idênticos em termos de funcionalidade. Deixando isso de lado, o comportamento que você está tentando nomear é chamado de boxe automático. Portanto, o que realmente acontece é que uma primitiva é convertida em seu tipo de invólucro quando um método do tipo de invólucro é invocado. Simplificando:

var s = 'test';

É um tipo de dados primitivo. Não tem métodos, nada mais é do que um ponteiro para uma referência de memória de dados brutos, o que explica a velocidade de acesso aleatório muito mais rápida.

Então, o que acontece quando você faz isso, s.charAt(i)por exemplo?

Uma vez que snão é uma instância de String, o JavaScript fará uma caixa automática s, que tem typeof stringseu tipo de invólucro String, com typeof objectou mais precisamente s.valueOf(s).prototype.toString.call = [object String].

O comportamento de caixa automática é convertido spara frente e para trás em seu tipo de invólucro conforme necessário, mas as operações padrão são incrivelmente rápidas, pois você está lidando com um tipo de dados mais simples. No entanto, auto-boxing e Object.prototype.valueOftem efeitos diferentes.

Se você quiser forçar a caixa automática ou converter uma primitiva para seu tipo de invólucro, você pode usar Object.prototype.valueOf, mas o comportamento é diferente. Com base em uma ampla variedade de cenários de teste, a caixa automática aplica apenas os métodos "necessários", sem alterar a natureza primitiva da variável. É por isso que você obtém mais velocidade.

flaviano
fonte
33

Isso depende da implementação, mas vou tentar. Vou exemplificar com o V8, mas presumo que outros motores usam abordagens semelhantes.

Uma primitiva de string é analisada em um v8::Stringobjeto. Conseqüentemente, os métodos podem ser chamados diretamente nele, conforme mencionado por jfriend00 .

Um objeto String, por outro lado, é analisado em um v8::StringObjectque se estende Objecte, além de ser um objeto completo, serve como um invólucro para v8::String.

Agora é lógico, uma chamada para new String('').method()tem que unbox este v8::StringObjecté v8::Stringantes de executar o método, por isso é mais lento.


Em muitas outras linguagens, os valores primitivos não têm métodos.

A forma como o MDN coloca parece ser a maneira mais simples de explicar como funciona a caixa automática dos primitivos (como também mencionado na resposta de flav ), ou seja, como os valores de y primitivo do JavaScript podem invocar métodos.

No entanto, um mecanismo inteligente não converterá uma string primitiva-y em um objeto String toda vez que você precisar chamar um método. Isso também é mencionado informativamente nas especificações ES5 anotadas. com relação à resolução de propriedades (e "métodos" ¹) de valores primitivos:

NOTA O objeto que pode ser criado na etapa 1 não está acessível fora do método acima. Uma implementação pode optar por evitar a criação real do objeto. [...]

Em um nível muito baixo, Strings são mais frequentemente implementadas como valores escalares imutáveis. Exemplo de estrutura de wrapper:

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

Quanto mais longe você estiver do primitivo, mais tempo levará para chegar a ele. Na prática, os Stringprimitivos são muito mais frequentes do que StringObjects, portanto, não é uma surpresa para os mecanismos adicionarem métodos à classe dos objetos correspondentes (interpretados) dos primitivos String, em vez de fazer a conversão entre Stringe StringObjectcomo sugere a explicação do MDN.


¹ Em JavaScript, "método" é apenas uma convenção de nomenclatura para uma propriedade que é resolvida como um valor da função de tipo.

Fabrício Matté
fonte
1
De nada. =]Agora estou me perguntando se a explicação do MDN está lá apenas porque parece ser a maneira mais fácil de entender a caixa automática ou se há alguma referência a ela na especificação ES. Lendo toda a especificação no momento para verificar, lembre-se de atualizo a resposta se eu encontrar uma referência.
Fabrício Matté
Ótimas informações sobre a implementação do V8. Devo acrescentar que o boxe não existe apenas para resolver a função. Ele também está lá para passar essa referência para o método. Agora, não tenho certeza se o V8 ignora isso para métodos integrados, mas se você adicionar sua própria extensão para dizer String.prototype, você obterá uma versão em caixa do objeto de string toda vez que for chamado.
Ben
17

No caso de literal de string, não podemos atribuir propriedades

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

Considerando que, no caso de String Object, podemos atribuir propriedades

var x = new String("hello");
x.y = "world";
console.log(x.y); // this will print world
refatorar
fonte
1
Finalmente, alguém motiva porque precisamos de Stringobjetos também. Obrigado!
Ciprian Tomoiagă
1
Por que alguém precisaria fazer isso?
Aditya
11

Literal de string:

Literais de string são imutáveis, o que significa que, uma vez criados, seu estado não pode ser alterado, o que também os torna thread-safe.

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

a==b o resultado será 'verdadeiro' ambas as strings referem-se ao mesmo objeto.

Objeto de string:

Aqui, dois objetos diferentes são criados e têm referências diferentes:

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

a==b o resultado será falso, porque eles têm referências diferentes.

Wajahat Ali Qureshi
fonte
1
O objeto string também é imutável?
Yang Wang
@YangWang que é uma linguagem de bobo, tanto para a& btentar atribuir a[0] = 'X'ele vai ser executado com êxito, mas não vai funcionar como você poderia esperar
Rux
Você escreveu "var a = 's'; var b = 's'; a == b o resultado será 'verdadeiro' ambas as strings referem-se ao mesmo objeto." Incorreto: aeb não se referem a nenhum mesmo objeto, o resultado é verdadeiro porque eles têm o mesmo valor. Esses valores são armazenados em locais de memória diferentes, por isso, se você mudar um, o outro não muda!
SC1000 de
9

Se você usar new, estará declarando explicitamente que deseja criar uma instância de um objeto . Portanto, new Stringestá produzindo um objeto envolvendo a primitiva String , o que significa que qualquer ação sobre ele envolve uma camada extra de trabalho.

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

Como são de tipos diferentes, seu interpretador de JavaScript também pode otimizá-los de maneira diferente, conforme mencionado nos comentários .

Paul S.
fonte
5

Quando você declara:

var s = '0123456789';

você cria um primitivo de string. Essa primitiva de string tem métodos que permitem chamar métodos nela sem converter a primitiva em um objeto de primeira classe. Portanto, sua suposição de que isso seria mais lento porque a string deve ser convertida em um objeto não está correta. Ele não precisa ser convertido em um objeto. O próprio primitivo pode invocar os métodos.

Convertê-lo em um objeto totalmente desenvolvido (que permite adicionar novas propriedades a ele) é uma etapa extra e não torna as operações de string mais rápidas (na verdade, seu teste mostra que isso as torna mais lentas).

jfriend00
fonte
Por que a primitiva de string herda todas as propriedades de protótipo, incluindo as personalizadas String.prototype?
Yuriy Galanter
1
var s = '0123456789';é um valor primitivo, como pode esse valor ter métodos, estou confuso!
The Alpha de
2
@SheikhHeera - as primitivas são construídas na implementação da linguagem para que o intérprete possa lhes dar poderes especiais.
jfriend00
1
@SheikhHeera - Não entendi seu último comentário / pergunta. Uma primitiva de string por si só não suporta a adição de suas próprias propriedades a ela. Para permitir isso, o javascript também tem um objeto String que tem todos os mesmos métodos de uma string primitiva, mas é um objeto desenvolvido que pode ser tratado como um objeto de todas as maneiras. Esta forma dupla parece ser um pouco confusa, mas eu suspeito que foi feita para comprometer o desempenho, uma vez que o caso de 99% é o uso de primitivas e elas provavelmente podem ser mais rápidas e mais eficientes em termos de memória do que objetos de string.
jfriend00
1
@SheikhHeera "convertido em string Object" é como o MDN o expressa para explicar como os primitivos são capazes de invocar métodos. Eles não são literalmente convertidos em objetos de string.
Fabrício Matté
4

Eu posso ver que esta questão foi resolvida há muito tempo, há outra distinção sutil entre literais de string e objetos de string, como ninguém parece ter tocado nisso, pensei em escrever apenas para completar.

Basicamente, outra distinção entre os dois é quando se usa eval. eval ('1 + 1') dá 2, enquanto eval (new String ('1 + 1')) dá '1 + 1', então se determinado bloco de código pode ser executado tanto 'normalmente' como com eval, ele poderia levar a resultados estranhos

luanped
fonte
Obrigado pela sua contribuição :-)
The Alpha
Uau, isso é um comportamento muito estranho. Você deve adicionar uma pequena demonstração em linha em seu comentário para mostrar esse comportamento - é extremamente revelador.
EyuelDK
isso é normal, se você pensar bem. new String("")retorna um objeto, e eval avalia apenas a string, e retorna todas as coisas como estão
Félix Brunet
3

A existência de um objeto tem pouco a ver com o comportamento real de uma String nos mecanismos ECMAScript / JavaScript, pois o escopo raiz simplesmente conterá objetos de função para isso. Assim, a função charAt (int) no caso de uma string literal será pesquisada e executada.

Com um objeto real, você adiciona mais uma camada onde o método charAt (int) também é pesquisado no próprio objeto antes que o comportamento padrão seja ativado (o mesmo que acima). Aparentemente, há uma quantidade surpreendentemente grande de trabalho realizada neste caso.

BTW, não acho que os primitivos sejam realmente convertidos em objetos, mas o mecanismo de script simplesmente marcará essa variável como tipo de string e, portanto, pode encontrar todas as funções fornecidas para que pareça que você invoca um objeto. Não se esqueça que este é um runtime de script que funciona em princípios diferentes de um runtime OO.

água limpa
fonte
3

A maior diferença entre uma primitiva de string e um objeto de string é que os objetos devem seguir esta regra para o ==operador :

Uma expressão que compara Objetos só é verdadeira se os operandos fizerem referência ao mesmo Objeto.

Portanto, embora os primitivos de string tenham uma conveniência ==que compara o valor, você está sem sorte quando se trata de fazer qualquer outro tipo de objeto imutável (incluindo um objeto de string) se comportar como um tipo de valor.

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

(Outros notaram que um objeto de string é tecnicamente mutável porque você pode adicionar propriedades a ele. Mas não está claro para que isso é útil; o valor da string em si não é mutável.)

personal_cloud
fonte
Obrigado por agregar valor à pergunta depois de tanto tempo :-)
The Alpha
1

O código é otimizado antes de ser executado pelo mecanismo javascript. Em geral, micro benchmarks podem ser enganosos porque compiladores e interpretadores reorganizam, modificam, removem e executam outros truques em partes de seu código para torná-lo executado mais rápido. Em outras palavras, o código escrito informa qual é o objetivo, mas o compilador e / ou o tempo de execução decidirá como atingir esse objetivo.

O bloco 1 é mais rápido principalmente por causa de: var s = '0123456789'; é sempre mais rápido do que var s = new String ('0123456789'); por causa da sobrecarga da criação do objeto.

A parte do loop não é a causa da desaceleração, porque chartAt () pode ser embutido pelo interpretador. Tente remover o loop e execute o teste novamente, você verá que a relação de velocidade será a mesma como se o loop não tivesse sido removido. Em outras palavras, para esses testes, os blocos de loop em tempo de execução possuem exatamente o mesmo bytecode / código de máquina.

Para esses tipos de micro benchmarks, olhar para o bytecode ou código de máquina fornecerá uma imagem mais clara.

Arco
fonte
1
Obrigado pela sua resposta.
The Alpha
0

Em Javascript, os tipos de dados primitivos, como string, são um bloco de construção não composto. Isso significa que eles são apenas valores, nada mais: let a = "string value"; Por padrão, não há métodos embutidos como toUpperCase, toLowerCase etc ...

Mas, se você tentar escrever:

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

Isso não gerará nenhum erro; em vez disso, eles funcionarão como deveriam.

O que aconteceu ? Bem, quando você tenta acessar uma propriedade de uma string, o aJavascript força a string a um objeto new String(a);conhecido como objeto wrapper .

Este processo está vinculado a conceitos chamados construtores de funções em Javascript, onde funções são utilizadas para criar novos objetos.

Quando você digita new String('String value');aqui String é o construtor de função, que recebe um argumento e cria um objeto vazio dentro do escopo da função, esse objeto vazio é atribuído a este e, neste caso, String fornece todas as funções internas conhecidas que mencionamos antes. e assim que a operação for concluída, por exemplo, fazer uma operação em maiúsculas, o objeto wrapper é descartado.

Para provar isso, vamos fazer isso:

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

Aqui, a saída será indefinida. Por quê ? Nesse caso, o Javascript cria o objeto String do wrapper, define a nova propriedade addNewProperty e descarta o objeto do wrapper imediatamente. é por isso que você fica indefinido. O pseudocódigo ficaria assim:

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

podemos definir String de três maneiras

  1. var a = "primeira maneira";
  2. var b = String ("segunda via");
  3. var c = nova String ("terceira via");

// também podemos criar usando 4. var d = a + '';

Verifique o tipo das strings criadas usando o operador typeof

  • tipo de // "string"
  • typeof b // "string"
  • typeof c // "objeto"


quando você compara a e b var a==b ( // yes)


quando você compara 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
fonte