O uso de 'protótipo' vs. 'this' em JavaScript?

776

Qual é a diferença entre

var A = function () {
    this.x = function () {
        //do something
    };
};

e

var A = function () { };
A.prototype.x = function () {
    //do something
};
sw234
fonte
conceito desta palavra-chave é explicada explicitamente aqui scotch.io/@alZami/understanding-this-in-javascript
AL-Zami
1
A leitura "deste" tópico mostra o quão horrível é JS e o quanto seus princípios não são claros para muitos desenvolvedores. O que é exatamente errado com idiomas mais fáceis de entender? Acho que é hora de os desenvolvedores expressarem sua voz para rejeitar tecnologias confusas que servem pouco ou nenhum valor aos negócios ou ao trabalho de desenvolvimento.
NoChance
No objeto a1.x !== a2.x:; no protótipo:a1.x === a2.x
Juan Mendes

Respostas:

467

Os exemplos têm resultados muito diferentes.

Antes de analisar as diferenças, deve-se observar o seguinte:

  • O protótipo de um construtor fornece uma maneira de compartilhar métodos e valores entre instâncias por meio da [[Prototype]]propriedade privada da instância .
  • Uma função é este é definido pela forma como a função é chamada, ou pelo uso de ligamento (não discutido aqui). Onde uma função é chamada em um objeto (por exemplo myObj.method()), isso dentro do método faz referência ao objeto. Quando isso não é definido pela chamada ou pelo uso de ligação , o padrão é o objeto global (janela em um navegador) ou no modo estrito, permanece indefinido.
  • JavaScript é uma linguagem orientada a objetos, ou seja, a maioria dos valores são objetos, incluindo funções. (Strings, números e booleanos não são objetos.)

Então, aqui estão os trechos em questão:

var A = function () {
    this.x = function () {
        //do something
    };
};

Nesse caso, Aé atribuído à variável um valor que é uma referência a uma função. Quando essa função é chamada usando A(), a função é esta não é definida pela chamada para o padrão é o objeto global e a expressão this.xé eficaz window.x. O resultado é que é atribuída uma referência à expressão da função no lado direito window.x.

No caso de:

var A = function () { };
A.prototype.x = function () {
    //do something
};

algo muito diferente ocorre. Na primeira linha, a variável Aé atribuída uma referência a uma função. Em JavaScript, todos os objetos de funções têm uma propriedade prototype por padrão, portanto, não há código separado para criar um objeto A.prototype .

Na segunda linha, A.prototype.x recebe uma referência a uma função. Isso criará uma propriedade x , se não existir, ou atribuirá um novo valor, se existir. Portanto, a diferença com o primeiro exemplo em que a propriedade x do objeto está envolvida na expressão.

Outro exemplo está abaixo. É semelhante ao primeiro (e talvez o que você queria perguntar):

var A = new function () {
    this.x = function () {
        //do something
    };
};

Neste exemplo, o newoperador foi adicionado antes da expressão da função para que a função seja chamada como construtor. Quando chamada com new, a função this é configurada para referenciar um novo Object cuja [[Prototype]]propriedade privada é configurada para referenciar o protótipo público do construtor . Portanto, na declaração de atribuição, a xpropriedade será criada nesse novo objeto. Quando chamada como construtora, uma função retorna esse objeto por padrão, portanto, não há necessidade de uma return this;instrução separada .

Para verificar se A possui uma propriedade x :

console.log(A.x) // function () {
                 //   //do something
                 // };

Esse é um uso incomum do novo, pois a única maneira de referenciar o construtor é através do A.constructor . Seria muito mais comum fazer:

var A = function () {
    this.x = function () {
        //do something
    };
};
var a = new A();

Outra maneira de obter um resultado semelhante é usar uma expressão de função chamada imediatamente:

var A = (function () {
    this.x = function () {
        //do something
    };
}());

Nesse caso, Aatribua o valor de retorno da chamada da função no lado direito. Aqui, novamente, como isso não está definido na chamada, ele fará referência ao objeto global e this.xé eficaz window.x. Como a função não retorna nada, Aterá um valor de undefined.

