Como o JavaScript .prototype funciona?


2041

Não gosto muito de linguagens de programação dinâmicas, mas escrevi meu quinhão de código JavaScript. Eu realmente nunca entendi essa programação baseada em protótipo, alguém sabe como isso funciona?

var obj = new Object();
obj.prototype.test = function() { alert('Hello?'); };
var obj2 = new obj();
obj2.test();

Lembro-me de muita discussão que tive com as pessoas há um tempo (não sei exatamente o que estou fazendo), mas, pelo que entendi, não há conceito de classe. É apenas um objeto, e as instâncias desses objetos são clones do original, certo?

Mas qual é o objetivo exato dessa propriedade ".prototype" em JavaScript? Como isso se relaciona a instanciar objetos?

Atualização: maneira correta

var obj = new Object(); // not a functional object
obj.prototype.test = function() { alert('Hello?'); }; // this is wrong!

function MyObject() {} // a first class functional object
MyObject.prototype.test = function() { alert('OK'); } // OK

Também esses slides realmente ajudaram bastante.


78
John Resig tem alguns slides sobre protótipos de funções que foram úteis para mim quando analisamos o assunto (você também pode fazer alterações no código e ver o que acontece ...) http://ejohn.org/apps/learn/#64
21415 John Foster

5
Um ótimo material de referência, para manter essa pergunta informativa, talvez coloque alguns dos comentários do site de John em sua resposta, caso o site dele mude de uma maneira que seu link não esteja mais disponível. De qualquer maneira +1, me ajudou.
27711 Chris

95
+1 no seu link para o slide Ninja n ° 64 de John Resig . A partir daí, foi realmente útil e sinto que entendi os protótipos corretamente.
um nerd pago

4
Nós realmente precisamos de um objeto funcional para aplicar o protótipo? Se sim, por quê?
Anshul

6
Naor

Respostas:


1007

Todo objeto JavaScript possui um "slot" interno chamado [[Prototype]]cujo valor é um nullou um object. Você pode pensar em um slot como uma propriedade em um objeto, interno ao mecanismo JavaScript, oculto no código que você escreve. Os colchetes ao redor[[Prototype]] são deliberados e são uma convenção de especificação ECMAScript para indicar slots internos.

O valor apontado pelo [[Prototype]] de um objeto é conhecido coloquialmente como "o protótipo desse objeto".

Se você acessar uma propriedade através da notação dot ( obj.propName) ou bracket ( obj['propName']), e o objeto não tiver essa propriedade diretamente (ou seja, uma propriedade própria , verificável via obj.hasOwnProperty('propName')), o tempo de execução procurará uma propriedade com esse nome no objeto referenciado pelo [[Prototype]]invés. Se o [[Prototype]] também não tiver essa propriedade, ela [[Prototype]]será verificada por sua vez, e assim por diante. Dessa forma, a cadeia de protótipos do objeto original é percorrida até que uma correspondência seja encontrada ou seu fim seja alcançado. No topo da cadeia de protótipos está o nullvalor.

As implementações modernas de JavaScript permitem acesso de leitura e / ou gravação ao [[Prototype]], das seguintes maneiras:

  1. o new operador (configura a cadeia de protótipo no objeto padrão retornado de uma função de construtor),
  2. A extendspalavra-chave (configura a cadeia de protótipos ao usar a sintaxe da classe),
  3. Object.createdefinirá o argumento fornecido como o [[Prototype]]do objeto resultante,
  4. Object.getPrototypeOfe Object.setPrototypeOf(obter / definir a criação do objeto [[Prototype]] após ), e
  5. A propriedade acessador padronizado (ou seja, getter / setter) denominada __proto__(semelhante a 4.)

Object.getPrototypeOfe Object.setPrototypeOfsão preferidos __proto__, em parte porque o comportamento de o.__proto__ é incomum quando um objeto possui um protótipo de null.

Um objeto [[Prototype]]é definido inicialmente durante a criação do objeto.

Se você criar um novo objeto via new Func(), [[Prototype]]por padrão, o objeto será definido como o objeto referenciado por Func.prototype.

Observe que, portanto, todas as classes e todas as funções que podem ser usadas com o newoperador têm uma propriedade nomeada .prototypealém de seu próprio [[Prototype]]slot interno. Esse uso duplo da palavra "protótipo" é fonte de confusão sem fim entre os recém-chegados ao idioma.

O uso newcom funções de construtor nos permite simular herança clássica em JavaScript; embora o sistema de herança do JavaScript seja - como vimos - prototípico, e não baseado em classes.

Antes da introdução da sintaxe da classe ao JavaScript, as funções do construtor eram a única maneira de simular classes. Podemos pensar nas propriedades do objeto referenciado pela .prototypepropriedade da função construtora como membros compartilhados; ie membros iguais para cada instância. Em sistemas baseados em classes, os métodos são implementados da mesma maneira para cada instância, portanto, métodos são conceitualmente adicionados ao.prototype propriedade; os campos de um objeto, no entanto, são específicos da instância e, portanto, são adicionados ao próprio objeto durante a construção.

Sem a sintaxe da classe, os desenvolvedores precisavam configurar manualmente a cadeia de protótipos para obter funcionalidade semelhante à herança clássica. Isso levou a uma preponderância de diferentes maneiras de conseguir isso.

Aqui está uma maneira:

function Child() {}
function Parent() {}
Parent.prototype.inheritedMethod = function () { return 'this is inherited' }

function inherit(child, parent) {
  child.prototype = Object.create(parent.prototype)
  child.prototype.constructor = child
  return child;
}

Child = inherit(Child, Parent)
const o = new Child
console.log(o.inheritedMethod()) // 'this is inherited'

... e aqui está outra maneira:

function Child() {}
function Parent() {}
Parent.prototype.inheritedMethod = function () { return 'this is inherited' }

function inherit(child, parent) {
    function tmp() {}
    tmp.prototype = parent.prototype
    const proto = new tmp()
    proto.constructor = child
    child.prototype = proto
    return child
}

Child = inherit(Child, Parent)
const o = new Child
console.log(o.inheritedMethod()) // 'this is inherited'

A sintaxe da classe introduzida no ES2015 simplifica as coisas, fornecendo extends como "a única maneira verdadeira" de configurar a cadeia de protótipos para simular a herança clássica em JavaScript.

Portanto, semelhante ao código acima, se você usar a sintaxe da classe para criar um novo objeto como este:

class Parent { inheritedMethod() { return 'this is inherited' } }
class Child extends Parent {}

const o = new Child
console.log(o.inheritedMethod()) // 'this is inherited'

... o objeto resultante [[Prototype]]será definido como uma instância de Parentcuja [[Prototype]], por sua vez, é Parent.prototype.

Finalmente, se você criar um novo objeto via Object.create(foo), o objeto resultante [[Prototype]]será definido como foo.


1
Então, estou fazendo algo errado ao definir novas propriedades na propriedade prototype no meu pequeno snippet?
21411 John Leidegren

3
Eu acho que é isso que significa ter objetos funcionais como cidadãos de primeira classe.
21420 John Leidegren

8
Eu odeio coisas fora do padrão, especialmente em linguagens de programação, por que existe um proto quando claramente não é necessário?
22611 John Leidegren

1
O @John __proto__ é necessário apenas para uso interno pelo interpretador JS. Cada Objeto precisa saber quais propriedades e métodos fazem parte do protótipo e quais fazem parte do próprio Objeto. O interpretador JS precisa ser capaz de chamar métodos prototipados em um objeto.
BMiner

17
observe que o uso de [[Protótipo]] é deliberado - o ECMA-262 inclui nomes de propriedades internas com colchetes duplos
Christoph

1798

Em uma linguagem que implementa herança clássica como Java, C # ou C ++, você começa criando uma classe - um blueprint para seus objetos - e pode criar novos objetos a partir dessa classe ou pode estender a classe, definindo uma nova classe que aprimora a classe original.

No JavaScript, você primeiro cria um objeto (não há conceito de classe) e, em seguida, pode aumentar seu próprio objeto ou criar novos objetos a partir dele. Não é difícil, mas um pouco estranho e difícil de metabolizar para alguém acostumado à maneira clássica.

Exemplo:

//Define a functional object to hold persons in JavaScript
var Person = function(name) {
  this.name = name;
};

//Add dynamically to the already defined object a new getter
Person.prototype.getName = function() {
  return this.name;
};

//Create a new object of type Person
var john = new Person("John");

//Try the getter
alert(john.getName());

//If now I modify person, also John gets the updates
Person.prototype.sayMyName = function() {
  alert('Hello, my name is ' + this.getName());
};

//Call the new method on john
john.sayMyName();

Até agora estendi o objeto base, agora crio outro objeto e depois herdo de Person.

//Create a new object of type Customer by defining its constructor. It's not 
//related to Person for now.
var Customer = function(name) {
    this.name = name;
};

//Now I link the objects and to do so, we link the prototype of Customer to 
//a new instance of Person. The prototype is the base that will be used to 
//construct all new instances and also, will modify dynamically all already 
//constructed objects because in JavaScript objects retain a pointer to the 
//prototype
Customer.prototype = new Person();     

//Now I can call the methods of Person on the Customer, let's try, first 
//I need to create a Customer.
var myCustomer = new Customer('Dream Inc.');
myCustomer.sayMyName();

