Criação de objetos em Javascript

Utilizando JavaScript, existem varias formas de se construir um objeto. As duas formas mais conhecidas são:  utilizando a notação literal e utilizando funções construtoras. É uma questão que aparece bastante com alunos mais experientes no nosso curso de JavaScript e jQuery.

Estas duas formas são abordadas nesse excelente post do Ricardo Valeriano, onde ele explica, além destas formas, uma boa maneira de adicionar atributos e métodos em todas as instâncias de determinada função: o prototype.

Suponha o seguinte exemplo de função construtora:

var Pessoa = function(nome, email) {
     this.nome =  nome;
 
     // verifica se o e-mail foi preenchido
     if (email) {
          this.email = email;    
     }
};

Para adicionar um método nos objetos do tipo Pessoa, poderíamos simplesmente adicionar uma função à referência this:

var Pessoa = function(nome, email) {
     this.nome =  nome;
 
     // verifica se o e-mail foi preenchido
     if (email) {
          this.email = email;    
     }

     this.fala = function(){
          console.log("Olá, meu nome é "+this.nome+" e meu email é "+this.email);
     };
};

Este padrão é chamado de Constructor paradigm por Nicholas Zakas em seu livro “Professional JavaScript for Web Developers”.

Mas, se conseguimos adicionar informações na própria função construtora, por que adicionaríamos no prototype da função?

Acontece que, se adicionarmos este novo método na função construtora, a cada vez que instanciarmos um objeto a partir desta função esse método será carregado novamente na memória.

Quando instanciamos um objeto, este já tem uma referência(__proto__) para o prototype da função que o construiu. Ou seja, só haverá um método na memória: o definido no prototype da função construtora!

Mas então como eu adiciono um método a partir do protótipo de uma função?

var Pessoa = function(nome, email) {
     this.nome =  nome;
 
     // verifica se o e-mail foi preenchido
     if (email) {
          this.email = email;    
     }
};

Pessoa.prototype.fala = function(){
     console.log("Olá, meu nome é "+this.nome+" e meu email é "+this.email);
};

E se eu quiser mais um método “anda”? Você precisaria novamente modificar o prototipo de Pessoa:

var Pessoa = function(nome, email) {
     this.nome =  nome;
 
     // verifica se o e-mail foi preenchido
     if (email) {
          this.email = email;    
     }
};

Pessoa.prototype.fala = function(){
     console.log("Olá, meu nome é "+this.nome+" e meu email é "+this.email);
};

Pessoa.prototype.anda = function(){
     console.log("Estou andando");
};

Um outro modo de se adicionar métodos aos protótipos das funções sem ter que chamar o prototype todas as vezes é sobrescrevê-lo por completo:

var Pessoa = function(nome, email) {
     this.nome =  nome;
 
     // verifica se o e-mail foi preenchido
     if (email) {
          this.email = email;    
     }
};

Pessoa.prototype ={
     constructor: Pessoa,     

     fala : function(){
         console.log("Olá, meu nome é "+this.nome+" e meu email é "+this.email);
     },
     
     anda : function(){
         console.log("Estou andando");
     }
};

Note que, como sobrescrevemos o protótipo por inteiro, precisamos setar também qual é a função construtora daquele protótipo (no caso, Pessoa).

Agora só falta instanciarmos uma pessoa. Para isso, existe uma palavra chave new:

var leo = new Pessoa("Leonardo", "leonardo.wolter@caelum.com.br");
leo.fala(); // Olá, meu nome é Leonardo e meu email é leonardo.wolter@caelum.com.br

Este modo de se criar um objeto onde há uma mistura da função construtora (com os atributos) e o prototype desta (com os métodos) foi apelidado por Douglas Crockford de Pseudo-classical pattern por tentar ser parecido com as classes de linguagens como Java.

O Pseudo-classical pattern não é o único jeito de criar um objeto. Alguns diriam que, apesar do ganho de memória e performance, o código acaba ficando poluído: não é tão lógico, para quem vem de outras linguagens orientadas a objetos como o Java, olhar para algumas propriedades dentro de uma função e outras fora dela (no prototype) e inferir que tudo isso representa uma classe.