Essas diferenças entre as duas abordagens também se manifestam se você estiver serializando e desserializando seus objetos Javascript de / para JSON. Os métodos definidos no protótipo de um objeto não são serializados quando você o serializa, o que pode ser conveniente quando, por exemplo, você deseja serializar apenas as partes de dados de um objeto, mas não os métodos:

var A = function () { 
    this.objectsOwnProperties = "are serialized";
};
A.prototype.prototypeProperties = "are NOT serialized";
var instance = new A();
console.log(instance.prototypeProperties); // "are NOT serialized"
console.log(JSON.stringify(instance)); 
// {"objectsOwnProperties":"are serialized"} 

Questões relacionadas :

Nota: Pode não haver economia significativa de memória entre as duas abordagens, no entanto, o uso do protótipo para compartilhar métodos e propriedades provavelmente usará menos memória do que cada instância com sua própria cópia.

JavaScript não é uma linguagem de baixo nível. Pode não ser muito valioso pensar em protótipos ou outros padrões de herança como uma maneira de alterar explicitamente a maneira como a memória é alocada.

keparo
fonte
49
@keparo: Você está errado. Todo objeto tem um objeto de protótipo [interno] (que pode ser null), mas isso é muito diferente da prototypepropriedade - que está nas funções e na qual o protótipo de todas as instâncias é definido quando elas são construídas new. Não posso acreditar que isso realmente tenha 87
votos positivos
8
"The language is functional"Tem certeza de que é isso que significa funcional?
Phant0m
23
Segundo o que a @Bergi disse sobre protótipos. Funções têm uma propriedade prototype. Todos os objetos, incluindo funções, têm outra propriedade interna que pode ser acessada com Object.getPrototypeOf (myObject) ou com myObject .__ proto__ em alguns navegadores. A propriedade proto indica o pai do objeto na cadeia de protótipos (ou o objeto do qual esse objeto é herdado). A propriedade prototype (que é apenas em funções) indicava o objeto que se tornará o pai de qualquer objeto que utilize a função para criar novos objetos usando a nova palavra-chave.
Jim Cooper
11
Este artigo é bastante equivocado e confunde como isso está definido. Trabalhando em uma reescrita.
RobG
37
Essa resposta é bastante bizarra e parece perder completamente o objetivo da pergunta. A pergunta parece ser muito comum sobre a definição de propriedades de tipo no construtor versus o protoype, mas metade da resposta é sobre o que aconteceria se você usasse Acomo função, e a outra metade é sobre maneiras obscuras e não-ortodoxas de fazer algo simples.
JLRishe
235

Como outros já disseram na primeira versão, usar "this" resulta em todas as instâncias da classe A com sua própria cópia independente do método da função "x". Enquanto o uso de "protótipo" significa que cada instância da classe A usará a mesma cópia do método "x".

Aqui está um código para mostrar essa diferença sutil:

// x is a method assigned to the object using "this"
var A = function () {
    this.x = function () { alert('A'); };
};
A.prototype.updateX = function( value ) {
    this.x = function() { alert( value ); }
};

var a1 = new A();
var a2 = new A();
a1.x();  // Displays 'A'
a2.x();  // Also displays 'A'
a1.updateX('Z');
a1.x();  // Displays 'Z'
a2.x();  // Still displays 'A'

// Here x is a method assigned to the object using "prototype"
var B = function () { };
B.prototype.x = function () { alert('B'); };

B.prototype.updateX = function( value ) {
    B.prototype.x = function() { alert( value ); }
}

var b1 = new B();
var b2 = new B();
b1.x();  // Displays 'B'
b2.x();  // Also displays 'B'
b1.updateX('Y');
b1.x();  // Displays 'Y'
b2.x();  // Also displays 'Y' because by using prototype we have changed it for all instances

Como outros já mencionaram, existem várias razões para escolher um método ou outro. Minha amostra é apenas para demonstrar claramente a diferença.