//If I add new methods to Person, they will be added to Customer, but if I
//add new methods to Customer they won't be added to Person. Example:
Customer.prototype.setAmountDue = function(amountDue) {
    this.amountDue = amountDue;
};
Customer.prototype.getAmountDue = function() {
    return this.amountDue;
};

//Let's try:       
myCustomer.setAmountDue(2000);
alert(myCustomer.getAmountDue());

Enquanto, como disse, não posso chamar setAmountDue (), getAmountDue () em uma Pessoa.

//The following statement generates an error.
john.setAmountDue(1000);

352
Eu acho que as respostas no stackoverflow não são apenas interessantes para o pôster original, mas também para uma grande comunidade de outras pessoas que estão à espreita ou provenientes de pesquisas. E eu fui um deles e me beneficiei de posts antigos. Eu acho que poderia contribuir com as outras respostas adicionando alguns exemplos de código. Sobre sua pergunta: se você deixar de fora o novo, ele não funcionará. quando eu chamo myCustomer.sayMyName (), ele retorna "myCustomer.sayMyName não é uma função". A maneira mais fácil é experimentar o firebug e ver o que acontece.
stivlo

7
Tanto quanto eu entendo var Person = function (name) {...}; está definindo uma função construtora capaz de criar objetos pessoais. Portanto, ainda não há Objeto, apenas a função construtora anônima é atribuída a Person. Esta é uma explicação muito boa: helephant.com/2008/08/how-javascript-objects-work
stivlo

17
AVISO: Esta resposta negligencia o fato de que o construtor da classe pai não é chamado por instância. A única razão pela qual isso funciona é porque ele fez exatamente a mesma coisa (definindo o nome) no construtor filho e pai. Para obter uma explicação mais aprofundada sobre erros comuns cometidos ao tentar herança em JavaScript (e uma solução final), consulte: este post de estouro de pilha
Aaren Cordova 13/13/13

3
Percebo que essa resposta também não menciona que, ao usar "new Person ()" como protótipo, você está realmente definindo a propriedade de instância "name" de "Person" como uma propriedade estática de "Customer" (portanto, todos os Customer instâncias terão a mesma propriedade). Embora seja um bom exemplo básico, NÃO FAÇA ISSO. :) Crie uma nova função anônima para atuar como uma "ponte", configurando o protótipo para "Person.prototype", crie uma instância a partir dela e defina "Customer.prototype" para essa instância anônima.
James Wilkins

10
Sobre a Customer.prototype = new Person();linha, o MDN mostra um exemplo usando Customer.prototype = Object.create(Person.prototype)e afirma que 'Um erro comum aqui é usar "new Person ()"' . fonte
Rafael Eyng

186

Este é um modelo de objeto baseado em protótipo muito simples que seria considerado uma amostra durante a explicação, sem comentários ainda:

function Person(name){
    this.name = name;
}
Person.prototype.getName = function(){
    console.log(this.name);
}
var person = new Person("George");

Há alguns pontos cruciais que devemos considerar antes de passar pelo conceito de protótipo.

1- Como as funções JavaScript realmente funcionam:

Para dar o primeiro passo, precisamos descobrir como as funções JavaScript realmente funcionam, como uma classe como uma função usando thispalavra-chave ou apenas como uma função regular com seus argumentos, o que faz e o que retorna.

Digamos que queremos criar um Personmodelo de objeto. mas nesta etapa tentarei fazer exatamente a mesma coisa sem usar uma palavra prototype- newchave .

Portanto, nesta etapa functions, objectse thispalavra - chave, são tudo o que temos.

A primeira pergunta seria como a thispalavra-chave poderia ser útil sem usar a newpalavra-chave .

Então, para responder isso, digamos que temos um objeto vazio e duas funções como:

var person = {};
function Person(name){  this.name = name;  }

function getName(){
    console.log(this.name);
}

e agora sem usar a newpalavra-chave como poderíamos usar essas funções. Portanto, o JavaScript tem três maneiras diferentes de fazer isso:

uma. A primeira maneira é apenas chamar a função como uma função regular:

Person("George");
getName();//would print the "George" in the console

nesse caso, esse seria o objeto de contexto atual, que geralmente é o windowobjeto global no navegador ou GLOBALno Node.js. Isso significa que teríamos window.name no navegador ou GLOBAL.name no Node.js, com "George" como seu valor.

b. Podemos anexá- los a um objeto, como suas propriedades

- A maneira mais fácil de fazer isso é modificar o personobjeto vazio , como:

person.Person = Person;
person.getName = getName;

Desta forma, podemos chamá-los como:

person.Person("George");
person.getName();// -->"George"

e agora o personobjeto é como:

Object {Person: function, getName: function, name: "George"}

- A outra maneira de anexar uma propriedade a um objeto é usar o prototypeobjeto que pode ser encontrado em qualquer objeto JavaScript com o nome de __proto__, e tentei explicar um pouco na parte de resumo. Para que pudéssemos obter o resultado semelhante, fazendo:

person.__proto__.Person = Person;
person.__proto__.getName = getName;

Mas, dessa maneira, o que realmente estamos fazendo é modificar o arquivo Object.prototype, porque sempre que criamos um objeto JavaScript usando literals ( { ... }), ele é criado com base em Object.prototype, o que significa que ele é anexado ao objeto recém-criado como um atributo chamado __proto__, portanto, se o alterarmos , como fizemos no snippet de código anterior, todos os objetos JavaScript seriam alterados, não é uma boa prática. Então, qual poderia ser a melhor prática agora:

person.__proto__ = {
    Person: Person,
    getName: getName
};

e agora outros objetos estão em paz, mas ainda não parece ser uma boa prática. Portanto, ainda temos mais uma solução, mas, para usá-la, devemos retornar à linha de código em que o personobjeto foi criado ( var person = {};) e alterá-lo da seguinte forma:

var propertiesObject = {
    Person: Person,
    getName: getName
};
var person = Object.create(propertiesObject);

o que ele faz é criar um novo JavaScript Objecte anexá propertiesObject-lo ao __proto__atributo Portanto, para garantir que você possa fazer:

console.log(person.__proto__===propertiesObject); //true

Mas o ponto complicado aqui é que você tem acesso a todas as propriedades definidas no __proto__primeiro nível do personobjeto (leia a parte do resumo para obter mais detalhes).


como você vê, usando qualquer uma dessas duas direções thisapontaria exatamente para o personobjeto.

c. O JavaScript tem outra maneira de fornecer a função this, que está usando call ou apply para invocar a função.

O método apply () chama uma função com um dado valor e argumentos fornecidos como uma matriz (ou um objeto semelhante a uma matriz).

e

O método call () chama uma função com um determinado valor e argumentos fornecidos individualmente.

dessa maneira, que é a minha favorita, podemos chamar facilmente nossas funções como:

Person.call(person, "George");

ou

//apply is more useful when params count is not fixed
Person.apply(person, ["George"]);

getName.call(person);   
getName.apply(person);

esses três métodos são as etapas iniciais importantes para descobrir a funcionalidade do protótipo.


2- Como a newpalavra - chave funciona?

este é o segundo passo para entender a .prototypefuncionalidade. é isso que eu uso para simular o processo:

function Person(name){  this.name = name;  }
my_person_prototype = { getName: function(){ console.log(this.name); } };

Nesta parte, tentarei executar todas as etapas que o JavaScript executar, sem usar a newpalavra - chave e prototype, quando você usar a newpalavra-chave. então, quando o fazemos new Person("George"), a Personfunção serve como construtor. É isso que o JavaScript faz, um por um:

uma. Antes de tudo, ele cria um objeto vazio, basicamente um hash vazio como:

var newObject = {};

b. a próxima etapa que o JavaScript executa é anexar todos os objetos de protótipo ao objeto recém-criado

temos my_person_prototypeaqui semelhante ao objeto protótipo.

for(var key in my_person_prototype){
    newObject[key] = my_person_prototype[key];
}

Não é assim que o JavaScript realmente anexa as propriedades definidas no protótipo. A maneira atual está relacionada ao conceito de cadeia de protótipos.


uma. & b. Em vez destas duas etapas, você pode obter exatamente o mesmo resultado:

var newObject = Object.create(my_person_prototype);
//here you can check out the __proto__ attribute
console.log(newObject.__proto__ === my_person_prototype); //true
//and also check if you have access to your desired properties
console.log(typeof newObject.getName);//"function"

agora podemos chamar a getNamefunção em nosso my_person_prototype:

newObject.getName();

c. então ele fornece esse objeto ao construtor,

podemos fazer isso com nossa amostra como:

Person.call(newObject, "George");

ou

Person.apply(newObject, ["George"]);

então o construtor pode fazer o que quiser, porque esse interior desse construtor é o objeto que acabou de ser criado.

agora o resultado final antes de simular as outras etapas: Objeto {nome: "George"}


Resumo:

Basicamente, quando você usa a nova palavra-chave em uma função, você está chamando isso e essa função serve como construtor; portanto, quando você diz:

new FunctionName()

O JavaScript cria internamente um objeto, um hash vazio e, em seguida, fornece esse objeto ao construtor; então, o construtor pode fazer o que quiser, porque isso dentro desse construtor é o objeto que acabou de ser criado e, em seguida, fornece esse objeto, é claro se você não usou a instrução de retorno em sua função ou se você colocou um return undefined;no final do corpo da função.

