As strings do JavaScript são imutáveis? Preciso de um "construtor de strings" em JavaScript?

237

O javascript usa seqüências imutáveis ​​ou mutáveis? Preciso de um "construtor de strings"?

DevelopingChris
fonte
3
Sim, eles são imutáveis ​​e você precisa de um "construtor de cadeias" de algum tipo. Leia este blog.codeeffects.com/Article/String-Builder-In-Java-Script ou este codeproject.com/KB/scripting/stringbuilder.aspx
Kizz
2
Interessante, esses exemplos contradizem minhas descobertas na minha resposta.
Juan Mendes

Respostas:

294

Eles são imutáveis. Você não pode alterar um caractere dentro de uma string com algo parecido var myString = "abbdef"; myString[2] = 'c'. Os métodos de manipulação de strings, como trim, sliceretornam novas strings.

Da mesma forma, se você tiver duas referências à mesma sequência, modificar uma não afeta a outra

let a = b = "hello";
a = a + " world";
// b is not affected

No entanto, sempre ouvi o que Ash mencionou em sua resposta (que usar Array.join é mais rápido para concatenação), então eu queria testar os diferentes métodos de concatenar seqüências de caracteres e abstrair a maneira mais rápida em um StringBuilder. Eu escrevi alguns testes para ver se isso é verdade (não é!).

Era o que eu achava que seria a maneira mais rápida, embora eu continuasse pensando que adicionar uma chamada de método pode torná-la mais lenta ...

function StringBuilder() {
    this._array = [];
    this._index = 0;
}

StringBuilder.prototype.append = function (str) {
    this._array[this._index] = str;
    this._index++;
}

StringBuilder.prototype.toString = function () {
    return this._array.join('');
}

Aqui estão os testes de velocidade de desempenho. Todos os três criam uma cadeia gigantesca composta de concatenação"Hello diggity dog" cem mil vezes em uma cadeia vazia.

Eu criei três tipos de testes

  • Usando Array.push eArray.join
  • Usando a indexação de matriz para evitar Array.push , em seguida, usandoArray.join
  • Concatenação de sequência reta

Então eu criei os mesmos três testes abstraindo-los em StringBuilderConcat, StringBuilderArrayPushe StringBuilderArrayIndex http://jsperf.com/string-concat-without-sringbuilder/5 Por favor, vá lá e executar testes para que possamos obter uma amostra agradável. Observe que eu corrigi um pequeno bug; portanto, os dados dos testes foram apagados; atualizarei a tabela assim que houver dados de desempenho suficientes. Acesse http://jsperf.com/string-concat-without-sringbuilder/5 para obter a tabela de dados antiga.

Aqui estão alguns números (última atualização no Ma5rch 2018), se você não quiser seguir o link. O número em cada teste é de 1000 operações / segundo (quanto maior, melhor )

| Browser          | Index | Push | Concat | SBIndex | SBPush | SBConcat |
---------------------------------------------------------------------------
| Chrome 71.0.3578 | 988   | 1006 | 2902   | 963     | 1008   | 2902     |
| Firefox 65       | 1979  | 1902 | 2197   | 1917    | 1873   | 1953     |
| Edge             | 593   | 373  | 952    | 361     | 415    | 444      |
| Exploder 11      | 655   | 532  | 761    | 537     | 567    | 387      |
| Opera 58.0.3135  | 1135  | 1200 | 4357   | 1137    | 1188   | 4294     | 

Constatações

  • Atualmente, todos os navegadores sempre verdes lidam bem com a concatenação de strings. Array.joinsó ajuda o IE 11

  • No geral, o Opera é mais rápido, 4 vezes mais rápido que o Array.join

  • O Firefox é o segundo e Array.joiné apenas um pouco mais lento no FF, mas consideravelmente mais lento (3x) no Chrome.

  • O Chrome é o terceiro, mas o concat de strings é 3 vezes mais rápido que o Array.join

  • Criar um StringBuilder parece não afetar muito o desempenho.