Observação importante:
O uso da palavra new quando utilizando o Pseudo-classical pattern não é dispensável.
Uma vez que você utilizou a referência this dentro de sua função, caso não utilize o new para instanciá-la, o this apontará para window, criando variáveis globais!.
Para evitar esquecer da palavra new (não, você não será avisado pelo interpretador), é uma boa prática sempre utilizar o padrão camelcase no nome de funções construtoras, isso te dará uma dica de que você precisa do new para utilizá-la.

Talvez os defensores desta ideia gostem mais de um modo com uma sintaxe mais limpa: construir objetos utilizando funções que retornam simplesmente objetos literais:

var pessoa = function(nome, email){
     return {
         nome : nome,
         email: email,
         fala: function(){
             console.log("Olá, meu nome é "+this.nome+" e meu email é "+this.email);
         }
     };
};

Deste modo, para obter uma pessoa, basta chamar a função acima:

var leo = pessoa("Leonardo", "leonardo.wolter@caelum.com.br");
leo.fala(); // Olá, meu nome é Leonardo e meu email é leonardo.wolter@caelum.com.br

Uma vez que nós estamos simplesmente retornando um objeto literal, o uso da palavra chave new se torna dispensável.

Conclusão

É importante notar que o resultado não é exatamente o mesmo:

Quando utilizando o Pseudo-classical pattern, os métodos estão nos prototypes das funções construtoras, que só são carregadas uma vez, ocasionando uma melhora de performance e menor consumo de memória. Apesar disso, ele não tem uma forma de criar atributos/métodos privados e acessá-los em métodos públicos (dentro de prototypes), uma séria desvantagem.

Enquanto isso, no método não prototype-based, nós não temos problemas com atributos/métodos privados e públicos uma vez que não temos prototypes. Mas, por não termos prototypes, todos os métodos e atributos estão no objeto construído e serão carregados sempre que chamarmos a função pessoa.

Estamos trocando performance por estética e qualidade de código. Assim, é importante verificar e escolher o modo mais adequado ao seu caso.