Portanto, quando o JavaScript procura uma propriedade em um objeto, a primeira coisa que faz é procurá-lo nesse objeto. E depois há uma propriedade secreta da [[prototype]]qual costumamos ter __proto__e é essa a aparência do JavaScript a seguir. E quando olha através do __proto__, na medida em que é outro objeto JavaScript, ele tem seu próprio __proto__atributo, sobe e sobe até chegar ao ponto em que o próximo __proto__é nulo. O ponto é o único objeto no JavaScript que seu __proto__atributo é nulo é o Object.prototypeobjeto:

console.log(Object.prototype.__proto__===null);//true

e é assim que a herança funciona em JavaScript.

A cadeia de protótipos

Em outras palavras, quando você tem uma propriedade prototype em uma função e chama uma nova, depois que o JavaScript terminar de examinar o objeto recém-criado por propriedades, ele analisará as propriedades da função .prototypee também é possível que esse objeto tenha sua propriedade. próprio protótipo interno. e assim por diante.


6
a) Por favor, não explique os protótipos copiando propriedades. b) A configuração do [[protótipo]] interno acontece antes que a função construtora seja aplicada à instância, altere a ordem. c) jQuery é totalmente offtopic nesta questão
Bergi

1
@ Bergi: obrigado por apontar, eu ficaria agradecido se você me informar se está tudo bem agora.
Mehran Hatami

7
Você pode simplificar? Você está certo em todos os pontos, mas os alunos que lêem essa explicação podem ficar realmente confusos pela primeira vez. escolha um exemplo mais simples e deixe o código se explicar ou adicione vários comentários para esclarecer o que você quer dizer.
PM

2
@PM: Obrigado pelo seu feedback. Tentei torná-lo o mais simples possível, mas acho que você está certo. Ainda há alguns pontos vagos. Então, tentarei modificá-lo e também ser mais descritivo. :)
Mehran Hatami 04/04

1
@AndreaMattioli porque dessa maneira você está criando um objeto totalmente novo e substituindo o antigo, que também pode ser compartilhado com outros objetos. Ao substituir, __proto__você primeiro apagará todas as propriedades de protótipo de nível superior e, em seguida, terá uma nova base de protótipos que não será mais compartilhada, a menos que você as compartilhe.
Mehran Hatami

77

Os sete Koans do protótipo

Enquanto Ciro San descia o Monte Fire Fox após meditação profunda, sua mente estava clara e pacífica.

Sua mão, no entanto, estava inquieta, e por si mesma pegou um pincel e anotou as seguintes notas.


0) Duas coisas diferentes podem ser chamadas de "protótipo":

  • a propriedade prototype, como em obj.prototype

  • a propriedade interna do protótipo, indicada como [[Prototype]] no ES5 .

    Pode ser recuperado através do ES5 Object.getPrototypeOf().

    O Firefox o torna acessível através da __proto__propriedade como uma extensão. O ES6 agora menciona alguns requisitos opcionais para __proto__.


1) Esses conceitos existem para responder à pergunta:

Quando eu faço obj.property, onde o JS procura .property?

Intuitivamente, a herança clássica deve afetar a pesquisa de propriedades.


2)

  • __proto__é usado para a .pesquisa de propriedades do ponto como em obj.property.
  • .prototypenão é usado para pesquisa diretamente, apenas indiretamente, pois determina __proto__na criação do objeto new.

A ordem de pesquisa é:

  • objpropriedades adicionadas com obj.p = ...ouObject.defineProperty(obj, ...)
  • propriedades de obj.__proto__
  • propriedades de obj.__proto__.__proto__e assim por diante
  • se __proto__houver null, volte undefined.

Essa é a chamada cadeia de protótipos .

Você pode evitar a .pesquisa com obj.hasOwnProperty('key')eObject.getOwnPropertyNames(f)


3) Existem duas maneiras principais de definir obj.__proto__:

  • new:

    var F = function() {}
    var f = new F()

    então newdefiniu:

    f.__proto__ === F.prototype

    Este é o lugar onde .prototypese acostuma.

  • Object.create:

     f = Object.create(proto)

    conjuntos:

    f.__proto__ === proto

4) O código:

var F = function(i) { this.i = i }
var f = new F(1)

Corresponde ao seguinte diagrama (algumas Numbercoisas são omitidas):

(Function)       (  F  )                                      (f)----->(1)
 |  ^             | | ^                                        |   i    |
 |  |             | | |                                        |        |
 |  |             | | +-------------------------+              |        |
 |  |constructor  | |                           |              |        |
 |  |             | +--------------+            |              |        |
 |  |             |                |            |              |        |
 |  |             |                |            |              |        |
 |[[Prototype]]   |[[Prototype]]   |prototype   |constructor   |[[Prototype]]
 |  |             |                |            |              |        |
 |  |             |                |            |              |        |
 |  |             |                | +----------+              |        |
 |  |             |                | |                         |        |
 |  |             |                | | +-----------------------+        |
 |  |             |                | | |                                |
 v  |             v                v | v                                |
(Function.prototype)              (F.prototype)                         |
 |                                 |                                    |
 |                                 |                                    |
 |[[Prototype]]                    |[[Prototype]]          [[Prototype]]|
 |                                 |                                    |
 |                                 |                                    |
 | +-------------------------------+                                    |
 | |                                                                    |
 v v                                                                    v
(Object.prototype)                                       (Number.prototype)
 | | ^
 | | |
 | | +---------------------------+
 | |                             |
 | +--------------+              |
 |                |              |
 |                |              |
 |[[Prototype]]   |constructor   |prototype
 |                |              |
 |                |              |
 |                | -------------+
 |                | |
 v                v |
(null)           (Object)

Este diagrama mostra muitos nós de objetos predefinidos por idioma:

  • null
  • Object
  • Object.prototype
  • Function
  • Function.prototype
  • 1
  • Number.prototype(pode ser encontrado com (1).__proto__parênteses obrigatórios para satisfazer a sintaxe)

Nossas duas linhas de código criaram apenas os seguintes novos objetos:

  • f
  • F
  • F.prototype

iagora é uma propriedade de fporque quando você faz:

var f = new F(1)

avalia Fcomo thissendo o valor que newretornará, o qual será atribuído f.


5) .constructor normalmente vem F.prototypeatravés da .pesquisa:

f.constructor === F
!f.hasOwnProperty('constructor')
Object.getPrototypeOf(f) === F.prototype
F.prototype.hasOwnProperty('constructor')
F.prototype.constructor === f.constructor

Quando escrevemos f.constructor, o JavaScript faz a .pesquisa como:

  • f não tem .constructor
  • f.__proto__ === F.prototypetem .constructor === F, então pegue

O resultado f.constructor == Fé intuitivamente correto, pois Fé usado para construir f, por exemplo, definir campos, como nos idiomas clássicos de POO.


6) A sintaxe clássica de herança pode ser alcançada através da manipulação de cadeias de protótipos.

O ES6 adiciona as palavras class- extendschave e , que são principalmente açúcar de sintaxe para a loucura de manipulação de protótipo anteriormente possível.

class C {
    constructor(i) {
        this.i = i
    }
    inc() {
        return this.i + 1
    }
}

class D extends C {
    constructor(i) {
        super(i)
    }
    inc2() {
        return this.i + 2
    }
}
// Inheritance syntax works as expected.
c = new C(1)
c.inc() === 2
(new D(1)).inc() === 2
(new D(1)).inc2() === 3
// "Classes" are just function objects.
C.constructor === Function
C.__proto__ === Function.prototype
D.constructor === Function
// D is a function "indirectly" through the chain.
D.__proto__ === C
D.__proto__.__proto__ === Function.prototype
// "extends" sets up the prototype chain so that base class
// lookups will work as expected
var d = new D(1)
d.__proto__ === D.prototype
D.prototype.__proto__ === C.prototype
// This is what `d.inc` actually does.
d.__proto__.__proto__.inc === C.prototype.inc
// Class variables
// No ES6 syntax sugar apparently:
// http://stackoverflow.com/questions/22528967/es6-class-variable-alternatives
C.c = 1
C.c === 1
// Because `D.__proto__ === C`.
D.c === 1
// Nothing makes this work.
d.c === undefined

Diagrama simplificado sem todos os objetos predefinidos:

(c)----->(1)
 |   i
 |
 |
 |[[Prototype]]
 |
 |
 v    __proto__
(C)<--------------(D)         (d)
| |                |           |
| |                |           |
| |prototype       |prototype  |[[Prototype]] 
| |                |           |
| |                |           |
| |                | +---------+
| |                | |
| |                | |
| |                v v
|[[Prototype]]    (D.prototype)--------> (inc2 function object)
| |                |             inc2
| |                |
| |                |[[Prototype]]
| |                |
| |                |
| | +--------------+
| | |
| | |
| v v
| (C.prototype)------->(inc function object)
|                inc
v
Function.prototype

Vamos dedicar um momento para estudar como o seguinte funciona:

c = new C(1)
c.inc() === 2

Os primeiros conjuntos de linha c.ipara 1como explicado em "4)".

Na segunda linha, quando fazemos:

c.inc()
  • .incé encontrado através da [[Prototype]]cadeia: c-> C-> C.prototype->inc
  • quando chamamos uma função em Javascript como X.Y(), o JavaScript automaticamente é thisigual a Xdentro da Y()chamada de função!

A mesma lógica exata também explica d.ince d.inc2.

Este artigo https://javascript.info/class#not-just-a-syntax-sugar menciona outros efeitos que classvale a pena conhecer. Alguns deles podem não ser alcançáveis ​​sem a classpalavra - chave (TODO, verifique qual):


1
@tomasb thanks! "Não sei de onde você tirou isso": depois de ver algumas dessas linguagens dinâmicas, notei o que mais importa no sistema de classes é como a .pesquisa funciona (e quantas cópias dos dados são feitas) . Então, decidi entender esse ponto. O resto são Google + posts do blog + um intérprete Js em mãos. :)
Ciro Santilli escreveu:

1
Ainda não entendi porque g.constructor === Objeto porque você disse que "4) Quando você faz f = new F, new também define f.constructor = F". Você poderia me explicar mais? Enfim, esta é a melhor resposta que estou procurando. Muito obrigado!
Nguyenngoc101

@ nguyenngoc101 thanks! A sets f.constructor = Fpeça estava descaradamente errada e contraditória com outras seções: .constructoré encontrada através da .pesquisa na cadeia de protótipos. Corrigido agora.
Ciro Santilli escreveu

da discussão toda o que eu recebo (proveniente da herança clássica) se eu criar uma função construtora e tentar criar uma instância dela usando o novo operador, obterá apenas métodos e propriedades anexados ao objeto proto, portanto, é necessário anexar todo o método e propriedades para proto objeto se quisermos herdar, mi certo?
Blackhawk

1
@CiroSantilli 法轮功 死 六四 事件 法轮功 Não acho que seja um bug no Chromium. Eu acho que é apenas um sintoma que fo protótipo é definido Fapenas no momento da construção; fnão saberá nem se preocupará F.prototypea qualquer momento após sua primeira construção.
John Glassmyer

76

prototypepermite que você faça aulas. se você não usar prototype, ele se tornará estático.

Aqui está um pequeno exemplo.

var obj = new Object();
obj.test = function() { alert('Hello?'); };

No caso acima, você tem um teste de chamada de função estática. Esta função pode ser acessada apenas por obj.test, onde você pode imaginar obj como uma classe.

onde como no código abaixo

function obj()
{
}

obj.prototype.test = function() { alert('Hello?'); };
var obj2 = new obj();
obj2.test();

O obj tornou-se uma classe que agora pode ser instanciada. Várias instâncias de obj podem existir e todas elas têm a testfunção.

O acima é o meu entendimento. Estou criando um wiki da comunidade, para que as pessoas possam me corrigir se eu estiver errado.


13
-1: prototypeé uma propriedade das funções do construtor, não das instâncias, ou seja, seu código está errado! Talvez você quis dizer a propriedade não-padrão __proto__de objetos, mas isso é um animal totalmente diferente ...
Christoph

@Christoph - Obrigado por apontar. Eu atualizei o código de exemplo.
Ramesh

3
Há muito mais nisso ... Além disso, o JavaScript não é uma linguagem baseada em classe - ele lida com herança por protótipos, é preciso cobrir as diferenças com mais detalhes!
James

5
Eu acho que essa resposta é um pouco enganadora.
Armin Cifuentes

Talvez a resposta seja "equivocada", mas explica para que serve o protótipo e agora está claro para mim, depois de todas essas "respostas" com centenas de votos "acima". Obrigado.
Aleks

66

Depois de ler este tópico, sinto-me confuso com a cadeia de protótipos JavaScript, e encontrei esses gráficos

http://iwiki.readthedocs.org/en/latest/javascript/js_core.html#inheritance * [[protytype]] * e <code> prototype </code> propriedade de objetos de função

é um gráfico claro para mostrar a herança de JavaScript pela cadeia de protótipos

e

http://www.javascriptbank.com/javascript/article/JavaScript_Classical_Inheritance/

este contém um exemplo com código e vários diagramas agradáveis.

A cadeia de protótipos finalmente volta ao Object.prototype.

A cadeia de protótipos pode ser tecnicamente estendida pelo tempo que você desejar, definindo o protótipo da subclasse igual a um objeto da classe pai.

Espero que também seja útil para você entender a cadeia de protótipos JavaScript.


É possível ter herança múltipla em Javascript?

Foo é um objeto literal aqui ou um objeto de função? Se for um objeto literal, acredito que o protótipo Foo.protect não apontará de volta ao Foo via construtor.
Madhur Ahuja

@ user3376708 JavaScript suporta apenas herança simples ( fonte )
Rafael Eyng

@ Nuno_147 Não está claro no começo, mas se você olhar o suficiente, poderá obter algo disso.
Marcelocra # 26/14

3
Você pode explicar o que [[Prototype]]significa?
CodyBugstein 27/10/2014

40

Todo objeto tem uma propriedade interna, [[Prototype]] , vinculando-a a outro objeto:

object [[Prototype]]  anotherObject

No javascript tradicional, o objeto vinculado é prototypepropriedade de uma função:

object [[Prototype]]  aFunction.prototype

Alguns ambientes expõem [[Prototype]] como __proto__:

anObject.__proto__ === anotherObject

Você cria o link [[Prototype]] ao criar um objeto.

// (1) Object.create:
var object = Object.create(anotherObject)
// object.__proto__ = anotherObject

// (2) ES6 object initializer:
var object = { __proto__: anotherObject };
// object.__proto__ = anotherObject

// (3) Traditional JavaScript:
var object = new aFunction;
// object.__proto__ = aFunction.prototype

Portanto, essas instruções são equivalentes:

var object = Object.create(Object.prototype);
var object = { __proto__: Object.prototype }; // ES6 only
var object = new Object;

Você não pode ver o link target ( Object.prototype) em uma nova declaração; em vez disso, o destino é implícito pelo construtor ( Object).

Lembrar:

  • Todo objeto tem um link, [[Prototype]] , às vezes exposto como __proto__ .
  • Toda função tem uma prototypepropriedade, inicialmente mantendo um objeto vazio.
  • Objetos criados com new estão vinculados à prototypepropriedade de seu construtor.
  • Se uma função nunca for usada como construtor, sua prototypepropriedade não será usada.
  • Se você não precisar de um construtor, use Object.create em vez de new.

1
A revisão 5 removeu algumas informações úteis, incluindo informações sobre Object.create (). Veja a revisão 4 .
Palec 26/07/15

@Palec, o que devo adicionar de volta?
sam

2
IMO, pelo menos, o link para Object.create()documentos , @sam. Links para __proto__e Object.prototypeseriam ótimos aprimoramentos. E gostei dos seus exemplos de como os protótipos funcionam com construtores e Object.create(), mas provavelmente eram a parte mais longa e menos relevante da qual você queria se livrar.
Palec 29/07

da discussão toda o que eu recebo (proveniente da herança clássica) se eu criar uma função construtora e tentar criar uma instância dela usando o novo operador, obterá apenas métodos e propriedades anexados ao objeto proto, portanto, é necessário anexar todo o método e propriedades para proto objeto se quisermos herdar, mi certo?
Blackhawk

29

Javascript não tem herança no sentido usual, mas possui a cadeia de protótipos.

cadeia de protótipos

Se um membro de um objeto não puder ser encontrado no objeto, ele será procurado na cadeia de protótipos. A cadeia consiste em outros objetos. O protótipo de uma determinada instância pode ser acessado com a __proto__variável Todo objeto tem um, pois não há diferença entre classes e instâncias em javascript.

A vantagem de adicionar uma função / variável ao protótipo é que ele precisa estar na memória apenas uma vez, e não em todas as instâncias.

Também é útil para herança, porque a cadeia de protótipos pode consistir em muitos outros objetos.


1
O FF e o Chrome suportam proto , mas não o IE nem o Opera.
alguns

Georg, esclareça para um noob - "não há diferença entre classes e instâncias em javascript." - você poderia elaborar? Como é que isso funciona?
Hamish Grubijan

da discussão toda o que eu recebo (proveniente da herança clássica) se eu criar uma função construtora e tentar criar uma instância dela usando o novo operador, obterá apenas métodos e propriedades anexados ao objeto proto, portanto, é necessário anexar todo o método e propriedades para proto objeto se quisermos herdar, mi certo?
Blackhawk

28

Este artigo é longo. Mas tenho certeza de que limpará a maioria das suas consultas sobre a natureza "prototípica" da herança do JavaScript. E ainda mais. Por favor, leia o artigo completo.

JavaScript basicamente tem dois tipos de dados

  • Não objetos
  • Objetos

Não objetos

A seguir estão os tipos de dados Não-objeto

  • corda
  • número (incluindo NaN e Infinity)
  • valores booleanos (verdadeiro, falso)
  • Indefinido

Esses tipos de dados retornam a seguir quando você usa o operador typeof

typeof "string literal" (ou uma variável que contém string literal) === 'string'

typeof 5 (ou qualquer literal numérico ou uma variável contendo literal numérico ou NaN ou Infynity ) === 'número'

typeof true (ou false ou uma variável contendo true ou false ) === 'booleano'

