Protótipos são uma otimização .
Um ótimo exemplo de como usá-los bem é a biblioteca jQuery. Cada vez que você obtém um objeto jQuery usando $('.someClass')
, esse objeto tem dezenas de "métodos". A biblioteca pode conseguir isso retornando um objeto:
return {
show: function() { ... },
hide: function() { ... },
css: function() { ... },
animate: function() { ... },
// etc...
};
Mas isso significaria que cada objeto jQuery na memória teria dezenas de slots nomeados contendo os mesmos métodos, continuamente.
Em vez disso, esses métodos são definidos em um protótipo e todos os objetos jQuery "herdam" esse protótipo para obter todos esses métodos com um custo de tempo de execução muito pequeno.
Uma parte vitalmente importante de como o jQuery acerta é que isso é escondido do programador. É tratado puramente como uma otimização, não como algo com que você precisa se preocupar ao usar a biblioteca.
O problema com o JavaScript é que as funções do construtor simples exigem que o chamador se lembre de prefixá-las com new
ou de outra forma normalmente não funcionam. Não há um bom motivo para isso. O jQuery acerta ao esconder esse absurdo atrás de uma função comum $
, então você não precisa se preocupar com a forma como os objetos são implementados.
Para que você possa criar convenientemente um objeto com um protótipo especificado, o ECMAScript 5 inclui uma função padrão Object.create
. Uma versão bastante simplificada ficaria assim:
Object.create = function(prototype) {
var Type = function () {};
Type.prototype = prototype;
return new Type();
};
Ele apenas cuida da dor de escrever uma função de construtor e, em seguida, chamá-la com new
.
Quando você evitaria protótipos?
Uma comparação útil é com linguagens OO populares, como Java e C #. Eles suportam dois tipos de herança:
- interface de herança, onde
implement
uma interface
tal forma que a classe fornece sua própria implementação exclusivo para cada membro da interface.
- implementação de herança, onde
extend
um class
que fornece implementações padrão de alguns métodos.
Em JavaScript, a herança prototípica é um tipo de herança de implementação . Portanto, nas situações em que (em C # ou Java) você teria derivado de uma classe base para obter o comportamento padrão, no qual você faria pequenas modificações por meio de substituições, em JavaScript, a herança prototípica faz sentido.
No entanto, se você estiver em uma situação em que usaria interfaces em C # ou Java, não precisará de nenhum recurso de linguagem específico em JavaScript. Não há necessidade de declarar explicitamente algo que representa a interface e não há necessidade de marcar objetos como "implementando" essa interface:
var duck = {
quack: function() { ... }
};
duck.quack(); // we're satisfied it's a duck!
Em outras palavras, se cada "tipo" de objeto tem suas próprias definições de "métodos", então não há valor em herdar de um protótipo. Depois disso, depende de quantas instâncias você aloca de cada tipo. Mas em muitos projetos modulares, existe apenas uma instância de um determinado tipo.
E, de fato, tem sido sugerido por muitas pessoas que a herança de implementação é má . Ou seja, se houver algumas operações comuns para um tipo, então talvez seja mais claro se elas não forem colocadas em uma classe base / super, mas apenas expostas como funções comuns em algum módulo, para o qual você passa o (s) objeto (s) você quer que eles operem.