Espero que alguém ache isso útil

Caso de teste diferente

Como o @RoyTinker achou que meu teste era defeituoso, criei um novo caso que não cria uma grande string concatenando a mesma string, ele usa um caractere diferente para cada iteração. A concatenação de cordas ainda parecia mais rápida ou mais rápida. Vamos executar esses testes.

Sugiro que todos continuem pensando em outras maneiras de testar isso e fique à vontade para adicionar novos links para diferentes casos de teste abaixo.

http://jsperf.com/string-concat-without-sringbuilder/7

Juan Mendes
fonte
@ Juan, o link que você pediu para visitar concatena uma string de 112 caracteres 30 vezes. Aqui está outro teste que pode ajudar a equilibrar as coisas - Array.join x concatenação de strings em 20.000 strings de 1 caractere diferentes (a junção é muito mais rápida no IE / FF). jsperf.com/join-vs-str-concat-large-array #
Roy Tinker
1
@ RoyTinker Roy, oh Roy, seus testes estão trapaceando porque você está criando a matriz na configuração do teste. Aqui está o verdadeiro teste usando personagens diferentes jsperf.com/string-concat-without-sringbuilder/7 Sinta-se livre para criar novos casos de teste, mas criar a matriz é parte do teste em si
Juan Mendes
@JuanMendes Meu objetivo era restringir o caso de teste a uma comparação estrita da joinconcatenação de vs strings, criando assim a matriz antes do teste. Eu não acho que isso é trapaça se esse objetivo for entendido (e joinenumera a matriz internamente, portanto, não é trapaça omitir um forloop do jointeste).
Roy Tinker
2
@RoyTinker Sim, qualquer construtor de strings exigirá a construção da matriz. A questão é se um construtor de strings é necessário. Se você já tem as cordas em uma matriz, então não é um caso de teste válido para o que estamos discutindo aqui
Juan Mendes
1
@Baltusaj As colunas que dizem Index / push estão usandoArray.join
Juan Mendes
41

do livro rinoceronte :

No JavaScript, as strings são objetos imutáveis, o que significa que os caracteres dentro deles não podem ser alterados e que quaisquer operações nas strings realmente criam novas strings. As strings são atribuídas por referência, não por valor. Em geral, quando um objeto é atribuído por referência, uma alteração feita no objeto por meio de uma referência fica visível em todas as outras referências ao objeto. Como as seqüências de caracteres não podem ser alteradas, no entanto, você pode ter várias referências a um objeto de sequência e não se preocupar que o valor da sequência seja alterado sem que você saiba

menta
fonte
7
Link para uma seção apropriada do livro de rinocerontes: books.google.com/…
baudtack 6/06/09
116
A citação do livro Rhino (e, portanto, esta resposta) está errada aqui. No JavaScript, as strings são tipos de valores primitivos e não objetos (especificações) . De fato, a partir do ES5, eles são um dos únicos 5 tipos de valor ao lado de null undefined numbere boolean. As strings são atribuídas por valor e não por referência e são passadas como tal. Assim, as strings não são apenas imutáveis, elas são um valor . Mudar a string "hello"para ser "world"é como decidir que a partir de agora o número 3 é o número 4 ... não faz sentido.
Benjamin Gruenbaum
8
Sim, como meu comentário diz que as strings são imutáveis, mas não são tipos de referência nem objetos - são tipos de valor primitivo. Uma maneira fácil de ver que eles estão nem seria tentar para adicionar uma propriedade em uma string e, em seguida, lê-lo:var a = "hello";var b=a;a.x=5;console.log(a.x,b.x);
Benjamin Gruenbaum
9
@ VidarS.Ramdal Não, os Stringobjetos criados usando o construtor de strings são wrappers em torno dos valores de strings JavaScript. Você pode acessar o valor da sequência do tipo em caixa usando a .valueOf()função - isso também é válido para Numberobjetos e valores numéricos. É importante observar que os Stringobjetos criados usando new Stringnão são cadeias reais, mas são invólucros ou caixas em torno das cadeias. Consulte es5.github.io/#x15.5.2.1 . Sobre como as coisas converter em objetos ver es5.github.io/#x9.9
Benjamin Gruenbaum
5
Quanto ao motivo pelo qual algumas pessoas dizem que strings são objetos, provavelmente elas são provenientes de Python ou Lisp ou de qualquer outra linguagem em que suas especificações usem a palavra "objeto" para significar qualquer tipo de dado (mesmo números inteiros). Eles só precisam ler como a especificação do ECMA define a palavra: "membro do tipo Objeto". Além disso, mesmo a palavra "valor" pode significar coisas diferentes, de acordo com especificações de idiomas diferentes.
Jisang Yoo
21