Benry
fonte
5
Isso é o que eu esperava que acontecesse, mas quando instanciei um novo objeto depois de alterar Ax como acima, ainda exibo 'A' a menos que eu use A como um singleton. jsbin.com/omida4/2/edit
jellyfishtree
19
Isso porque meu exemplo estava errado. Só está errado há dois anos. Suspiro. Mas o ponto ainda é válido. Atualizei o exemplo com um que realmente funciona. Obrigado por apontar isso.
Benry 23/10/10
4
É um método estático! : D
6
sim ... 'prototype' significa nível estático ou de classe .. que será compartilhado por todas as instâncias criadas ... enquanto 'this' é um método de instância em que cada instância terá sua própria cópia
Aneer Dev
7
Não é estático. Static, como usado na maioria das linguagens OO, implica que não há dependência do thisobjeto, que é o proprietário do método. isto é, o método não possui nenhum objeto que seja seu proprietário. Nesse caso, há um thisobjeto, conforme mostrado na classe A no exemplo.
CJStuart
152

Veja estes 2 exemplos:

var A = function() { this.hey = function() { alert('from A') } };

vs.

var A = function() {}
A.prototype.hey = function() { alert('from prototype') };

A maioria das pessoas aqui (especialmente as respostas mais bem avaliadas) tentou explicar como elas são diferentes sem explicar POR QUE. Eu acho que isso está errado e se você entender os fundamentos primeiro, a diferença se tornará óbvia. Vamos tentar explicar os fundamentos primeiro ...