37 Comentários

  1. Roberto Shizuo 15/04/2014 at 19:54 #

    Parabens pelo post Leonardo!

  2. Leonardo Wolter 15/04/2014 at 20:07 #

    Muito obrigado, Roberto!

  3. Alexandre Gama 16/04/2014 at 06:10 #

    Muito bacana Leo!

    Essa sacada de estética do código por performance se encaixou muito bem no post!

  4. Frederico barbosa 16/04/2014 at 06:47 #

    Object.create ecma5 não resolve esse problema não?

  5. Leonardo Wolter 16/04/2014 at 10:23 #

    O que o Object.create faz é criar um objeto utilizando uma função construtora(constructor paradigm) e atribui o objeto passado como o protótipo do objeto criado.

    O problema é que como você não tem acesso a essa função construtora e, por isso, não vai conseguir adicionar atributos e fazer alguma validação neles, a não ser que adicione uma função .init() que faça isso.

    Com isso, se fosse utilizar o Object.create, teria que sempre lembrar de chamar o .init antes de usar

    Criei um exemplo de criação de uma pessoa utilizando Object.create que pode ser visualizado aqui:

    https://gist.github.com/leocwolter/10874128

    Logo mais vai sair um outro post explicando um pouco dos usos do Object.create 🙂

  6. Leonardo Wolter 16/04/2014 at 10:23 #

    Valeu, Gama! 🙂

  7. Francisco 16/04/2014 at 11:03 #

    Tá bem legal, mas até onde sei, quando utilizamos funções construtoras o padrão default é utilizar PascalCase e não camelCase, não? E independente do padrão, se quisermos forçar o uso do new quando não houver podemos fazer a seguinte validação na função construtora:

    if(false === (this instanceof Pessoa)) {
    return new Pessoa();
    }

    E acho que sobrescrever prototype não é legal, isso pode dar alguns problemas lá na frente, se você quiser fazer herança, ou algo do tipo.Depende do código, mas prefiro pendurar os métodos mesmo. Existem builder patterns que ajudam neste sentido se você não gostar da sintaxe de pendurar um a um. Acho que o jshint tem uma validação contra sobrescrita de prototype…

  8. Leonardo Wolter 16/04/2014 at 11:17 #

    Olá, Francisco!

    Você tem razão, para funções construtoras utilizamos CamelCase com a primeira letra maiúscula, o que é conhecido por alguns como PascalCase.(o camel case pode ser camelCase ou CamelCase :))

    Sobre a sobrescrita de prototype, o problema é justamente sobrescrever o atributo constructor dos prototypes, você teria que lembrar de fixá-lo sempre que sobrescrever! E quanto a herança, vai sair um post mais pra frente que trata justamente desse assunto!

    Bacana essa verificação do new, mas desse modo você vai acabar perdendo o padrão e misturando Pessoa() com new Pessoa(), certo? Nos projetos em que você trabalha tem alguma forma de evitar essa perda?

  9. Bruno Viana 16/04/2014 at 12:47 #

    Excelente post, Leonardo. Um dos textos mais didaticos que ja li sobre o assunto. Alem de tudo o mais importante: extremamente conciso. Parabéns.

  10. Leonardo Wolter 16/04/2014 at 12:59 #

    Muito obrigado, Bruno!

  11. Frederico barbosa 16/04/2014 at 13:13 #

    Oi Leonardo. Objects.create tem o segundo parâmetro que é a inicialização do objeto. Não precisa do init como esta no exemplo. Mas valeu a paciência.

  12. Frederico barbosa 16/04/2014 at 13:22 #

    A minha dúvida é que na web os papas dizem para esquecer construction functions e usar object.create por suas vantagens. Podemos criar atributos somente leitura..métodos privilegiados. .Será que a preocupação com o construtor e mais importante do que todas as outras? Eu gosto da idéia. ..mas.. enfim..so vendo na prática.

  13. Leonardo Wolter 16/04/2014 at 13:49 #

    Sim sim, tem razão, mas a inicialização do objeto são somente atributos e caracteristicas destes, certo? Você pede a liberdade de estar dentro de uma função 🙂

    Mas você tem razão, não existe bala de prata, tem que medir o seu caso, ver o que se encaixa melhor e o que sua equipe prefere

  14. Francisco 16/04/2014 at 17:54 #

    Legal essa do camelCase não sabia.
    Sobre a verificação do new, o código perde consistência mesmo, até porque var `p = new Pessoa()` será igual `var p = Pessoa()`, mas por outro lado, você acaba “blindando” o código, que também não sei se é tão bom. haha. Mas é isso aí que disseram, vai de colocar na balança e pesar se traz mais vantagens ou não.

  15. Régis 17/04/2014 at 11:52 #

    Leonardo,

    Construindo objetos utilizando funções que retornam objetos literais não temos o mesmo problema do constructor paradigm, onde a cada nova instância a partir de uma função, os métodos (funções) são carregados novamente na memória?

    Parabéns pelo artigo!

  16. Leonardo Wolter 17/04/2014 at 12:06 #

    Olá, Régis.

    Você tem toda razão, nós teríamos o mesmo problema se na nossa função construtora nós adicionarmos os métodos à referencia this.

    A diferença seria a forma de se escrever:

    Uma utiliza a palavra chave new e this para referenciar os atributos e ḿétodos, a outra não precisa do new e utiliza a notação de objeto literal em vez da referencia this.

    Uma outra diferença que será abordada em outro post é o modo de se implementar herança em cada um dos casos

    Muito obrigado!

  17. Arthur 18/04/2014 at 14:59 #

    Se você fizer assim, dá para trabalhar com atributos e métodos privados:

    (function(window){

    //ATRIBUTO STATIC
    Funcionario.QUANTIDADE_DE_FUNCIONARIOS = 0;

    function Funcionario(nome){
    Funcionario.QUANTIDADE_DE_FUNCIONARIOS ++;

    //ATRIBUTOS PRIVADOS
    var _nome = nome;
    var _salario = 0;
    var _cpf = “”;
    var _endereco = “”;

    //GETTERS AND SETTERS
    this.getNome = function(){return _nome;};
    this.setNome = function(nome){_nome = nome;};

    this.getSalario = function(){return _salario;};
    this.setSalario = function(salario){_salario = salario}

    this.getCpf = function(){return _cpf;};
    this.setCpf = function(cpf){_cpf = cpf;};

    this.getEndereco = function(){return _endereco;};
    this.setEndereco = function(endereco){_endereco = endereco;};
    }

    Funcionario.prototype ={
    constructor : Funcionario,

    //MÉTODO PÚBLICOS
    recebeAumento : function(valor){
    this.salario += valor;
    },

    //MÉTODO PÚBLICOS
    mostra : function(){
    //Chamando método privado
    console.log(getDescricao(this));
    }
    }

    //MÉTODO PRIVADO
    function getDescricao(ctx){
    var desc = “Nome: “+ctx.getNome()+”\n”;
    desc += “CPF: “+ctx.getCpf()+”\n”;
    desc += “Salario: “+ctx.getSalario()+”\n”;
    desc += “Endereço: “+ctx.getEndereco();
    return desc;
    }

    window.Funcionario = Funcionario;
    }(window));

  18. Leonardo Wolter 24/04/2014 at 14:30 #

    Olá, Arthur.

    Você tem razão, você consegue ter atributos privados.

    O problema é que, para você conseguir acessá-los, é necessário criar método privilegiados, ou seja, métodos sendo atribuídos diretamente à referencia this da função construtora, perdendo a vantagem dos prototipos.

    Quanto a metodos privados, a sua abordagem de criar um método dentro de uma função wrapper me pareceu bem interessante.

    Um outro modo de criar a função sem precisar passar um ctx como argumento seria usar this dentro da função:

    function getDescricao(){
    var desc = “Nome: “+this.getNome()+”\n”;
    desc += “CPF: “+this.getCpf()+”\n”;
    desc += “Salario: “+this.getSalario()+”\n”;
    desc += “Endereço: “+this.getEndereco();
    return desc;
    }

    E na chamada dela, utilizar o metodo call para utilizar o funcionario como referencia:

    Funcionario.prototype ={
    constructor : Funcionario,

    //MÉTODO PÚBLICOS
    mostra : function(){
    //Chamando método privado
    console.log(getDescricao.call(this));
    }
    }

    Abraço!

  19. Arthur 24/04/2014 at 15:45 #

    Ihhh… essa eu nao sabia! Boa!

    getDescricao.call(this)

  20. Arthur 24/04/2014 at 15:51 #

    Getters & Setters no construtor não é lá um problema. É muito, mas muuuito melhor do que não utilizar, não acha?

  21. Leonardo Wolter 24/04/2014 at 17:03 #

    Isso varia de caso a caso, Arthur.

    Imagina que você tem uma função construtora com 30 getters & setters e é instanciada 1000 vezes no sistema,
    você teria 60000 funções na memoria.

    Se você não puder usar e abusar da memória e estiver muuito preocupado com performance, talvez você tenha que abrir mão dessas boas praticas de OO por conta dessa limitação do javascript.

    Ja se você não estiver nem um pouco preocupado com essas coisas, você pode utilizar a segunda abordagem mencionada no post, é realmente algo a se medir caso a caso.

  22. Arthur 28/04/2014 at 14:54 #

    hehehe… nao testei, mas acho que nesse caso não vai valer a pena.

  23. Weslley Henrique 06/05/2014 at 02:18 #

    Show de bola! Parabéns!

    Valeu a dica!

  24. Marcelo Guimarães 15/05/2014 at 19:05 #

    Ótimo post Leonardo! Parabéns

  25. Leonardo Wolter 16/05/2014 at 00:52 #

    Muito obrigado Weslley e Marcelo!

  26. Andre 26/05/2014 at 08:50 #

    obrigado, show de bola

  27. Marden 14/06/2014 at 00:13 #

    Tem como disponibilizar esses materiais

  28. Leonardo Wolter 14/06/2014 at 02:20 #

    Obrigado Andre!

    Desculpa, Marden, que materiais?

  29. Júnior 19/06/2014 at 17:13 #

    Para criar atributos e métodos privados baste definir o prototype dentro da função construtora.
    function Pessoa() {
    var nome = null;

    this.constructor.prototype.setNome = function(_nome) {
    nome = _nome;
    }
    }
    Correto?

  30. Júnior 20/06/2014 at 01:57 #

    Corrigindo o que eu disse. var nome não é um atributo dos objetos instanciado de Pessoa, muito menos um atributo privado. O exemplo acima foi muito infeliz é melhor excluir. Sempre que for instanciado um novo objeto a função construtora é executada e var nome recebe null e isso afeta todos os objetos ou melhor não existe relação alguma de var nome com os objetos instanciados. Por isso var nome não é atributo do objeto muito menos atributo privado. var nome é apenas uma variável local da função.

  31. Leonardo Wolter 20/06/2014 at 10:28 #

    Olá, Júnior

    Isso funciona sim, mas desse modo você está redefinindo a função setNome no prototype da Pessoa a cada instanciação dela(uma vez que o código da função construtora será executado várias vezes), certo? Nesse caso talvez seja até pior do que colocar funções no this.

    Além disso, como você disse, a variável nome só está sendo disponibilizada pois ela se encontra dentro da closure de setNome, fazendo com que se você instanciar outra pessoa a variável nome seja limpa! Mas isso só acontece pois a função está no prototype, que se encontra fora do escopo do objeto. Caso você coloque o setNome no this deve funcionar sem problemas.

    Abraço!

  32. Júnior 21/06/2014 at 16:13 #

    É isso mesmo. Obrigado Leonardo. Então para cada atributo privado uso métodos get e set e continuo definindo minhas funções no prototype?

    		function Pessoa() {
    			var nome = null;
    
    			this.setNome = function(_nome) {
    				nome = _nome;
    			}
    			
    			this.getNome = function() {
    				return nome;
    			}
    		}
    		Pessoa.prototype.mostrar = function() {
    			return 'Nome: ' + this.getNome();
    		}
    
  33. Júnior 21/06/2014 at 19:49 #

    Essa forma de organizar o código é boa ou estou complicando o código? Existe alguma forma de remover os métodos gets e sets e continuar com os atributos privados e acessível pelo prototype?

    		var Pessoa = (function() {
    			var count = 0; // variável estática
    			
    			// métodos privado
    			var inc_count = function() {
    				count++;
    				this.setNum(count);
    			}
    			
    			function Pessoa(nome) {
    				// variáveis privadas
    				var nome = null,
    					num = null;
    				
    				// apenas métodos gets e sets
    				this.setNum = function(val) {
    					num = val;
    				}
    				
    				this.getNum = function() {
    					return num;
    				}
    				
    				this.setNome = function(val) {
    					nome = val;
    				}
    				
    				this.getNome = function() {
    					return nome;
    				}
    				
    				inc_count.call(this);
    			}
    			
    			// métodos público
    			Pessoa.prototype = {
    				constructor: Pessoa,
    				
    				getCount: function() {
    					return count;
    				}
    			};
    
    			return Pessoa;
    		})();
    
  34. Leonardo Wolter 24/06/2014 at 10:40 #

    Oi Júnior,

    Se você fizer questão de ter atributos privados e métodos que os acessem,vai ter que criar métodos privilegiadas no this sim, não conheço outro jeito que funcione.
    Uma outra estratégia que costumam usar é deixar um _(underline) no atributo para indicar que é privado, ou seja, que não deve ser acessado diretamente e esperar que respeitem.

    Você pode encontrar mais informações sobre como implementar atributos privados nesse post do Douglas Crockford

    Abraço!

  35. Fabiano Alves 18/03/2016 at 17:56 #

    Uma duvida, Me ajudem por favor…

    Eu tenho o seguinte código:

    var exemplo = {
    “objetos”: [
    {
    “nome”: “Restaurante”,
    “titulo” : “Corporate Plaza”
    },
    {
    “nome”: “Estacionamento”,
    “titulo” : “Tranquilidade”

    }
    ]
    };

    Como adicionar um novo objeto?

  36. tiago marques 20/07/2016 at 19:57 #

    ajudou bastante, muito obrigado!!!

Deixe uma resposta