tipo de indefinido (ou uma variável indefinida ou uma variável contendo indefinido ) === 'indefinido'

A string , o número e os tipos de dados booleanos podem ser representados como Objetos e Não objetos . Quando eles são representados como objetos, seu tipo é sempre === 'objeto'. Voltaremos a isso assim que entendermos os tipos de dados do objeto.

Objetos

Os tipos de dados do objeto podem ser divididos em dois tipos

  1. Objetos de tipo de função
  2. Objetos do tipo Não Função

Os objetos do tipo Function são os que retornam a string 'function' com o operador typeof . Todas as funções definidas pelo usuário e todo o JavaScript construído em objetos que podem criar novos objetos usando o novo operador se enquadram nessa categoria. Por exemplo.

  • Objeto
  • Corda
  • Número
  • boleano
  • Matriz
  • Matrizes digitadas
  • RegExp
  • Função
  • Todos os outros objetos internos que podem criar novos objetos usando o novo operador
  • função UserDefinedFunction () {/ * código definido pelo usuário * /}

Então, typeof (Object) === typeof (String) === typeof (Number) === typeof (Boolean) === typeof (Array) === typeof (RegExp) === typeof (Function) == = typeof (UserDefinedFunction) === 'função'

Na verdade, todos os objetos do tipo Function são instâncias do objeto JavaScript incorporado Function (incluindo o objeto Function, isto é, definido recursivamente). É como se esses objetos tivessem sido definidos da seguinte maneira

var Object= new Function ([native code for object Object])
var String= new Function ([native code for object String])
var Number= new Function ([native code for object Number])
var Boolean= new Function ([native code for object Boolean])
var Array= new Function ([native code for object Array])
var RegExp= new Function ([native code for object RegExp])
var Function= new Function ([native code  for object Function])
var UserDefinedFunction= new Function ("user defined code")

Como mencionado, os objetos do tipo Função podem criar novos objetos usando o novo operador . Por exemplo, um objeto do tipo Objeto , String , Número , Booleano , Matriz , RegExp ou UserDefinedFunction pode ser criado usando

var a=new Object() or var a=Object() or var a={} //Create object of type Object
var a=new String() //Create object of type String
var a=new Number() //Create object of type Number
var a=new Boolean() //Create object of type Boolean
var a=new Array() or var a=Array() or var a=[]  //Create object of type Array
var a=new RegExp() or var a=RegExp() //Create object of type RegExp
var a=new UserDefinedFunction() 

Os objetos assim criados são todos os objetos do tipo Non Function e retornam seu tipo de === 'objeto' . Em todos esses casos, o objeto "a" não pode mais criar objetos usando o operador new. Então, o seguinte está errado

var b=new a() //error. a is not typeof==='function'

O objeto interno Math é typeof === 'objeto' . Portanto, um novo objeto do tipo Math não pode ser criado por um novo operador.

var b=new Math() //error. Math is not typeof==='function'

Observe também que as funções Objeto , Matriz e RegExp podem criar um novo objeto sem usar o operador novo . No entanto, os seguintes não.

var a=String() // Create a new Non Object string. returns a typeof==='string' 
var a=Number() // Create a new Non Object Number. returns a typeof==='number'
var a=Boolean() //Create a new Non Object Boolean. returns a typeof==='boolean'

As funções definidas pelo usuário são um caso especial.

var a=UserDefinedFunction() //may or may not create an object of type UserDefinedFunction() based on how it is defined.

Como os objetos do tipo Função podem criar novos objetos, eles também são chamados de Construtores .

Todo Construtor / Função (incorporado ou definido pelo usuário) quando definido automaticamente possui uma propriedade chamada "prototype" cujo valor por padrão é definido como um objeto. Esse objeto em si possui uma propriedade chamada "construtor" que, por padrão, faz referência ao Construtor / Função .

Por exemplo, quando definimos uma função

function UserDefinedFunction()
{
}

o seguinte acontece automaticamente

UserDefinedFunction.prototype={constructor:UserDefinedFunction}

Essa propriedade "prototype" está presente apenas nos objetos do tipo Function (e nunca nos objetos do tipo Non Function ).

Isso ocorre porque, quando um novo objeto é criado (usando o novo operador), ele herda todas as propriedades e métodos do objeto de protótipo atual da função Constructor, ou seja, uma referência interna é criada no objeto recém-criado que faz referência ao objeto referenciado pelo objeto de protótipo atual da função Constructor.

Essa "referência interna" criada no objeto para referenciar propriedades herdadas é conhecida como protótipo do objeto (que faz referência ao objeto referenciado pela propriedade "prototype" do Construtor, mas é diferente dele). Para qualquer objeto (Função ou Não Função), isso pode ser recuperado usando o método Object.getPrototypeOf () . Usando esse método, é possível rastrear a cadeia de protótipos de um objeto.

Além disso, todo objeto criado ( tipo de função ou tipo não funcional ) possui uma propriedade "construtora" que é herdada do objeto referenciado pela propriedade prototype da função construtora. Por padrão, essa propriedade "construtor" faz referência à função Construtor que a criou (se o "protótipo" padrão da Função Construtor não for alterado).

Para todos os objetos do tipo Function, a função construtora é sempre a função Function () {}

Para objetos do tipo Non Function (por exemplo, Javascript Built in Math object) a função construtora é a função que o criou. Para o objeto Math , é a função Object () {} .

Todo o conceito explicado acima pode ser um pouco assustador de entender sem nenhum código de suporte. Por favor, siga o seguinte código linha por linha para entender o conceito. Tente executá-lo para entender melhor.

function UserDefinedFunction()
{ 

} 

/* creating the above function automatically does the following as mentioned earlier

UserDefinedFunction.prototype={constructor:UserDefinedFunction}

*/


var newObj_1=new UserDefinedFunction()

alert(Object.getPrototypeOf(newObj_1)===UserDefinedFunction.prototype)  //Displays true

alert(newObj_1.constructor) //Displays function UserDefinedFunction

//Create a new property in UserDefinedFunction.prototype object

UserDefinedFunction.prototype.TestProperty="test"

alert(newObj_1.TestProperty) //Displays "test"

alert(Object.getPrototypeOf(newObj_1).TestProperty)// Displays "test"

//Create a new Object

var objA = {
        property1 : "Property1",
        constructor:Array

}


//assign a new object to UserDefinedFunction.prototype
UserDefinedFunction.prototype=objA

alert(Object.getPrototypeOf(newObj_1)===UserDefinedFunction.prototype)  //Displays false. The object referenced by UserDefinedFunction.prototype has changed

//The internal reference does not change
alert(newObj_1.constructor) // This shall still Display function UserDefinedFunction

alert(newObj_1.TestProperty) //This shall still Display "test" 

alert(Object.getPrototypeOf(newObj_1).TestProperty) //This shall still Display "test"


//Create another object of type UserDefinedFunction
var newObj_2= new UserDefinedFunction();

alert(Object.getPrototypeOf(newObj_2)===objA) //Displays true.

alert(newObj_2.constructor) //Displays function Array()

alert(newObj_2.property1) //Displays "Property1"

alert(Object.getPrototypeOf(newObj_2).property1) //Displays "Property1"

//Create a new property in objA
objA.property2="property2"

alert(objA.property2) //Displays "Property2"

alert(UserDefinedFunction.prototype.property2) //Displays "Property2"

alert(newObj_2.property2) // Displays Property2

alert(Object.getPrototypeOf(newObj_2).property2) //Displays  "Property2"

A cadeia de protótipos de cada objeto é rastreada até Object.prototype (que por si só não possui nenhum objeto de protótipo). O código a seguir pode ser usado para rastrear a cadeia de protótipos de um objeto

var o=Starting object;

do {
    alert(o + "\n" + Object.getOwnPropertyNames(o))

}while(o=Object.getPrototypeOf(o))

A cadeia de protótipos para vários objetos funciona da seguinte maneira.

  • Todo objeto Function (incluindo o objeto Function incorporado) -> Function.prototype -> Object.prototype -> null
  • Objetos simples (criados por novo Object () ou {}, incluindo o objeto Math incorporado) -> Object.prototype -> null
  • Objeto criado com new ou Object.create -> Uma ou mais cadeias de protótipos -> Object.prototype -> null

Para criar um objeto sem nenhum protótipo, use o seguinte:

var o=Object.create(null)
alert(Object.getPrototypeOf(o)) //Displays null

Pode-se pensar que definir a propriedade prototype do Constructor como null deve criar um objeto com um protótipo null. No entanto, nesses casos, o protótipo do objeto recém-criado é definido como Object.prototype e seu construtor é definido para a função Object. Isso é demonstrado pelo seguinte código

function UserDefinedFunction(){}
UserDefinedFunction.prototype=null// Can be set to any non object value (number,string,undefined etc.)

var o=new UserDefinedFunction()
alert(Object.getPrototypeOf(o)==Object.prototype)   //Displays true
alert(o.constructor)    //Displays Function Object

