Como funciona a atribuição de variável em JavaScript?

97

Então eu estava brincando outro dia apenas para ver exatamente como a atribuição em massa funciona em JavaScript.

Primeiro tentei este exemplo no console:

a = b = {};
a.foo = 'bar';
console.log(b.foo);

O resultado foi "bar" sendo exibido em um alerta. Isso é bastante justo ae , na bverdade , são apenas apelidos para o mesmo objeto. Então eu pensei, como eu poderia tornar este exemplo mais simples.

a = b = 'foo';
a = 'bar';
console.log(b);

É praticamente a mesma coisa, não é? Bem, desta vez, ele foonão retorna barcomo eu esperava do comportamento do primeiro exemplo.

Por que isso acontece?

NB Este exemplo pode ser simplificado ainda mais com o seguinte código:

a = {};
b = a;
a.foo = 'bar';
console.log(b.foo);

a = 'foo';
b = a;
a = 'bar';
console.log(b);

(Suspeito que o JavaScript trata primitivos como strings e inteiros de maneira diferente para hashes. Os hashes retornam um ponteiro, enquanto os primitivos "principais" retornam uma cópia de si mesmos)

Chris Lloyd
fonte
Explicando a atribuição aqui: syntaxsuccess.com/viewarticle/…
TGH

Respostas:

114

No primeiro exemplo, você está definindo uma propriedade de um objeto existente. No segundo exemplo, você está atribuindo um novo objeto.

a = b = {};

ae bagora são ponteiros para o mesmo objeto. Então, quando você faz:

a.foo = 'bar';

Estabelece b.footambém desde ae bapontam para o mesmo objeto.

Contudo!

Se você fizer isso:

a = 'bar';

você está dizendo que agora aaponta para um objeto diferente. Isso não tem efeito sobre o que foi aapontado antes.

Em JavaScript, atribuir uma variável e atribuir uma propriedade são 2 operações diferentes. É melhor pensar em variáveis ​​como ponteiros para objetos, e quando você atribui diretamente a uma variável, você não está modificando nenhum objeto, apenas repondo sua variável para um objeto diferente.

Mas atribuir uma propriedade, como a.foo, modificará o objeto para o qual aaponta. Isso, é claro, também modifica todas as outras referências que apontam para esse objeto simplesmente porque todas apontam para o mesmo objeto.

Alex Wayne
fonte
3
"você está dizendo que a aponta para um objeto diferente agora." Não, não use a palavra objeto. Uma string não é um objeto em JavaScript.
Nosredna
9
Talvez as strings não sejam tecnicamente do tipo javascript "Objeto", mas podem ser consideradas objetos no sentido OO.
Alex Wayne
2
@Squeegy: strings são primitivas, não objetos: você não pode atribuir propriedades arbitrárias a strings! Eles só se comportam como objetos por causa do que é chamado de autoboxing em Java
Christoph
11
Mas strings têm métodos e propriedades, e o protótipo de String pode definitivamente ser modificado. Eles certamente agem como objetos.
Cameron Martin
9
@ddlshack: Como Christoph explicou, isso se deve ao autoboxing. Se você tivesse que definir uma string via var foo = new String('foo');, então seria um objeto string (e irá confirmar isso). Mas se você declarar por meio de um literal de string, então eles são primitivos de string. Veja: developer.mozilla.org/en/JavaScript/Reference/Global_Objects/…typeof
Lèse majesté
26

Sua pergunta já foi satisfatoriamente respondida por Squeegy - ela não tem nada a ver com objetos vs. primitivos, mas com reatribuição de variáveis ​​vs. propriedades de configuração no mesmo objeto referenciado.

Parece haver muita confusão sobre os tipos de JavaScript nas respostas e comentários, então aqui está uma pequena introdução ao sistema de tipos do JavaScript:

Em JavaScript, existem dois tipos fundamentalmente diferentes de valores: primitivos e objetos (e não existe nada como um 'hash').

Cordas, números e booleanos, bem como nulle undefinedsão primitivos, os objetos são tudo o que pode ter propriedades. Mesmo matrizes e funções são objetos regulares e, portanto, podem conter propriedades arbitrárias. Eles apenas diferem na propriedade interna [[Class]] (funções adicionalmente têm uma propriedade chamada [[Call]] e [[Construct]], mas ei, isso são detalhes).

A razão pela qual os valores primitivos podem se comportar como objetos é o autoboxing, mas os próprios primitivos não podem conter nenhuma propriedade.

Aqui está um exemplo:

var a = 'quux';
a.foo = 'bar';
document.writeln(a.foo);