Dica de desempenho:

Se você precisar concatenar cadeias grandes, coloque as partes em uma matriz e use o Array.Join()método para obter a cadeia geral. Isso pode ser muitas vezes mais rápido para concatenar um grande número de strings.

Não há StringBuilderno JavaScript.

Cinza
fonte
Eu sei que não há um stringBuilder, o msAjax tem um, e eu estava apenas pensando se é útil ou não
DevelopingChris
5
O que isso tem a ver com seqüências imutáveis ​​ou não?
baudtack
4
@docgnome: Porque strings são imutáveis, concatenação requer a criação de mais objetos do que a abordagem Array.join
Juan Mendes
8
De acordo com o teste de Juan acima, a concatenação de strings é realmente mais rápida no IE e no Chrome, enquanto mais lenta no Firefox.
Bill Yang
9
Considere atualizar sua resposta, pode ter sido verdade há muito tempo, mas não é mais. Veja jsperf.com/string-concat-without-sringbuilder/5
Juan Mendes
8

Apenas para esclarecer mentes simples como a minha (da MDN ):

Imutáveis ​​são os objetos cujo estado não pode ser alterado depois que o objeto é criado.

String e Numbers são imutáveis.

Imutável significa que:

Você pode fazer um nome de variável apontar para um novo valor, mas o valor anterior ainda é mantido na memória. Daí a necessidade de coleta de lixo.

var immutableString = "Hello";

// No código acima, um novo objeto com o valor da string é criado.

immutableString = immutableString + "World";

// Agora estamos anexando "Mundo" ao valor existente.

Parece que estamos mudando a string 'immutableString', mas não estamos. Em vez de:

Ao anexar o "immutableString" com um valor de sequência, ocorrem os seguintes eventos:

  1. O valor existente de "immutableString" é recuperado
  2. "Mundo" é anexado ao valor existente de "immutableString"
  3. O valor resultante é então alocado para um novo bloco de memória
  4. O objeto "immutableString" agora aponta para o espaço de memória recém-criado
  5. O espaço de memória criado anteriormente está agora disponível para coleta de lixo.
Katinka Hesselink
fonte
4

O valor do tipo string é imutável, mas o objeto String, criado usando o construtor String (), é mutável, porque é um objeto e você pode adicionar novas propriedades.

> var str = new String("test")
undefined
> str
[String: 'test']
> str.newProp = "some value"
'some value'
> str
{ [String: 'test'] newProp: 'some value' }

Enquanto isso, embora você possa adicionar novas propriedades, não é possível alterar as propriedades já existentes.

Captura de tela de um teste no console do Chrome

Em conclusão, 1. todo o valor do tipo string (tipo primitivo) é imutável. 2. O objeto String é mutável, mas o valor do tipo de string (tipo primitivo) que ele contém é imutável.

