Objetos - Referências de valores em JavaScript

Vamos conversar um pouco sobre objetos em JavaScript? Vem comigo então =)

Em todas as linguagens (ao menos todas que eu conheço :P), existem duas formas de você passar valores: - Por referência; - Por valor.

Quando eu digo “passar valores”, eu estou me referindo à:

  • Atribuir um valor à uma variável;
  • Atribuir um valor à uma propriedade ou método de um objeto;
  • Passar argumentos para uma função (ou método de um objeto).

Por exemplo:

1
2
var string = 'uma string qualquer'
var objeto = {}

No exemplo acima, eu atribuí o valor 'uma string qualquer' à variável string, e também atribuí um novo objeto à variável objeto.

Até aqui, nada de anormal. Mas existem alguns conceitos por detrás dessas atribuições:

O valor 'uma string qualquer' foi passado para a variável string por valor.

Já o objeto passado para a variável objeto, foi passado por referência.

E para explicar a diferença entre essas duas formas de passagem passagem de valor, precisamos entender algumas coisas da linguagem JavaScript.

Tipos de dados em JavaScript

JavaScript tem alguns tipos de dados que podemos separar em:

  • Tipos primitivos;
  • Objetos.

Os tipos primitivos da linguagem são: number, string, boolean, null e undefined. (Symbol também é um tipo primitivo, que veio no novo pacotinho do ES6/2015).

Todos os outros tipos de dados em JavaScript são do tipo object: - Objetos; - Funções; - Arrays; - Regex; - etc.

Tá, e daí? O que isso tem a ver com o post?

Aí que está: tem tudo a ver! E agora que você conhece quem são os tipos primitivos e quem são os objetos, eu já posso te dizer que:

  • Tipos primitivos são passados por valor, e são imutáveis;
  • Objetos são passados por referência, e são mutáveis.

Guarde bem essa informação, ela vai ser útil logo, logo =)

Passagem de dados “por valor”

Quando é feita uma passagem de tipos por valor, significa que o valor foi copiado.

Vamos aos exemplos:

1
2
3
var a = 10
var b = 10
console.log(a === b) // true

No exemplo, eu atribuí o valor 10 para as variáveis a e b. O 10 - tipo primitivo number - foi passado por valor, ou seja, ele foi copiado para as variáveis a e b. Ao tentar comparar seus valores, vemos que o resultado é true, ou seja, é o mesmo valor.

Agora vamos ver o que acontece se tentarmos fazer o mesmo com um objeto:

1
2
3
var a = {}
var b = {}
console.log(a === b) // false

O resultado da comparação deu false? Por quê?

Passagem de tipos “por referência”

Lembra que eu falei ali em cima que objetos são passados por referência? Então, vou explicar o que está acontecendo.

A cada vez que você cria um novo objeto, o JavaScript reserva um espaço para esse objeto na memória. Sim, alguns bytes da memória do seu computador são reservados para guardar as informações desse novo objeto criado.

Usar as chaves {} é o mesmo que new Object(), só que mais rápido (performance não vem ao caso agora).

O que eu quero dizer é que, sempre que você faz isso no seu código: {}; você está criando um novo objeto.

E a cada novo objeto criado, um novo espaço na memória é ocupado.

Huummmm, acho que estou sacando xD

É isso mesmo! O resultado do exemplo acima deu false, porque são dois objetos diferentes! Cada um foi criado em um espaço diferente da memória!

O que acontece quando você cria um novo objeto e o atribui à uma variável, é que, a partir desse momento, essa variável passa a apontar para o objeto criado. É o que chamamos de ponteiro.

Logo, se fizermos:

1
2
3
var a = {}
var b = a
console.log(a === b) // true

Ou seja: a operação acima não copiou o valor do objeto que foi atribuido à a para a variável b; o que aconteceu foi que, agora as duas variáveis apontam para o mesmo objeto.

  • O objeto foi criado e atribuído à variável a;
  • Criamos uma nova variável b, e atribuímos o valor de a para b;
  • Como o valor de a é um objeto, o valor não foi copiado, mas sim passado por referência;
  • E agora as duas variáveis são ponteiros, que apontam para o mesmo local na memória onde está esse objeto.