Isso resultará em undefined: acontém um valor primitivo, que é promovido a um objeto ao atribuir a propriedade foo. Mas esse novo objeto é imediatamente descartado, então o valor de fooé perdido.

Pense assim:

var a = 'quux';
new String(a).foo = 'bar'; // we never save this new object anywhere!
document.writeln(new String(a).foo); // a completly new object gets created
Christoph
fonte
A página da Mozilla Foundation 'Uma reintrodução ao JavaScript (tutorial JS)' descreve objetos JavaScript "como coleções simples de pares nome-valor. Como tal, eles são semelhantes a ...", em seguida, segue com uma lista de dicionários, hash , tabelas de hash e mapas de hash de várias linguagens de programação. A mesma página descreve referências de propriedade de objeto como pesquisas de tabela de hash. Portanto, os objetos são tudo como uma tabela 'hash'. Isso não anula as outras informações úteis, mas a caracterização original de Chris Lloyd não era imprecisa.
C Perkins
2

Você está mais ou menos correto, exceto que o que está se referindo como um "hash" é, na verdade, apenas uma sintaxe abreviada para um objeto.

No primeiro exemplo, um e b referem-se ambos ao mesmo objecto. No segundo exemplo, você altera a para se referir a outra coisa.

Kevin
fonte
Então, por que os padrões duplos para Object?
Chris Lloyd
Não é um padrão duplo. No primeiro exemplo, aeb ainda se referem ao mesmo objeto, você está apenas modificando uma propriedade desse objeto. No segundo exemplo, você está apontando para um objeto diferente.
Kevin
1
Não, a diferença é que, no segundo caso, você está lidando com uma string, não com um objeto.
Nosredna
1
Para ser claro: isso não tem nada a ver com strings retornando uma cópia de si mesmas. A razão pela qual os dois trechos de código são diferentes está no segundo parágrafo de Kevin (explicado com mais detalhes na resposta de Squeegy).
Chuck
Não importa se você tem uma string ou um objeto na variável. Você atribui um valor novo e diferente e a variável contém esse valor novo e diferente.
sth
2

aqui está minha versão da resposta:

obj = {a:"hello",b:"goodbye"}
x = obj
x.a = "bonjour"

// now obj.a is equal to "bonjour"
// because x has the same reference in memory as obj
// but if I write:
x = {}
x.a = obj.a
x.b = obj.b
x.a = "bonjour"

// now x = {a:"bonjour", b:"goodbye"} and obj = {a:"hello", b:"goodbye"}
// because x points to another place in the memory
Bashir Abdelwahed
fonte
0

Você está definindo a para apontar para um novo objeto string, enquanto b continua apontando para o objeto string antigo.

mdm
fonte
0

No primeiro caso você altera alguma propriedade do objeto contido na variável, no segundo caso você atribui um novo valor à variável. Isso são coisas fundamentalmente diferentes. As variáveis ae bnão são de alguma forma magicamente ligadas pela primeira atribuição, elas apenas contêm o mesmo objeto. Esse também é o caso no segundo exemplo, até que você atribua um novo valor à bvariável.

sth
fonte
0

A diferença é entre tipos e objetos simples.

Qualquer coisa que seja um objeto (como um array ou uma função) é passado por referência.

Qualquer coisa que seja um tipo simples (como uma string ou um número) é copiado.

Sempre tenho uma função copyArray à mão, então posso ter certeza de que não estou criando um monte de aliases para o mesmo array.

Nosredna
fonte
A diferença não é perceptível em muitos cenários, mas o Javascript não é aprovado ou atribuído por referência. Ele copia os valores de referência.
Juan Pablo Califano
Esses caras já fizeram um bom trabalho explicando isso, então vou apenas colar o link: stackoverflow.com/questions/40480/is-java-pass-by-reference (me refiro a Java, mas a semântica para passar e atribuir os valores / referências são iguais aos de Javascript)
Juan Pablo Califano
1
Na verdade essa resposta está incorreta, tudo é passado por valor em JavaScript. De MDN, "Os parâmetros de uma chamada de função são os argumentos da função. Os argumentos são passados ​​para funções por valor. Se a função alterar o valor de um argumento, essa alteração não será refletida globalmente ou na função de chamada. No entanto, as referências de objeto são valores também, e são especiais: se a função altera as propriedades do objeto referido, essa alteração é visível fora da função. " developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
bittersweetryan
Os primitivos se comportam como objetos imutáveis ​​( exatamente como eles no modo estrito). Esta resposta não está correta.
Ry-