A seguir no resumo deste artigo

  • Existem dois tipos de objetos, tipos de função e tipos de não função
  • Somente objetos do tipo Função podem criar um novo objeto usando o operador new . Os objetos assim criados são objetos do tipo Não Função . Os objetos do tipo Non Function não podem mais criar um objeto usando o operador new .

  • Todos os objetos do tipo Função, por padrão, têm uma propriedade "prototype" . Essa propriedade "prototype" faz referência a um objeto que possui uma propriedade "constructor" que, por padrão, faz referência ao próprio objeto do tipo Function .

  • Todos os objetos ( Tipo de função e Tipo não funcional ) têm uma propriedade "construtor" que, por padrão, faz referência ao objeto Tipo de função / Construtor que o criou.

  • Todo objeto criado internamente faz referência ao objeto referenciado pela propriedade "prototype" do Construtor que o criou. Este objeto é conhecido como protótipo do objeto criado (que é diferente da propriedade "prototype" dos objetos do tipo Função). Dessa maneira, o objeto criado pode acessar diretamente os métodos e propriedades definidos no objeto referenciado pela propriedade "prototype" do Constructor (no momento da criação do objeto).

  • O protótipo de um objeto (e, portanto, seus nomes de propriedades herdados) pode ser recuperado usando o método Object.getPrototypeOf () . De fato, esse método pode ser usado para navegar por toda a cadeia de protótipos do objeto.

  • A cadeia de protótipos de cada objeto é rastreada até Object.prototype (a menos que o objeto seja criado usando Object.create (null); nesse caso, o objeto não possui protótipo).

  • typeof (new Array ()) === 'objeto' é por design da linguagem e não é um erro, como apontado por Douglas Crockford

  • Definir a propriedade prototype do Constructor como null (ou indefinido, number, true, false, string) não deve criar um objeto com um protótipo nulo. Nesses casos, o protótipo do objeto recém-criado é definido como Object.prototype e seu construtor é definido para funcionar como Object.

Espero que isto ajude.


24

O conceito de prototypalherança é um dos mais complicados para muitos desenvolvedores. Vamos tentar entender a raiz do problema para entender prototypal inheritancemelhor. Vamos começar com uma plainfunção.

insira a descrição da imagem aqui

Se usarmos um newoperador no Tree function, chamamos isso de constructorfunção.

insira a descrição da imagem aqui

Toda JavaScriptfunção tem a prototype. Quando você faz logon Tree.prototype, você obtém ...

insira a descrição da imagem aqui

Se você olhar para a console.log()saída acima , poderá ver uma propriedade construtora Tree.prototypee também uma __proto__propriedade. O __proto__representa o fato de prototypeque isso functionse baseia e, como isso é apenas uma planície JavaScript functionsem inheritanceconfiguração ainda, refere-se ao Object prototypeque é algo que acabou de ser incorporado ao JavaScript ...

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/prototype

Isso tem coisas como .toString, .toValue, .hasOwnPropertyetc ...

__proto__que foi trazido meu mozilla está obsoleto e é substituído pelo Object.getPrototypeOfmétodo para obter o object's prototype.

insira a descrição da imagem aqui

Object.getPrototypeOf(Tree.prototype); // Object {} 

Vamos adicionar um método ao nosso Tree prototype.

insira a descrição da imagem aqui

Nós modificamos o Roote adicionamos um functionramo a ele.

insira a descrição da imagem aqui

Isso significa que quando você cria um instancede Tree, você pode chamá-lo de branchmétodo.

insira a descrição da imagem aqui

Também podemos adicionar primitivesou objectsao nosso Prototype.

insira a descrição da imagem aqui

Vamos adicionar um child-treeao nosso Tree.

insira a descrição da imagem aqui

Aqui o Childherda prototypeda árvore, o que estamos fazendo aqui é usar o Object.create()método para criar um novo objeto com base no que você passa, aqui está Tree.prototype. Nesse caso, o que estamos fazendo é definir o protótipo de Child para um novo objeto que pareça idêntico ao Treeprotótipo. Em seguida, estamos definindo o Child's constructor to Child, caso contrário, ele apontaria Tree().

insira a descrição da imagem aqui

Childagora tem a sua própria prototype, seus __proto__pontos para Treee Tree's prototypepontos para a base Object.

Child  
|
 \
  \
   Tree.prototype
   - branch
   |
   |
    \
     \
      Object.prototype
      -toString
      -valueOf
      -etc., etc.

Agora você cria uma chamada instancede Childe branchque está originalmente disponível em Tree. Na verdade, não definimos o nosso branchno Child prototype. MAS, no Root prototypequal o Filho herda.

insira a descrição da imagem aqui

No JS nem tudo é um objeto, tudo pode funcionar como um objeto.

Javascripttem primitivos como strings, number, booleans, undefined, null.eles não são object(i.e reference types), mas certamente podem agir como um object. Vamos ver um exemplo aqui.

insira a descrição da imagem aqui

Na primeira linha desta listagem, um primitivevalor de sequência é atribuído ao nome. A segunda linha trata o nome como objecte chama charAt(0)usando a notação de ponto.

É o que acontece nos bastidores: // o que o JavaScriptmecanismo faz

insira a descrição da imagem aqui

O String objectsó existe para uma declaração antes de ser destruído (um processo chamado autoboxing). Vamos voltar novamente ao nosso prototypal inheritance.

  • Javascriptsuporta herança via delegationbaseado em prototypes.
  • Cada Functionum tem uma prototypepropriedade, que se refere a outro objeto.
  • properties/functionssão olhados a partir de objectsi ou através da prototypecadeia, se não existir

A prototypeem JS é um objeto que yieldsvocê é o pai de outro object. [ie .. delegação] Delegation significa que se você não conseguir fazer algo, dirá a alguém para fazer isso por você.

insira a descrição da imagem aqui

https://jsfiddle.net/say0tzpL/1/

Se você procurar o violino acima, o cão tem acesso ao toStringmétodo, mas não está disponível nele, mas disponível através da cadeia de protótipos que delega paraObject.prototype

insira a descrição da imagem aqui

Se você olhar para o abaixo, estamos tentando acessar o callmétodo que está disponível em todos function.

insira a descrição da imagem aqui

https://jsfiddle.net/rknffckc/

Se você procurar o violino acima, o ProfileFunction tem acesso ao callmétodo, mas não está disponível nele, mas disponível através da cadeia de protótipos que delega paraFunction.prototype

insira a descrição da imagem aqui

Nota: prototype é uma propriedade do construtor da função, enquanto __proto__é uma propriedade dos objetos construídos a partir do construtor da função. Toda função vem com uma prototypepropriedade cujo valor é um vazio object. Quando criamos uma instância da função, obtemos uma propriedade interna [[Prototype]]ou __proto__cuja referência é o protótipo da função constructor.

insira a descrição da imagem aqui

O diagrama acima parece um pouco complicado, mas mostra toda a imagem de como prototype chainingfunciona. Vamos passar por isso lentamente:

Existem duas instâncias b1e b2, cujo construtor é Bare o pai é Foo e possui dois métodos da cadeia de protótipos identifye speakvia BareFoo

insira a descrição da imagem aqui

https://jsfiddle.net/kbp7jr7n/

Se você procurar o código acima, temos o Fooconstrutor que possui o método identify()e o Barconstrutor que possui o speakmétodo Criamos duas Barinstâncias b1e b2cujo tipo pai é Foo. Agora, enquanto chamamos o speakmétodo de Bar, somos capazes de identificar quem está chamando o speak através da prototypecadeia.

insira a descrição da imagem aqui

Baragora tem todos os métodos Foodefinidos em seu prototype. Vamos aprofundar na compreensão do Object.prototypee Function.prototypee como eles estão relacionados. Se você olhar para cima o construtor Foo, Bare Objectsão Function constructor.

insira a descrição da imagem aqui

O prototypede Baré Foo, prototypede Fooé Objecte se você observar de perto o prototypede Fooestá relacionado Object.prototype.

insira a descrição da imagem aqui

Antes de encerrarmos, vamos resumir com um pequeno pedaço de código aqui para resumir tudo acima . Estamos usando o instanceofoperador aqui para verificar se um objectpossui em sua prototypecadeia a prototypepropriedade de um constructorque, abaixo, resume todo o grande diagrama.

insira a descrição da imagem aqui

Espero que este complemento tenha alguma informação, eu sei que isso pode ser muito difícil de entender ... em palavras simples , são apenas objetos vinculados a objetos !!!!


22

qual é o objetivo exato dessa propriedade ".prototype"?

A interface para classes padrão se torna extensível. Por exemplo, você está usando a Arrayclasse e também precisa adicionar um serializador personalizado para todos os seus objetos de matriz. Você gastaria tempo codificando uma subclasse, ou usaria composição ou ... A propriedade prototype resolve isso permitindo que os usuários controlem o conjunto exato de membros / métodos disponíveis para uma classe.

Pense nos protótipos como um ponteiro vtable extra. Quando alguns membros estão ausentes da classe original, o protótipo é pesquisado em tempo de execução.


21

Pode ajudar a categorizar cadeias de protótipos em duas categorias.

Considere o construtor:

 function Person() {}

O valor de Object.getPrototypeOf(Person)é uma função. De fato é Function.prototype. Como Personfoi criado como uma função, ele compartilha o mesmo objeto de função de protótipo que todas as funções possuem. É o mesmo que Person.__proto__, mas essa propriedade não deve ser usada. De qualquer forma, Object.getPrototypeOf(Person)você efetivamente sobe a escada do que é chamado de cadeia de protótipos.