zhanziyang
fonte
Os objetos Javascript String são imutáveis developer.mozilla.org/pt-BR/docs/Web/JavaScript/Data_structures
prasun 14/10/15
@prasun mas nessa página diz: "Todos os tipos, exceto os objetos, definem valores imutáveis ​​(valores que não podem ser alterados)." Objetos de seqüência de caracteres são objetos. E como é imutável se você pode adicionar novas propriedades?
zhanziyang
leia a seção "Tipo de string". O link String do Javascript aponta para Primitive e Object e, mais tarde, diz "As strings JavaScript são imutáveis". Parece que o documento não é clara sobre este assunto, uma vez que entra em conflito em duas notas diferentes
Prasun
6
new Stringgera um invólucro mutável em torno de uma seqüência imutável
tomdemuyt
1
É muito fácil testar executando o código de @ zhanziyang acima. Você pode adicionar totalmente novas propriedades a um Stringobjeto (invólucro), o que significa que não é imutável (por padrão; como qualquer outro objeto que você pode chamar Object.freezepara torná-lo imutável). Mas um tipo de valor de cadeia primitivo, contido Stringou não em um invólucro de objeto, é sempre imutável.
Mark Reed
3

Strings são imutáveis - elas não podem mudar, só podemos criar novas strings.

Exemplo:

var str= "Immutable value"; // it is immutable

var other= statement.slice(2, 10); // new string
GibboK
fonte
1

Em relação à sua pergunta (no seu comentário à resposta de Ash) sobre o StringBuilder no ASP.NET Ajax, os especialistas parecem discordar.

Christian Wenz diz em seu livro Programming ASP.NET AJAX (O'Reilly) que "essa abordagem não tem nenhum efeito mensurável na memória (de fato, a implementação parece ser um tique mais lento que a abordagem padrão)".

Por outro lado, Gallo e colaboradores dizem em seu livro ASP.NET AJAX in Action (Manning) que "quando o número de seqüências de caracteres para concatenar é maior, o construtor de seqüências de caracteres se torna um objeto essencial para evitar grandes quedas de desempenho".

Acho que você precisaria fazer seu próprio benchmarking e os resultados também podem diferir entre os navegadores. No entanto, mesmo que não melhore o desempenho, ainda pode ser considerado "útil" para programadores acostumados a codificar com StringBuilders em linguagens como C # ou Java.

BirgerH
fonte
1

É um post tardio, mas não encontrei uma boa citação de livro entre as respostas.

Aqui está uma definição, exceto em um livro confiável:

Strings são imutáveis ​​no ECMAScript, o que significa que, uma vez criados, seus valores não podem ser alterados. Para alterar a string mantida por uma variável, a string original deve ser destruída e a variável preenchida com outra string contendo um novo valor ... - JavaScript profissional para desenvolvedores web, 3ª Ed., P.43

Agora, a resposta que cita o trecho do livro Rhino está certa sobre imutabilidade de strings, mas está errado dizer "Strings são atribuídos por referência, não por valor". (provavelmente eles originalmente pretendiam colocar as palavras de maneira oposta).

O equívoco "referência / valor" é esclarecido no capítulo "JavaScript profissional", chamado "Valores primitivos e de referência":

Os cinco tipos primitivos ... [são]: Indefinido, Nulo, Booleano, Número e String. Diz-se que essas variáveis ​​são acessadas por valor, porque você está manipulando o valor real armazenado na variável. - JavaScript profissional para desenvolvedores da Web, 3ª Ed., P.85

isso é contra objetos :

Quando você manipula um objeto, está realmente trabalhando em uma referência a esse objeto, e não no próprio objeto. Por esse motivo, diz-se que esses valores são acessados ​​por referência. - JavaScript profissional para desenvolvedores da Web, 3ª Ed., P.85

regozijar-se
fonte
FWIW: O livro Rhino provavelmente significa que internamente / implementar uma atribuição de string está armazenando / copiando um ponteiro (em vez de copiar o conteúdo da string). Não parece um acidente da parte deles, com base no texto depois disso. Mas eu concordo: eles usam mal o termo "por referência". Não é "por referência" apenas porque a implementação passa ponteiros (para desempenho). A estratégia de avaliação da Wiki é uma leitura interessante sobre esse tópico.
Página
-2

Strings em Javascript são imutáveis

Glenn Slaven
fonte