Deu pra entender?

Tá, entendi, mas por que isso é tão importante assim?

Pelo seguinte motivo: lembra que eu falei ali em cima que objetos são mutáveis e tipos primitivos não? Eis a treta:

1
2
3
4
var a = {}
var b = a
a.prop = 'any value'
console.log(b.prop) // 'any value'

Olha só o que aconteceu: como objetos são mutáveis, significa que, mesmo após ter criado o objeto, você pode modificá-lo, adicionando ou removendo propriedades e métodos.

No exemplo acima, eu fiz o seguinte: - Criei um novo objeto e atribuí à variável a; - Criei uma variável b, e atribuí o objeto passado para a, por referência; - Usando a variável a, eu criei uma nova propriedade chamada prop, com a string 'any value'; - Ao verificar o que tem na propriedade prop da variável b, vemos que lá está o valor que foi atribuído à variável a.

E isso acontece simplesmente porque o objeto atribuido para a e b é o mesmo!

Esse comportamento acontece com qualquer tipo de dado que seja um objeto. Olhe, por exemplo, com uma função:

1
2
3
4
5
6
7
8
9
10
var a = function () {
return 'a'
}

var b = a

a.prop = 'any value'

console.log(b()) // 'a'
console.log(b.prop) // 'any value'

Veja que, como funções também são objetos, você também pode adicionar novas propriedades e métodos.

No exemplo acima, eu criei uma função, e adicionei uma propriedade prop. Perceba que a função é mutável, ou seja, eu consigo criar novas propriedades mesmo depois que a função já foi criada, e veja como ela é passada por referência para b, pois eu fiz as modificações na variável a, e elas foram refletidas também na variável b.

Acho que ficou claro até aqui, certo? Se não ficou, só comenta aí no post que a gente discute sobre o assunto =)

Por agora, vamos prosseguir =)

Passando tipos por referência, via argumento de função

Você pode passar referências de objetos não somente usando atribuição, mas também como argumentos para uma função. Veja o exemplo:

1
2
3
4
5
6
7
function updateObject (object) {
object.newProp = 'vixx, mudou mesmo!'
}

var object = {}
updateObject(object)
console.log(object) // { newProp: 'vixx, mudou mesmo!' }

Olha que loucura o que aconteceu aqui em cima:

  • Criei uma nova função chamada updateObject, que recebe via argumento um objeto qualquer, e adiciona uma nova popriedade newProp nesse objeto, com o valor 'vixx, mudou mesmo!';
  • Logo abaixo, crio um novo objeto e atribuo à variável object;
  • Invoco a função updateObject, passando por parâmetro o valor da variável object. Lembre-se que objetos são passados por referência;
  • Logo após invocar a função - que não retorna valor algum - eu testo o valor da variável object, e olhe o que aconteceu: uma nova propriedade newProp foi criada e o valor 'vixx, mudou mesmo!' atribuído!

Nossa que legal! Então quer dizer que eu posso passar qualquer objeto para uma função, e modificá-lo lá dentro que, quando eu pegar meu objeto novamente, ele vai estar atualizado?

Sim, você pode. Mas NÃO DEVE!. Isso se chama efeito colateral (ou side effect), e isso é assunto para um outro post (que vai sair em breve xD).

Por enquanto, você só precisa saber que NÃO DEVE fazer isso, de forma alguma.

Entender essa forma de passagem de valores em JavaScript é muito importante se você quer aprender a linguagem de verdade.

Entendendo isso, você consegue, no mínimo, duas coisas:

  • melhorar a performance da sua aplicação (pois não fica criando objetos sem necessidade);
  • evita bugs causados por efeitos colaterais (pois não vai modificar um objeto diretamente, mas vai trabalhar com imutabilidade).

Imutabilidade é o assunto do nosso próximo post, aguarde!

Enquanto isso: deu pra entender o assunto do post? Ficou alguma dúvida? Gostaria de acrescentar algo? Comenta aí embaixo e até o próximo! :D