A corrente na direção ascendente é assim:

    PersonFunction.prototypeObject.prototype(ponto final)

Importante é que essa cadeia de protótipos tem pouco a ver com os objetos que Personpodem ser construídos . Esses objetos construídos têm sua própria cadeia de protótipos, e essa cadeia pode não ter um ancestral próximo em comum com o mencionado acima.

Tomemos, por exemplo, este objeto:

var p = new Person();

p não possui relação direta de protótipo-cadeia com Person . O relacionamento deles é diferente. O objeto p possui sua própria cadeia de protótipos. Usando Object.getPrototypeOf, você encontrará a cadeia da seguinte forma:

    pPerson.prototypeObject.prototype(ponto final)

Não há objeto de função nessa cadeia (embora possa ser).

Então Personparece relacionado a dois tipos de correntes, que vivem suas próprias vidas. Para "pular" de uma cadeia para outra, use:

  1. .prototype: pula da cadeia do construtor para a cadeia do objeto criado. Essa propriedade é, portanto, definida apenas para objetos de função (como newsó pode ser usada em funções).

  2. .constructor: pula da cadeia do objeto criado para a cadeia do construtor.

Aqui está uma apresentação visual das duas cadeias de protótipos envolvidas, representadas como colunas:

insira a descrição da imagem aqui

Para resumir:

A prototypepropriedade não fornece informações da cadeia de protótipos do sujeito , mas dos objetos criados pelo sujeito.

Não é surpresa que o nome da propriedade prototypepossa causar confusão. Talvez fosse mais claro se essa propriedade tivesse sido nomeada prototypeOfConstructedInstancesou algo assim.

Você pode ir e voltar entre as duas cadeias de protótipos:

Person.prototype.constructor === Person

Essa simetria pode ser quebrada atribuindo explicitamente um objeto diferente à prototypepropriedade (mais sobre isso posteriormente).

Crie uma função, obtenha dois objetos

Person.prototypeé um objeto que foi criado ao mesmo tempo em que a função Personfoi criada. Tem Personcomo construtor, mesmo que esse construtor ainda não tenha sido executado. Portanto, dois objetos são criados ao mesmo tempo:

  1. A Personprópria função
  2. O objeto que atuará como protótipo quando a função for chamada como construtor

Ambos são objetos, mas têm papéis diferentes: o objeto de função é construído , enquanto o outro objeto representa o protótipo de qualquer objeto que a função irá construir. O objeto protótipo se tornará o pai do objeto construído em sua cadeia de protótipos.

Como uma função também é um objeto, ela também tem seu próprio pai em sua própria cadeia de protótipos, mas lembre-se de que essas duas cadeias são sobre coisas diferentes.

Aqui estão algumas igualdades que podem ajudar a entender o problema - todas elas impressas true:

function Person() {};

// This is prototype chain info for the constructor (the function object):
console.log(Object.getPrototypeOf(Person) === Function.prototype);
// Step further up in the same hierarchy:
console.log(Object.getPrototypeOf(Function.prototype) === Object.prototype);
console.log(Object.getPrototypeOf(Object.prototype) === null);
console.log(Person.__proto__ === Function.prototype);
// Here we swap lanes, and look at the constructor of the constructor
console.log(Person.constructor === Function);
console.log(Person instanceof Function);

// Person.prototype was created by Person (at the time of its creation)
// Here we swap lanes back and forth:
console.log(Person.prototype.constructor === Person);
// Although it is not an instance of it:
console.log(!(Person.prototype instanceof Person));
// Instances are objects created by the constructor:
var p = new Person();
// Similarly to what was shown for the constructor, here we have
// the same for the object created by the constructor:
console.log(Object.getPrototypeOf(p) === Person.prototype);
console.log(p.__proto__ === Person.prototype);
// Here we swap lanes, and look at the constructor
console.log(p.constructor === Person);
console.log(p instanceof Person);

Adicionando níveis à cadeia de protótipos

Embora um objeto protótipo seja criado quando você cria uma função de construtor, você pode ignorar esse objeto e atribuir outro objeto que deve ser usado como protótipo para quaisquer instâncias subseqüentes criadas por esse construtor.

Por exemplo:

function Thief() { }
var p = new Person();
Thief.prototype = p; // this determines the prototype for any new Thief objects:
var t = new Thief();

Agora, a cadeia de protótipos de t é um passo mais longa que a de p :

    tpPerson.prototypeObject.prototype(ponto final)

A outra cadeia de protótipos não é mais: Thiefe Personos irmãos compartilham o mesmo pai em sua cadeia de protótipos:

    Person}
    Thief  } → Function.prototypeObject.prototype(ponto final)

O gráfico apresentado anteriormente pode ser estendido para isso (o original Thief.prototypeé deixado de fora):

insira a descrição da imagem aqui

As linhas azuis representam cadeias de protótipos, as outras linhas coloridas representam outros relacionamentos:

  • entre um objeto e seu construtor
  • entre um construtor e o objeto de protótipo que será usado para construir objetos


16

Achei útil explicar a "cadeia de protótipos" como convenção recursiva quando obj_n.prop_Xestá sendo referenciada:

se obj_n.prop_Xnão existir, verifique obj_n+1.prop_Xondeobj_n+1 = obj_n.[[prototype]]

Se prop_Xfinalmente for encontrado no objeto protótipo k-ésimo

obj_1.prop_X = obj_1.[[prototype]].[[prototype]]..(k-times)..[[prototype]].prop_X

Você pode encontrar um gráfico da relação dos objetos Javascript por suas propriedades aqui:

gráfico de objetos js

http://jsobjects.org


14

Quando um construtor cria um objeto, esse objeto faz referência implícita à propriedade "prototype" do construtor com a finalidade de resolver as referências de propriedade. A propriedade “prototype” do construtor pode ser referenciada pela expressão do programa constructor.prototype, e as propriedades adicionadas ao protótipo de um objeto são compartilhadas, por herança, por todos os objetos que compartilham o protótipo.


11

Há duas entidades distintas, mas relacionadas, que precisam ser explicadas:

  • A .prototypepropriedade das funções.
  • A propriedade [[Prototype]][1] de todos os objetos [2] .

Estas são duas coisas diferentes.

A [[Prototype]]propriedade:

Esta é uma propriedade que existe em todos os [2] objetos.

O que é armazenado aqui é outro objeto que, como um objeto em si, possui um objeto [[Prototype]]próprio que aponta para outro objeto. Esse outro objeto tem um [[Prototype]]próprio. Esta história continua até você alcançar o objeto prototípico que fornece métodos acessíveis em todos os objetos (como .toString).

A [[Prototype]]propriedade faz parte do que forma a [[Prototype]]cadeia. Essa cadeia de [[Prototype]]objetos é o que é examinado quando, por exemplo, [[Get]]ou [[Set]]operações são executadas em um objeto:

var obj = {}
obj.a         // [[Get]] consults prototype chain
obj.b = 20    // [[Set]] consults prototype chain

A .prototypepropriedade:

Esta é uma propriedade encontrada apenas em funções. Usando uma função muito simples:

function Bar(){};

A .prototypepropriedade contém um objeto que será atribuído b.[[Prototype]]quando você o fizer var b = new Bar. Você pode facilmente examinar isso:

// Both assign Bar.prototype to b1/b2[[Prototype]]
var b = new Bar;
// Object.getPrototypeOf grabs the objects [[Prototype]]
console.log(Object.getPrototypeOf(b) === Bar.prototype) // true

Um dos .prototypes mais importantes é o da Objectfunção . Este protótipo contém o objeto prototípico que todas as [[Prototype]]cadeias contêm. Nele, todos os métodos disponíveis para novos objetos são definidos:

// Get properties that are defined on this object
console.log(Object.getOwnPropertyDescriptors(Object.prototype))

Agora, como .prototypeé um objeto, ele tem uma [[Prototype]]propriedade. Quando você não fazer quaisquer atribuições para Function.prototypeo .prototype's [[Prototype]]pontos ao objeto protótipo ( Object.prototype). Isso é realizado automaticamente sempre que você cria uma nova função.

Dessa forma, sempre que você fizer new Bar;a cadeia de protótipos, você terá tudo definido Bar.prototypee definido Object.prototype:

var b = new Bar;
// Get all Bar.prototype properties
console.log(b.__proto__ === Bar.prototype)
// Get all Object.prototype properties
console.log(b.__proto__.__proto__ === Object.prototype)

Quando você fazer atribuições fazer para Function.prototypetudo o que você está fazendo é estender a cadeia de protótipos para incluir outro objeto. É como uma inserção em uma lista vinculada individualmente.

Isso basicamente altera a [[Prototype]]cadeia, permitindo que as propriedades definidas no objeto atribuído Function.prototypesejam vistas por qualquer objeto criado pela função.