a) Uma função é um objeto em JavaScript. CADA objeto no JavaScript obtém uma propriedade interna (o que significa que você não pode acessá-lo como outras propriedades, exceto talvez em navegadores como o Chrome), geralmente chamado de __proto__(você pode realmente digitar o anyObject.__proto__Chrome para ver o que ele faz referência. , uma propriedade, nada mais.Uma propriedade em JavaScript = uma variável dentro de um objeto, nada mais.O que as variáveis ​​fazem? Elas apontam para coisas.

Então, o que essa __proto__propriedade aponta? Bem, geralmente outro objeto (explicaremos o porquê mais tarde). A única maneira de forçar o JavaScript para que a __proto__propriedade NÃO aponte para outro objeto é usar var newObj = Object.create(null). Mesmo se você fizer isso, a __proto__propriedade AINDA existe como uma propriedade do objeto, mas não aponta para outro objeto, aponta para null.

Aqui é onde a maioria das pessoas se confunde:

Quando você cria uma nova função no JavaScript (que também é um objeto, lembra?), No momento em que é definida, o JavaScript cria automaticamente uma nova propriedade nessa função chamada prototype. Tente:

var A = [];
A.prototype // undefined
A = function() {}
A.prototype // {} // got created when function() {} was defined

A.prototypeé TOTALMENTE DIFERENTE da __proto__propriedade. No nosso exemplo, 'A' agora tem DUAS propriedades chamadas 'protótipo' e __proto__. Esta é uma grande confusão para as pessoas. prototypee as __proto__propriedades não têm relação alguma, são coisas separadas apontando para valores separados.

Você pode se perguntar: Por que o JavaScript possui __proto__propriedades criadas em todos os objetos? Bem, uma palavra: delegação . Quando você chama uma propriedade em um objeto e o objeto não a possui, o JavaScript procura o objeto referenciado por __proto__para ver se ele talvez o possua. Se não o possuir, ele examinará a __proto__propriedade desse objeto e assim por diante ... até a cadeia terminar. Assim, o nome da cadeia de protótipos . Obviamente, se __proto__não apontar para um objeto e, em vez disso, apontar para null, com muita sorte, o JavaScript perceberá isso e o retornará undefinedpara a propriedade.

Você também pode se perguntar: por que o JavaScript cria uma propriedade chamada prototypepara uma função quando você a define? Porque ele tenta enganar você, sim engane você que funciona como linguagens baseadas em classes.

Vamos continuar com o nosso exemplo e criar um "objeto" de A:

var a1 = new A();

Há algo acontecendo em segundo plano quando isso aconteceu. a1é uma variável comum à qual foi atribuído um novo objeto vazio.

O fato de você ter usado o operador newantes de uma chamada de função A()fez algo ADICIONAL em segundo plano. A newpalavra-chave criou um novo objeto que agora faz referência a1e esse objeto está vazio. Aqui está o que está acontecendo adicionalmente:

Dissemos que em cada definição de função há uma nova propriedade criada chamada prototype(que você pode acessá-la, diferente da __proto__propriedade) criada? Bem, essa propriedade está sendo usada agora.

Então, agora estamos no ponto em que temos um a1objeto vazio recém-assado . Dissemos que todos os objetos em JavaScript têm uma __proto__propriedade interna que aponta para algo ( a1também possui), seja nulo ou outro objeto. O que o newoperador faz é que ele defina essa __proto__propriedade para apontar para a prototypepropriedade da função . Leia isso de novo. É basicamente isso:

a1.__proto__ = A.prototype;

Dissemos que A.prototypenada mais é do que um objeto vazio (a menos que o alteremos para outra coisa antes de definir a1). Então agora basicamente a1.__proto__aponta para a mesma coisa A.prototype, que é esse objeto vazio. Ambos apontam para o mesmo objeto que foi criado quando essa linha aconteceu:

A = function() {} // JS: cool. let's also create A.prototype pointing to empty {}

Agora, há outra coisa acontecendo quando a var a1 = new A()instrução é processada. Basicamente, A()é executado e se A é algo como isto:

var A = function() { this.hey = function() { alert('from A') } };

Todas essas coisas lá dentro function() { }serão executadas. Quando você atinge a this.hey..linha, thisé alterado para a1e você obtém o seguinte:

a1.hey = function() { alert('from A') }

Não falarei sobre o porquê de thisalterações, a1mas esta é uma ótima resposta para saber mais.

Então, para resumir, quando você faz, var a1 = new A()há três coisas acontecendo em segundo plano:

  1. Um objeto vazio totalmente novo é criado e atribuído a a1.a1 = {}
  2. a1.__proto__A propriedade é atribuída para apontar para a mesma coisa que A.prototypeaponta para (outro objeto vazio {})

  3. A função A()está sendo executada com thisdefinido para o novo objeto vazio criado na etapa 1 (leia a resposta que eu referenciei acima sobre o motivo de thismudar para a1)

Agora, vamos tentar criar outro objeto:

var a2 = new A();

Os passos 1,2,3 serão repetidos. Você percebe alguma coisa? A palavra-chave é repetir. Etapa 1: a2será um novo objeto vazio, etapa 2: sua __proto__propriedade apontará para a mesma coisa A.prototypee, mais importante, etapa 3: a função A()é AGAIN executada, o que significa que a2obterá a heypropriedade que contém uma função. a1e a2tem duas propriedades SEPARATE nomeadas heyque apontam para 2 funções SEPARATE! Agora temos funções duplicadas nos mesmos dois objetos diferentes fazendo a mesma coisa, oops ... Você pode imaginar as implicações de memória disso se tivermos 1000 objetos criados com new A, depois de todas as declarações de funções ocuparem mais memória do que algo como o número 2. Então como podemos evitar isso?

Lembra por que a __proto__propriedade existe em todos os objetos? Para que, se você recuperar a yoManpropriedade em a1(que não existe), sua __proto__propriedade será consultada; se for um objeto (e na maioria dos casos, for), verificará se ela contém yoMane, se não, ele consultará o objeto __proto__etc. etc. Se o fizer, ele pegará esse valor da propriedade e exibirá para você.

Então, alguém decidiu usar esse fato + o fato de que, quando você cria a1, sua __proto__propriedade aponta para o mesmo objeto (vazio) A.prototypepara:

var A = function() {}
A.prototype.hey = function() { alert('from prototype') };

Legal! Agora, quando você cria a1, ele passa novamente por todas as 3 etapas acima e, na etapa 3, não faz nada, pois function A()não tem nada para executar. E se fizermos:

a1.hey

Ele verá que a1não contém heye verificará seu __proto__objeto de propriedade para ver se o possui, qual é o caso.

Com essa abordagem, eliminamos a parte da etapa 3, na qual as funções são duplicadas em cada nova criação de objeto. Em vez de a1e a2ter uma heypropriedade separada , agora NENHUM deles a possui. Acho que você já se descobriu agora. Essa é a coisa legal ... se você entender __proto__e Function.prototype, perguntas como essas serão bem óbvias.

NOTA: Algumas pessoas tendem a não chamar a propriedade Prototype interna __proto__, pois usei esse nome na postagem para distingui-lo claramente da Functional.prototypepropriedade como duas coisas diferentes.

daremkd
fonte
1
Resposta realmente completa e informativa. Fiz alguns testes de memória usando as estruturas de objetos acima (A.prototype.hey vs object this.hey) e criei 1000 instâncias de cada. A pegada de memória para a abordagem de propriedade do objeto era cerca de 100kb maior em comparação com o protótipo. Eu então adicionei outra função com o mesmo objetivo chamado "bobo" e ela aumentou linearmente para 200kb. Não é significativo, mas também não é amendoim.
Jokyone #
O mais interessante é que o método prototype foi marginalmente mais lento que o método de propriedade do objeto em execução localmente. No geral, não tenho certeza de que o javascript deva ser usado para manipulação de dados de objetos com números acima de 10k, portanto, negando qualquer motivo para alterar abordagens com base em possíveis efeitos de memória. Nesse ponto, o trabalho deve ser descarregado em um servidor.
Jookyone 3/08
O ponto é __proto__e .prototypesão coisas totalmente diferentes.
Wayou 19/12/19
1
Eu não me sinto satisfeito em simplesmente dar-lhe um upvote ... Bem feito!
Kristianmitk
58

Na maioria dos casos, eles são essencialmente os mesmos, mas a segunda versão economiza memória porque há apenas uma instância da função em vez de uma função separada para cada objeto.

Uma razão para usar o primeiro formulário é acessar "membros privados". Por exemplo:

var A = function () {
    var private_var = ...;

    this.x = function () {
        return private_var;
    };

    this.setX = function (new_x) {
        private_var = new_x;
    };
};

Devido às regras de escopo do javascript, private_var está disponível para a função atribuída a this.x, mas não fora do objeto.

Matthew Crumley
fonte
1
Veja esta postagem: stackoverflow.com/a/1441692/654708 para obter um exemplo de como acessar membros privados por meio de protótipos.
GFoley83
@ GFoley83 que responde não mostra que - os métodos de protótipo podem acessar apenas as propriedades "públicas" do objeto especificado. Somente os métodos privilegiados (que não estão no protótipo) podem acessar os membros privados.
Alnitak
27

O primeiro exemplo altera a interface apenas para esse objeto. O segundo exemplo altera a interface para todos os objetos dessa classe.

Glenn
fonte
Ambos irão fazer a função xdisponível para todos os objetos cujo protótipo é atribuída uma nova instância A:function B () {}; B.prototype = new A(); var b = new B(); b.x() // Will call A.x if A is defined by first example;
Spencer Williams
21

O maior problema com o uso em thisvez de prototypeé que, ao substituir um método, o construtor da classe base ainda fará referência ao método substituído. Considere isto:

BaseClass = function() {
    var text = null;

    this.setText = function(value) {
        text = value + " BaseClass!";
    };

    this.getText = function() {
        return text;
    };

    this.setText("Hello"); // This always calls BaseClass.setText()
};

SubClass = function() {
    // setText is not overridden yet,
    // so the constructor calls the superclass' method
    BaseClass.call(this);

    // Keeping a reference to the superclass' method
    var super_setText = this.setText;
    // Overriding
    this.setText = function(value) {
        super_setText.call(this, "SubClass says: " + value);
    };
};
SubClass.prototype = new BaseClass();

var subClass = new SubClass();
console.log(subClass.getText()); // Hello BaseClass!

subClass.setText("Hello"); // setText is already overridden
console.log(subClass.getText()); // SubClass says: Hello BaseClass!

versus:

BaseClass = function() {
    this.setText("Hello"); // This calls the overridden method
};

BaseClass.prototype.setText = function(value) {
    this.text = value + " BaseClass!";
};

BaseClass.prototype.getText = function() {
    return this.text;
};

SubClass = function() {
    // setText is already overridden, so this works as expected
    BaseClass.call(this);
};
SubClass.prototype = new BaseClass();

SubClass.prototype.setText = function(value) {
    BaseClass.prototype.setText.call(this, "SubClass says: " + value);
};

var subClass = new SubClass();
console.log(subClass.getText()); // SubClass says: Hello BaseClass!

Se você acha que isso não é um problema, depende se você pode viver sem variáveis ​​privadas e se possui experiência suficiente para conhecer um vazamento ao vê-lo. Além disso, ter que colocar a lógica do construtor após as definições do método é inconveniente.

var A = function (param1) {
    var privateVar = null; // Private variable

    // Calling this.setPrivateVar(param1) here would be an error

    this.setPrivateVar = function (value) {
        privateVar = value;
        console.log("setPrivateVar value set to: " + value);

        // param1 is still here, possible memory leak
        console.log("setPrivateVar has param1: " + param1);
    };

    // The constructor logic starts here possibly after
    // many lines of code that define methods

    this.setPrivateVar(param1); // This is valid
};

var a = new A(0);
// setPrivateVar value set to: 0
// setPrivateVar has param1: 0

a.setPrivateVar(1);
//setPrivateVar value set to: 1
//setPrivateVar has param1: 0

versus:

var A = function (param1) {
    this.setPublicVar(param1); // This is valid
};
A.prototype.setPublicVar = function (value) {
    this.publicVar = value; // No private variable
};

var a = new A(0);
a.setPublicVar(1);
console.log(a.publicVar); // 1
tarkabak
fonte
20

Todo objeto está vinculado a um objeto de protótipo. Ao tentar acessar uma propriedade que não existe, o JavaScript procurará no objeto de protótipo do objeto e a retornará, se existir.

A prototypepropriedade de um construtor de função refere-se ao objeto de protótipo de todas as instâncias criadas com essa função ao usar new.


No seu primeiro exemplo, você está adicionando uma propriedade xa cada instância criada com a Afunção

var A = function () {
    this.x = function () {
        //do something
    };
};

var a = new A();    // constructor function gets executed
                    // newly created object gets an 'x' property
                    // which is a function
a.x();              // and can be called like this

No segundo exemplo, você está adicionando uma propriedade ao objeto de protótipo para o qual todas as instâncias criadas Aapontam.

var A = function () { };
A.prototype.x = function () {
    //do something
};

var a = new A();    // constructor function gets executed
                    // which does nothing in this example

a.x();              // you are trying to access the 'x' property of an instance of 'A'
                    // which does not exist
                    // so JavaScript looks for that property in the prototype object
                    // that was defined using the 'prototype' property of the constructor

Em conclusão, no primeiro exemplo, uma cópia da função é atribuída a cada instância . No segundo exemplo, uma única cópia da função é compartilhada por todas as instâncias .

pishpish
fonte
1
Votou como sendo a resposta mais direta à pergunta.
22416 Nick Pineda
1
Eu gostei da sua abordagem direta !! polegares para cima!
Prince Vijay Pratap 26/03
16

Qual é a diferença? => Muito.

Eu acho que a thisversão é usada para ativar o encapsulamento, ou seja, ocultar dados. Ajuda a manipular variáveis ​​privadas.

Vejamos o seguinte exemplo:

var AdultPerson = function() {

  var age;

  this.setAge = function(val) {
    // some housekeeping
    age = val >= 18 && val;
  };

  this.getAge = function() {
    return age;
  };

  this.isValid = function() {
    return !!age;
  };
};

Agora, a prototypeestrutura pode ser aplicada da seguinte maneira:

Adultos diferentes têm idades diferentes, mas todos têm os mesmos direitos.
Então, nós o adicionamos usando protótipo, e não isso.

AdultPerson.prototype.getRights = function() {
  // Should be valid
  return this.isValid() && ['Booze', 'Drive'];
};

Vamos analisar a implementação agora.

var p1 = new AdultPerson;
p1.setAge(12); // ( age = false )
console.log(p1.getRights()); // false ( Kid alert! )
p1.setAge(19); // ( age = 19 )
console.log(p1.getRights()); // ['Booze', 'Drive'] ( Welcome AdultPerson )

var p2 = new AdultPerson;
p2.setAge(45);    
console.log(p2.getRights()); // The same getRights() method, *** not a new copy of it ***

Espero que isto ajude.

oozzal
fonte
3
+1 Uma resposta muito menos complicada e mais gráfica do que as outras. Mas você deve elaborar um pouco mais antes de fornecer esses (bons) exemplos.
yerforkferchips
1
Não tenho certeza sobre "esta versão é usada para ativar o encapsulamento, ou seja, ocultar dados". Se uma propriedade dentro de uma função for definida usando "this" como em "this.myProperty = ...", essa propriedade não será "privada" e poderá ser acessada a partir de objetos fora da classe usando "new".
precisa saber é o seguinte
14

Protótipo é o modelo da classe; que se aplica a todas as instâncias futuras dele. Considerando que esta é a instância específica do objeto.

harropriiz
fonte
14

Eu sei que isso foi respondido até a morte, mas eu gostaria de mostrar um exemplo real de diferenças de velocidade.

Função diretamente no objeto

Função no protótipo

Aqui, estamos criando 2.000.000 de novos objetos com um printmétodo no Chrome. Estamos armazenando todos os objetos em uma matriz. A colocação printdo protótipo leva cerca de 1/2 do tempo.

Arnav Aggarwal
fonte
13

Deixe-me dar uma resposta mais abrangente que aprendi durante um curso de treinamento em JavaScript.

A maioria das respostas já mencionou a diferença, ou seja, ao prototipar a função é compartilhada com todas as instâncias (futuras). Considerando que declarar a função na classe criará uma cópia para cada instância.

Em geral, não há certo ou errado, é mais uma questão de gosto ou uma decisão de design, dependendo de suas necessidades. O protótipo, no entanto, é a técnica usada para desenvolver de maneira orientada a objetos, como espero que você veja no final desta resposta.

Você mostrou dois padrões em sua pergunta. Vou tentar explicar mais duas e tentarei explicar as diferenças, se relevante. Sinta-se livre para editar / estender. Em todos os exemplos, trata-se de um objeto de carro que possui um local e pode se mover.

Padrão Decorador de Objetos

Não tenho certeza se esse padrão ainda é relevante hoje em dia, mas existe. E é bom saber sobre isso. Você simplesmente passa um objeto e uma propriedade para a função decoradora. O decorador retorna o objeto com propriedade e método.

var carlike = function(obj, loc) {
    obj.loc = loc;
    obj.move = function() {
        obj.loc++;
    };
    return obj;
};

var amy = carlike({}, 1);
amy.move();
var ben = carlike({}, 9);
ben.move();

Classes funcionais

Uma função em JavaScript é um objeto especializado. Além de ser invocada, uma função pode armazenar propriedades como qualquer outro objeto.

Nesse caso, Caré uma função ( também pense em objeto ) que pode ser invocada como você costuma fazer. Possui uma propriedade methods(que é um objeto com uma movefunção). Quando Caré chamada, a extendfunção é chamada, o que faz alguma mágica e estende a Carfunção (objeto de reflexão) com os métodos definidos dentro methods.

Este exemplo, embora diferente, se aproxima do primeiro exemplo da pergunta.

var Car = function(loc) {
    var obj = {loc: loc};
    extend(obj, Car.methods);
    return obj;
};

Car.methods = {
    move : function() {
        this.loc++;
    }
};

var amy = Car(1);
amy.move();
var ben = Car(9);
ben.move();

Classes prototípicas

Os dois primeiros padrões permitem uma discussão sobre o uso de técnicas para definir métodos compartilhados ou o uso de métodos definidos em linha no corpo do construtor. Nos dois casos, cada instância tem sua própria movefunção.

O padrão prototípico não se presta bem ao mesmo exame, porque o compartilhamento de funções por meio de uma delegação de protótipo é o próprio objetivo do padrão prototípico. Como outros apontaram, espera-se que tenha uma melhor pegada de memória.

No entanto, há um ponto interessante a saber: todo prototypeobjeto tem uma propriedade de conveniência constructor, que aponta para a função (objeto de reflexão) à qual ele foi anexado.

Em relação às três últimas linhas:

Neste exemplo, Carvincula-se ao prototypeobjeto, que vincula-se constructora Carele próprio, ou seja, Car.prototype.constructoréCar ele próprio. Isso permite que você descubra qual função do construtor construiu um determinado objeto.

amy.constructorA pesquisa de falha e, portanto, é delegada para Car.prototype, que possui a propriedade construtora. E assim amy.constructoré Car.

Além disso, amyé um instanceof Car. O instanceofoperador trabalha verificando se o objeto protótipo do operando direito ( Car) pode ser encontrado em qualquer lugar da amycadeia protótipo ( ) do operando esquerdo .

var Car = function(loc) {
    var obj = Object.create(Car.prototype);
    obj.loc = loc;
    return obj;
};

Car.prototype.move = function() {
        this.loc++;
};

var amy = Car(1);
amy.move();
var ben = Car(9);
ben.move();

console.log(Car.prototype.constructor);
console.log(amy.constructor);
console.log(amy instanceof Car);

Alguns desenvolvedores podem ficar confusos no começo. Veja o exemplo abaixo:

var Dog = function() {
  return {legs: 4, bark: alert};
};

var fido = Dog();
console.log(fido instanceof Dog);

O instanceofoperador retorna false, porque Dogo protótipo não pode ser encontrado em nenhum lugar da fidocadeia de protótipos. fidoé um objeto simples criado com um literal de objeto, ou seja, apenas delega paraObject.prototype .

Padrões pseudoclássicos

Essa é realmente apenas outra forma do padrão prototípico de forma simplificada e mais familiar para quem programa em Java, por exemplo, uma vez que usa o newconstrutor.

Ele realmente faz o mesmo que no padrão prototípico, é apenas uma sobreposição sintática de açúcar do padrão prototípico.

No entanto, a principal diferença é que há otimizações implementadas nos mecanismos JavaScript que só se aplicam ao usar o padrão pseudoclássico. Pense no padrão pseudoclássico uma versão provavelmente mais rápida do padrão prototípico; as relações de objeto nos dois exemplos são as mesmas.

var Car = function(loc) {
    this.loc = loc;
};

Car.prototype.move = function() {
        this.loc++;
};

var amy = new Car(1);
amy.move();
var ben = new Car(9);
ben.move();

Finalmente, não deve ser muito difícil perceber como a programação orientada a objetos pode ser feita. Existem duas seções.

Uma seção que define propriedades / métodos comuns no protótipo (cadeia).

E outra seção em que você coloca as definições que distinguem os objetos um do outro ( locvariável nos exemplos).

É isso que nos permite aplicar conceitos como superclasse ou subclasse em JavaScript.

Sinta-se livre para adicionar ou editar. Mais uma vez completo, talvez eu possa fazer deste um wiki da comunidade.

Ely
fonte
Para não bater em um post muito completo, mas eu pensei que OO e herança prototípica eram diferentes escolas de pensamento.
22416 Nick Pineda
Eles são, mas pode-se "fazer OO" com diferentes técnicas / pensamentos, não é?
Ely
Não tenho certeza. Muitos apenas dizem que a filosofia prototípica é apenas diferente e muitos tentam compará-la à OO porque é a escola de pensamento com a qual muitos estão acostumados.
22416 Nick Pineda
Quero dizer, se você deseja praticar o estilo OO e a linguagem oferece um conjunto de técnicas que ajudam a fazê-lo, isso não é necessariamente errado.
Ely
11

Acredito que @Matthew Crumley está certo. Eles são funcionalmente , se não estruturalmente, equivalentes. Se você usar o Firebug para examinar os objetos criados usando new, poderá ver que eles são iguais. No entanto, minha preferência seria a seguinte. Eu estou supondo que parece mais com o que estou acostumado em C # / Java. Ou seja, defina a classe, defina os campos, construtor e métodos.

var A = function() {};
A.prototype = {
    _instance_var: 0,

    initialize: function(v) { this._instance_var = v; },

    x: function() {  alert(this._instance_var); }
};

EDIT Não quis dizer que o escopo da variável era privado, apenas estava tentando ilustrar como defino minhas classes em javascript. O nome da variável foi alterado para refletir isso.

tvanfosson
fonte
2
_instance_var como na propriedade initializee x methods do not refer to the _instance_var` em uma Ainstância, mas em uma global. Use this._instance_varse você pretendia usar a _instance_varpropriedade de uma Ainstância.
Lekensteyn
2
O engraçado é que, Benry feito tal erro, assim, que foi descoberto depois de dois anos, bem como: p
Lekensteyn
10

Conforme discutido em outras respostas, é realmente uma consideração de desempenho porque a função no protótipo é compartilhada com todas as instanciações - em vez da função que está sendo criada para cada instanciação.

Eu montei um jsperf para mostrar isso. Há uma diferença dramática no tempo necessário para instanciar a classe, embora seja realmente relevante apenas se você estiver criando muitas instâncias.

http://jsperf.com/functions-in-constructor-vs-prototype

Devgr
fonte
8

Pense em linguagem de tipo estaticamente, as coisas prototypesão estáticas e as coisas thissão relacionadas à instância.

Wayou
fonte