Javascript: A diferença entre Constructor Function e Object Literal

Meses atrás, escrevi sobre como era fácil criar objetos em Javascript. Devo confessar que, a minha visão sobre a utilização da Orientação a Objetos em Javascript era muito superficial, e que uma dúvida muito pertinente surgiu na época: Onde usar object literal e onde usar classes?

Hoje, depois de entender que a Orientação a Objetos no Javascript não é muito diferente da utilizada em outras linguagens, sou capaz de enxergar as possibilidades utilizando as duas maneiras. E este é o objetivo deste post: compartilhar esta visão com vocês.

Traçando um paralelo

O Nettuts fez um ótimo post comparando as diferenças entre os dois métodos de criação de objetos (este artigo é fortemente baseado na publicação deles). Vamos adotar a estratégia deles e criar estruturas semelhantes utilizando object literal e constructors:

var object_literal = {
  Automovel: {
    quantidadeRodas: 4,
  },
};

var constructor = {
  Automovel: function() {
    this.quantidadeRodas = 4;
  },
};

Acima já reparamos numa das maiores vantagens do object literal: criar namespaces. Isolamos as duas declarações para que possamos usar os mesmos nomes:

// object literal
var carro = object_literal.Automovel;
console.log("quantidade de rodas:", carro.quantidadeRodas); // 4

// constructor function
var carro = new constructor.Automovel();
console.log("quantidade de rodas:", carro.quantidadeRodas); // 4

Acessar quantidadeRodas de object_literal.Automovel é muito mais fácil do que de constructor.Automovel. Não é errado dizer que quantidadeRodas de constructor.Automovel é um “atributo de instância”, logo, é necessário criar uma instância da classe para acessá-lo.

Os métodos entram em ação

Vamos atribuir aos nossos automóveis a capacidade de ligarMotor:

var object_literal = {
  Automovel: {
    quantidadeRodas: 4,
    motorLigado: false,

    ligarMotor: function() {
      //object_literal.Automovel.motorLigado = true;
      this.motorLigado = true;
    },
  },
};

var constructor = {
  Automovel: function() {
    this.quantidadeRodas = 4;
    this.motorLigado = false;
  },
};

constructor.Automovel.prototype.ligarMotor = function() {
  this.motorLigado = true;
};

Aqui que as diferenças começam a ficar mais “gritantes”.

Por não conseguirmos criar uma instância de um object literal (afinal de contas, ele já é um objeto), não conseguimos fazer uma referência this como em classes não temos a definição de atributos e métodos via Prototype. Se o fizéssemos, estaríamos explorando atributos da função anônima associada à propriedade ligarMotor, da propriedade Automovel, da variável object_literal. Mas, conseguimos sim explorar atributos e métodos do objeto através do this. O mesmo ocorre em ligarMotor de constructor, o this é capaz de acessar os valores e métodos nas instâncias de Automovel:

// object literal
carro.ligarMotor();
console.log("motor ligado:", carro.motorLigado); // true

// constructor function
carro.ligarMotor();
console.log("motor ligado:", carro.motorLigado); // true

Atributos diferentes para instâncias diferentes

Sabemos que um automóvel pode ter 2, 4, 6 ou até 8 rodas, então vamos adaptá-los:

var constructor = {
  Automovel: function(qtndRodas) {
    this.quantidadeRodas = qtndRodas;
    this.motorLigado = false;
  },
};

Ué?! Mas e o object literal? Pois é.. como ele não pode gerar instâncias (novamente, ele já é um objeto), temos que alterar “na mão”:

// object literal
var moto = object_literal.Automovel;
moto.quantidadeRodas = 2;
console.log("quantidade de rodas (moto):", moto.quantidadeRodas); // 2

// constructor function
var moto = new constructor.Automovel(2);
console.log("quantidade de rodas (moto):", moto.quantidadeRodas); // 2

Só por curiosidade, como estarão os nossos objetos carro? (Lá vem a pegadinha =P )

// object literal
console.log("quantidade de rodas (carro):", carro.quantidadeRodas); // 2

// constructor function
console.log("quantidade de rodas (carro):", carro.quantidadeRodas); // 4

Pelo lado da instância de constructor.Automovel, nenhuma surpresa. Mas o que aconteceu com o valor do atributo quantidadeRodas da variável carro em object_literal.Automovel?

Instâncias x Referências

A grosso modo, com uso de constructor functions, criamos uma “estrutura” (classe), que a cada new é “copiada” para um novo espaço na memória (instância). Assim, a propriedade quantidadeRodas de moto é diferente da propriedade de mesmo nome, da instância carro (leia mais sobre instâncias e classes):

// constructor function
console.log("carro == moto:", carro == moto); // false

Já com object literal, não temos a capacidade de criar uma classe. Criamos diretamente uma instância, e vinculamos o espaço na memória onde esta foi criada à uma variável. No nosso caso, a variável em questão é object_literal.Automovel.

Quando atribuímos object_literal.Automovel à variável carro e depois à variável moto, na verdade estávamos criando referências a instânca contida em object_literal.Automovel (ou seja, as três variáveis correspondem ao mesmo endereço e valor na memória):

// object literal
console.log("carro == moto:", carro == moto); // true

Portanto, se eu criar uma variável chamada moto2 e atribuir a ela a instância de constructor.Automovel contida em moto, terei o mesmo resultado que acima:

// constructor function
var moto2 = moto;
moto2.quantidadeRodas = 3;
console.log("quantidade de rodas (instancia carro):", carro.quantidadeRodas); // 4
console.log("quantidade de rodas (instancia moto):", moto.quantidadeRodas); // 3
console.log("quantidade de rodas (referencia moto2):", moto2.quantidadeRodas); // 3
console.log("moto == moto2:", moto == moto2); // true

Interessante, não?

Considerações finais

Uma vez descobrindo a diferença entre object literal e constructor function, notamos que não há diferenças conceituais em relação a linguagens como Python, C++ ou Java. Basta termos em mente que o tipo de escrita, através da prototype, é diferente (e mais dinâmica) do que o usual, mas os resultados são (conceitualmente falando) praticamente os mesmos.

Então, quando você tiver um tipo que possuirá várias instâncias, utilize constructor functions. Quando quiser criar objetos “estáticos”, que não sofrerão alterações no decorrer de uma execução (como o exemplo do namespace), utilize object literal.

Referências

O exemplo completo está disponível para download em: https://github.com/kplaube/post-javascript-object-literal-constructors

Até a próxima…