[1: Isso não vai confundir ninguém; disponibilizado através da __proto__propriedade em muitas implementações.
[2]: Todos, exceto null.


10

Deixe-me contar minha compreensão dos protótipos. Não vou comparar a herança aqui com outras línguas. Eu gostaria que as pessoas parassem de comparar idiomas e apenas entendessem o idioma como ele mesmo. Entender os protótipos e a herança prototípica é tão simples, como mostrarei a seguir.

O protótipo é como um modelo, com base no qual você cria um produto. O ponto crucial a entender é que, quando você cria um objeto usando outro objeto como protótipo, o vínculo entre o protótipo e o produto é permanente. Por exemplo:

var model = {x:2};
var product = Object.create(model);
model.y = 5;
product.y
=>5

Todo objeto contém uma propriedade interna chamada [[protótipo]], que pode ser acessada pela Object.getPrototypeOf()função. Object.create(model)cria um novo objeto e define sua propriedade [[prototype]] para o modelo de objeto . Portanto, quando o fizer Object.getPrototypeOf(product), você obterá o modelo de objeto .

As propriedades do produto são tratadas da seguinte maneira:

  • Quando uma propriedade é acessada para apenas ler seu valor, ela é pesquisada na cadeia de escopo. A busca pela variável começa do produto para o protótipo. Se uma variável desse tipo for encontrada na pesquisa, a pesquisa será interrompida e o valor retornado. Se essa variável não puder ser encontrada na cadeia de escopo, undefined será retornado.
  • Quando uma propriedade é gravada (alterada), ela é sempre gravada no objeto do produto . Se o produto ainda não tiver essa propriedade, ele será criado e gravado implicitamente.

Essa vinculação de objetos usando a propriedade prototype é chamada de herança prototípica. Lá, é tão simples, concorda?


Nem sempre escrito no produto em serviço. Você não está deixando muito claro que membros específicos da instância precisam ser inicializados e membros compartilhados podem usar o protótipo. Especialmente quando você possui membros mutáveis ​​específicos da instância: stackoverflow.com/questions/16063394/…
HMR

HMR: No seu exemplo na sua resposta, o ben.food.push ("Hamburger"); A linha altera a propriedade do objeto de protótipo devido ao seguinte: 1.) Primeiro o ben.food é pesquisado e qualquer ação de pesquisa simplesmente pesquisa a cadeia de escopo. 2.) A função push desse objeto ben.food é executada. Ao escrever o modo na minha resposta, quero dizer quando você define explicitamente um valor para ele, como em: ben.food = ['Idly']; Isso sempre criará uma nova propriedade (se ainda não estiver lá) no objeto do produto e, em seguida, atribuirá o valor a ele.
Aravind

HMR: Obrigado pelo seu comentário, isso me fez pensar e testar minha compreensão.
Aravind

Ao atribuir o ben.food, ele sombreará o membro do alimento, a menos que o alimento seja criado usando Object.defineProperty, Object.defineProperties ou Object.create com o segundo argumento (nem sempre). Você pode até alterar o protótipo com (o que parece) uma nova atribuição ao criar um setter getter. Quando se trata de padrões de herança, entendo que a função construtora é difícil de entender e tem alguns problemas importantes, mas é bom se você a entender. A herança em JavaScript não começa e termina com a configuração de um protótipo, as inicializações (construtores) também devem ser (re) usadas.
HMR

Sua resposta é boa para explicar o protótipo, mas pode ser mal interpretada por simplificar a herança em JavaScript e em membros específicos da instância. Muitas perguntas foram feitas por que a mutação de um membro do protótipo em uma instância afeta outras instâncias.
HMR


10

Considere o seguinte keyValueStoreobjeto:

var keyValueStore = (function() {
    var count = 0;
    var kvs = function() {
        count++;
        this.data = {};
        this.get = function(key) { return this.data[key]; };
        this.set = function(key, value) { this.data[key] = value; };
        this.delete = function(key) { delete this.data[key]; };
        this.getLength = function() {
            var l = 0;
            for (p in this.data) l++;
            return l;
        }
    };

    return  { // Singleton public properties
        'create' : function() { return new kvs(); },
        'count' : function() { return count; }
    };
})();

Eu posso criar uma nova instância deste objeto fazendo o seguinte:

kvs = keyValueStore.create();

Cada instância deste objeto teria as seguintes propriedades públicas:

  • data
  • get
  • set
  • delete
  • getLength

Agora, suponha que criamos 100 instâncias desse keyValueStoreobjeto. Mesmo que get, set, delete, getLengthvai fazer exatamente a mesma coisa para cada um desses 100 casos, cada instância tem a sua própria cópia desta função.

Agora, imagine se você pudesse ter apenas um único get, set, deletee getLengthcópia, e cada instância faria referência essa mesma função. Isso seria melhor para o desempenho e exigiria menos memória.

É aí que entram os protótipos. Um protótipo é um "blueprint" de propriedades que são herdadas, mas não copiadas por instâncias. Portanto, isso significa que ele existe apenas uma vez na memória para todas as instâncias de um objeto e é compartilhado por todas essas instâncias.

Agora, considere o keyValueStoreobjeto novamente. Eu poderia reescrevê-lo assim:

var keyValueStore = (function() {
    var count = 0;
    var kvs = function() {
        count++;
        this.data = {};
    };

    kvs.prototype = {
        'get' : function(key) { return this.data[key]; },
        'set' : function(key, value) { this.data[key] = value; },
        'delete' : function(key) { delete this.data[key]; },
        'getLength' : function() {
            var l = 0;
            for (p in this.data) l++;
            return l;
        }
    };

    return  {
        'create' : function() { return new kvs(); },
        'count' : function() { return count; }
    };
})();

Isso é EXATAMENTE o mesmo que a versão anterior do keyValueStoreobjeto, exceto que todos os seus métodos agora estão em um protótipo. O que isso significa é que todas as 100 instâncias agora compartilham esses quatro métodos, em vez de cada um ter sua própria cópia.


9

Resumo:

  • Funções são objetos em javascript e, portanto, podem ter propriedades
  • As funções (construtor) sempre têm uma propriedade prototype
  • Quando uma função é usada como construtor com a newpalavra-chave, o objeto recebe protótipo. Uma referência a este protótipo pode ser encontrada na __proto__propriedade do objeto recém-criado.
  • Esta __proto__propriedade refere-se à prototypepropriedade da função construtora.

Exemplo:

function Person (name) {
  this.name = name;
}

let me = new Person('willem');

console.log(Person.prototype) // Person has a prototype property

console.log(Person.prototype === me.__proto__) // the __proto__ property of the instance refers to prototype property of the function.

Por que isso é útil:

Javascript tem um mecanismo ao procurar propriedades em objetos que é chamado de 'herança prototípica' , aqui está o que basicamente faz:

  • Primeiro é verificado se a propriedade está localizada no próprio objeto. Nesse caso, essa propriedade é retornada.
  • Se a propriedade não estiver localizada no próprio objeto, ele 'subirá o protochain'. Basicamente, olha para o objeto referido pela propriedade proto . Lá, ele verifica se a propriedade está disponível no objeto referido por proto
  • Se a propriedade não estiver localizada no objeto proto, ela subirá a cadeia proto até o objeto Objeto.
  • Se não conseguir encontrar a propriedade em nenhum lugar do objeto e sua cadeia de protótipos, ele retornará indefinido.

Por exemplo:

function Person(name) {
  this.name = name;
}

let mySelf = new Person('Willem');

console.log(mySelf.__proto__ === Person.prototype);

console.log(mySelf.__proto__.__proto__ === Object.prototype);

Atualizar:

A __proto__propriedade foi descontinuada, embora seja implementada na maioria dos navegadores modernos, uma maneira melhor de obter a referência do objeto prototype seria:

Object.getPrototypeOf()


7

Eu sempre gosto de analogias quando se trata de entender esse tipo de coisa. 'Herança prototípica' é bastante confusa em comparação com a herança de graves de classe na minha opinião, mesmo que os protótipos sejam um paradigma muito mais simples. De fato, com os protótipos, realmente não há herança; portanto, o nome em si é enganoso, é mais um tipo de 'delegação'.

Imagina isto ....

Você está no ensino médio, está na sala de aula e tem um questionário que deve ser entregue hoje, mas não tem uma caneta para preencher suas respostas. Doh!

Você está sentado ao lado de seu amigo Finnius, que pode ter uma caneta. Você pergunta, e ele olha em volta da mesa, sem sucesso, mas em vez de dizer "eu não tenho uma caneta", ele é um bom amigo. Derp de fato tem uma caneta sobressalente e a devolve a Finnius, que a entrega a você para concluir seu teste. Derp confiou a caneta a Finnius, que delegou a caneta a você para uso.

O importante aqui é que Derp não lhe dá a caneta, pois você não tem um relacionamento direto com ele.

Este é um exemplo simplificado de como os protótipos funcionam, onde uma árvore de dados é pesquisada para o que você está procurando.


3

outro esquema mostrando __proto__ , relações de protótipo e construtor : insira a descrição da imagem aqui


1

É que você já possui um objeto, Object.newmas ainda não o possui ao usar a sintaxe do construtor.


1

É importante entender que existe uma distinção entre o protótipo de um objeto (que está disponível via Object.getPrototypeOf(obj)ou através da __proto__propriedade reprovada ) e a prototypepropriedade nas funções do construtor. O primeiro é a propriedade em cada instância, e o último é a propriedade no construtor. Ou seja, Object.getPrototypeOf(new Foobar())refere-se ao mesmo objeto que Foobar.prototype.

Referência: https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Object_prototypes


0

O Prototype cria um novo objeto ao clonar o objeto existente . Então, quando pensamos em protótipo, podemos pensar em clonar ou fazer uma cópia de algo em vez de inventar.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.