
Esta é uma história sobre um pedaço muito especial de JavaScript, a linguagem artificial
mais usada no mundo hoje (2019).
O artigo apresenta uma espécie de visão filosófica da herança em JavaScript, e ouso apenas esperar que seja baseada na fonte mais impressionante de conhecimento: a própria vida em todas as suas manifestações. Não sei se isso foi uma inspiração ao criar o design da cadeia de protótipos em JavaScript.
Mas se assim for, é tão significativo e poderoso que, quando começo a pensar nisso, às vezes fica difícil respirar ...
(todos os links estão sublinhados )
Também tenho certeza de que nenhum de nós duvidará que
Brendan Ike (Eich) - o autor da linguagem de programação JavaScript - seja um gênio extraordinário! E não apenas porque ele costuma repetir:
Sempre aposte no JavaScript!
Vamos começar! E nosso primeiro ponto de partida será a Imaginação, na qual desligamos todos os preconceitos, omissões e outros efeitos colaterais.
Estamos voltando
ao futuro , a era que precedeu a criação da Internet moderna no início dos anos 90.
Desde os
primeiros hackers que inventaram tudo o que nós (
funcionários de TI ) estamos
usando agora, passamos a um esboço impressionante: sobre a guerra entre os navegadores
Netstcape Navigator 2 e
Internet Explorer 3. O
Java acabou de sair e quase tudo na Internet moderna ainda não foi inventado ou não pré-aberto. É possível que, como eu, naqueles "bons velhos tempos" você fosse jovem e se lembrasse desse maravilhoso sentimento de envolvimento em todo o esplendor que está sendo criado diante de seus olhos.
Portanto, você tem um
PC muito poderoso no mais moderno
Intell Pentium 200 MMX com 32Mb de RAM, Windows 3.11 ou mesmo Windows 95 e está olhando para o futuro com esperança! E, é claro, os dois
navegadores também
estão instalados. Você tem uma conexão
dial-up , através da qual você se conecta à rede para obter novos itens indesejados também, para estudar ou apenas conversar, conversar. Embora, espere um pouco, você ainda não pode conversar diretamente no navegador, provavelmente está usando sistemas de entrega de mensagens atrasadas, algo como
EMail ou talvez
UseNet , ou, possivelmente, você já domina a entrega instantânea via
IRC .
Alguns anos se passam e literalmente TUDO muda ... De repente, você assiste à animação de flocos de neve nas páginas da web parabenizando você no Ano Novo e no Natal. Obviamente, você está interessado em como isso é feito e descobre um novo idioma - JavaScript. Como o
HTML não é novo para você, você está começando a aprender esse artesanato tecnológico atraente. Logo você descobre o
CSS e verifica-se que isso também é importante, pois tudo agora é feito a partir de uma combinação desses três: HTML, JavaSript e CSS. Uau.
Na mesma época, você pode notar algumas coisas excelentes no próprio Windows, o
CScript e o
HTA apareceram nele e, mesmo assim, tornou-se possível criar aplicativos de desktop completos diretamente no JS (e isso ainda funciona).
E assim você começou a criar seu primeiro servidor Web, possivelmente em
Perl ou
C ~
C ++ . Talvez até você tenha começado a usar
sistemas operacionais semelhantes ao Unix e o faça no
bash . E tudo isso "girando girando" graças à
Common Gateway Interface (não confunda com esse outro
CGI ).
O PHP ainda quase não existe, mas talvez você goste em breve.
Era 200x. Agora você está fazendo
ASP em
JScript . Isso é muito semelhante ao JavaScript que funciona dentro de suas páginas da web. Isso é tão legal! Você está pensando em criar seu próprio
mecanismo de modelo , uma espécie de paródia de
XML . E então alguém chama repentinamente o
AJAX de todas essas maneiras divertidas de carregar
dinamicamente o conteúdo que você usa há vários anos. E todos eles agora pensam que existe apenas
XMLHTTPRequest , mas você lembra que os dados podem ser transferidos para
BMP ,
IFrame ou mesmo inserindo a
tag <script> . E, de repente, alguém fala com entusiasmo sobre
JSON e como é útil quando você conduz dados por uma
eternidade com algo assim:
document.write("<" + "script src=" + path + ">");
Não é tudo o que importa
agora , mas você ainda se lembra
como ...
Quando você volta a si, de tempos em tempos, começa a se intrometer com
Rhino ou
Nashorn na tentativa de satisfazer os desejos dos clientes de Java que usam
Alfresco ou
Asterisk . E você já ouviu falar sobre o iminente advento do JavaScript no mundo dos microchips e está muito inspirado por essas notícias. E, claro, agora você tem
jQuery e
Backbone .
Observando a queda de neve do próximo ano de 2010, você já sabe que em seu mundo todas as regras do jogo mudarão em breve, à medida que o "Jogador nº 1" entrar em campo:
Node.js. E nos próximos 10 anos você passará com este novo brinquedo e, mesmo agora, em 2019, ainda não se cansará do quão legal é.
Em geral, você é feliz com tudo, tudo combina com você, todos esses brinquedos e jogos constituem uma grande parte dos interesses da sua vida.
Mas há uma pequena pergunta que você se faz dia após dia, noite após noite, há duas décadas:Se você tivesse que fazer isso, como explicaria o Empathy usando JavaScript?
Você sabe que um dos tópicos mais complicados do JavaScript é a
herança de protótipo e a cadeia de protótipos . E você ama este tópico, pode explicar como tudo funciona e funciona, simplesmente porque você o aprendeu quase desde o início, mesmo antes do nascimento da
primeira versão do Padrão , e onde, como você se lembra, existem
4,2 .1 Objetos :
O ECMAScript suporta herança baseada em protótipo. Todo construtor tem um protótipo associado e todo objeto criado por esse construtor tem uma referência implícita ao protótipo (chamado protótipo do objeto) associado ao seu construtor. Além disso, um protótipo pode ter uma referência implícita não nula ao seu protótipo, e assim por diante; isso é chamado de cadeia de protótipo .
Cada objeto é criado com uma referência implícita ao protótipo. Isso é chamado de cadeia de herança do protótipo, que você pode continuar pelo tempo que quiser.Uau ... E se de repente, como eu, você pode estar pensando que essa é uma das maiores invenções da
Ciência dos Computadores , como você expressaria o efeito que a leitura desta declaração teve sobre você?
Vamos voltar ao começo. No quintal de 1995. Você é Brendan Ike e
precisa inventar uma nova linguagem de programação. Talvez você goste de
Lisp ou
Scheme , pelo menos algumas de suas partes favoritas. E você se depara com a necessidade de resolver o problema da
herança , já que você deve fingir que a linguagem possui uma certa implementação do
OOP .
Vamos pensar : você precisa misturar todas as coisas que você gosta, talvez também algumas que não gosta, e o coquetel resultante deve ser bom o suficiente para que ninguém perceba um truque até que haja uma necessidade real de entender como organizado dentro.
E agora a pergunta é:
o que aconteceria com a herança?Vamos voltar à realidade por um momento. O que todos nós sabemos sobre herança? Algumas respostas óbvias a esta pergunta:
- A maioria das formas de vida é baseada no genoma . É um repositório de informações sobre as características prováveis e o comportamento alegado dos seres vivos. Quando você é uma criatura viva, carrega uma parte do genoma dentro de você, pode distribuí-lo, mas o recebeu de gerações anteriores.
- Você pode criar uma criatura viva usando duas técnicas: mistura (genomas) de dois ancestrais ou, possivelmente, usar a clonagem monóica de um deles. É claro que hoje somos tecnologias disponíveis que permitem misturar os genomas de mais de uma criatura, mas isso é muito menos óbvio e não tão natural.
- O fator tempo é importante. Se não houver propriedade herdada, ou ainda não, nossa única saída é criá-la do zero. Além disso, há também o Legado, que passa para o ser a partir de seus ancestrais, não através do genoma, mas através das leis da propriedade, e isso também pode ser significativo.
Voltando ao passado, e a pergunta certa que precisamos nos perguntar é agora:
E, de fato, herança O que queremos obter?Bem, em qualquer caso, no processo de resolver o problema da herança, precisamos pelo menos preencher a lacuna entre a programação e a vida real; caso contrário, em geral, não teremos o direito de chamá-lo de Herança.
Agora vamos terminar: estamos na década de 1995 e temos o PC mais poderoso com apenas 32 megabytes de RAM, e estamos tentando criar uma linguagem interpretada, por isso precisamos cuidar muito dessa memória. Cada pedaço de dados, especialmente
objetos String , consome muita memória, e devemos poder declarar esse pedaço apenas uma vez e, sempre que possível no futuro, sempre use apenas ponteiros para a área de memória ocupada por esse pedaço.
Resumimos: uma pergunta muito difícil.Existe uma opinião popular: "
JavaScript é criado a partir de objetos ". Usando essa trivialidade, podemos facilmente responder à pergunta “
do que ” herdar e “o
que ”: de objetos para objetos. Por uma questão de economia de memória, verifica-se que todos os dados devem ser armazenados nesses objetos e todos esses links de dados também devem ser armazenados nas propriedades Herdadas desses objetos. Talvez agora esteja claro por que, em 1995, realmente precisávamos criar um Design baseado em uma cadeia de protótipos: economiza memória o maior tempo possível! E, em geral, acho que esse ainda pode ser um aspecto muito importante.
Com base no design indicado e na opinião "
tudo é um objeto ", podemos tentar clonar algo assim. Mas o que está clonando aqui agora? Penso que, seguindo os requisitos de nossos requisitos, podemos assumir algo como
Cópia Estrutural ou de Superfície , algo semelhante aos antecessores do
Object.assign moderno.
Vamos implementar uma cópia estrutural simples em 1995 usando
for (var i in) {} , pois o padrão
já permitiu isso :
Como você pode ver, essa abordagem ainda “funciona”, embora, em geral, é claro, eu recomendo examinar o módulo de
extensão profunda para uma compreensão mais detalhada de como fazer a clonagem em JavaScript, mas, para os fins do artigo, um aplicativo consistente é bastante adequado para nós. descrito por
cloneProps , porque poderíamos usá-lo naqueles tempos antigos:
- clonagem de objetos usando o construtor: usando o construtor, crie pelo menos dois clones diferentes
- clonando um construtor de um construtor: implementamos o uso do comportamento de um construtor de outro construtor
var SomeConstructor = function () { this.a = 'cloned'; }; var AnotherConstructor = function () {
- clonando um construtor usando um objeto: usaremos o mesmo objeto para implementar a clonagem em pelo menos dois construtores
var existentObject = { foo : 'bar' }; var SomeConstructor = function () { cloneProps(foo, this); }; var OtherConstructor = function () { cloneProps(foo, this); };
- clonando um objeto de outro objeto: use um objeto para criar vários de seus clones . Não há nada para descrever aqui, apenas como nosso cloneProps do primeiro exemplo acima.
Com a clonagem, em geral, tudo é simples, como vemos, tudo é claro e em geral, mas ...É tão fácil para nós fazer a Herança de Entidades, usando uma combinação de seus predecessores?
- Herança de um objeto usando o Construtor: esse é o próprio objetivo dos designers, mostraremos apenas como ele foi originalmente projetado .
var existentObject = { foo : 'bar' }; var SomeConstructor = function () {}; SomeConstructor.prototype = existentObject; var inheritedObject = new SomeConstructor();
- Herança do Construtor de outro Construtor: sem dúvida, o primeiro que percebeu isso foi um excelente gênio. Em suma, este é outro exemplo clássico de qualquer lugar .
var FirstConstructor = function () { this.foo = 'bar'; }; var InheritedConstructor = function () { FirstConstructor.call(this); }; InheritedConstructor.prototype = { bar : 'foo' }; InheritedConstructor.prototype.constructor = FirstConstructor; var inherited = new InheritedConstructor();
seria possível afirmar algo sofisticado, mas por que
- Herança do Construtor do Objeto: novamente, usamos apenas .prototype = object toda vez que precisamos, não há nada para descrever, sempre precisamos atribuir o Constructor.prototype, pois ele deve colocar o objeto lá e, por um link implícito, obtemos todas as suas propriedades .
- Herança de um objeto de um objeto: a mesma coisa. Simplesmente colocamos o primeiro objeto no Constructor.prototype e, assim que dissermos o novo Constructor , criaremos uma cópia herdada na qual haverá referências implícitas às propriedades do nosso primeiro objeto.
E, é claro, em todas essas situações com herança, teremos a oportunidade de verificar usando a
instância de qual construtor criamos os objetos, embora, é claro, deva-se notar que a
instância de si mesma apareceu nos padrões quase quatro anos depois.
É verdade que permaneceu um detalhe tão pequeno do ponto 4.2.1:
ser capaz de fazer isso pelo tempo que for necessário, como diz:
e assim por diante
Bem,
vamos tentar tornar a herança verdadeiramente infinita , usando a tecnologia
de 1995 .
De fato, vamos imaginar que temos duas entidades, e
não construtores, mas objetos simples. E queríamos herdar um do outro, e então talvez do outro, e do outro, e assim por diante ...
Mas como
Dê uma olhada um pouco mais, mais fundo.
A resposta correta aqui novamente é:
Herança do que precisamos criar?Afinal, não precisamos dessas entidades por conta própria. Precisamos de suas propriedades: dados associados que consomem memória; e também, provavelmente precisamos de algum comportamento: métodos usando esses dados. E será igualmente
honesto se tivermos a oportunidade de verificar onde e onde herdamos e usando o quê. Em geral, seria tão legal se pudéssemos reproduzir o design inerente dos padrões de herança no futuro, implicando que, se herdarmos um do outro muitas vezes, sempre obteremos o mesmo resultado, de acordo com o que escrevemos (contrato) . Embora ainda seja, pode ser igualmente útil para nós, de alguma forma, fixar o momento da criação, já que nossas entidades "anteriores" podem mudar com o tempo e os "herdeiros", respeitando-os nisso, ainda mudam com eles. pode muito bem não querer.
E, como todo o nosso código é uma combinação de dados e comportamento, será geralmente normal usar o mesmo método - combinando dados e apresentação - ao projetar um sistema de herança?
Quanto a mim, tudo isso se assemelha ao que vemos quando observamos a Vida em todas as suas formas incríveis. Do primeiro unicelular, ao multicelular e seus descendentes, e ainda aos Animais, pessoas, humanismo e tribos, civilizações, Intelecto e suas formas artificiais, e ainda ao Espaço, à Galáxia, às Estrelas! e:
"... Tudo o que precisamos fazer é garantir que continuemos conversando ..."
(tudo o que precisamos fazer é continuar a comunicação)Incrível em sua consideração, uma citação de
Stephen Hawking , posteriormente popularizada
nesta obra-prima de
Pind Floyd .
As linguagens de programação baseadas na
passagem de mensagens e um conceito
baseado em fluxo implementado por meio de uma API interna robusta permitem que você passe de
dados simples para abstrações mais altas, descrição e tudo mais. Eu acho que isso é pura arte e como ele funciona em particular em estruturas JavaScript profundamente ocultas por meio de relacionamentos implícitos entre dados em cadeias de protótipos.
Imagine novamente dois ancestrais, eles se comunicam e, em um momento, suas emoções e sentimentos criam um filho. A criança cresce, conhece outra criança, eles se comunicam e o próximo descendente aparece, e assim por diante, e assim por diante ... E sempre precisamos de Dois pais, caso contrário não é natural, já será engenharia genética. Dois, nem mais nem menos. Um descendente recebe o mesmo que seu legado, por isso é simples e compreensível.
Entendo que parecerá estranho, mas sim, temos tudo o que precisamos para criar esse modelo de herança em 1995. E a base de tudo isso é precisamente
4.2.1 Objetos , referência implícita através de protótipo.
E é exatamente isso, combinando
ParentObject com
ParentConstructor , especificando
.prototype e, em seguida, o
Constructor provavelmente nos criará um
ChildObject , é claro, se dissermos a palavra mágica "
new ":
var ParentObject = { foo : 'bar' }; var ParentConstructor = function () {}; ParentConstructor.prototype = ParentObject; var ChildObject = new ParentConstructor();
Podemos discernir aqui os dois ancestrais. No momento em que dissemos a palavra mágica "
novo ", pedimos que eles conversassem. Se eles não quiserem se comunicar, o Life irá parar, o processo cairá com um erro e o
compilador (intérprete) nos falará sobre isso.
Claro que sim, mas pedimos a
Árvore da
Herança ou, obviamente, é muito mais simples, pelo menos para a
Árvore Genealógica. E a resposta ainda é a mesma ... nosso
Objeto Filho cresce e se
torna um Objeto Pai , depois encontra um novo
Objeto Construtor e assim que dizemos a cobiçada palavra "
novo " - mágica:
E podemos continuar fazendo isso ad infinitum. E talvez eu realmente acredite que essa foi a idéia principal ao desenvolver o design da Cadeia de Protótipos, porque, como todos sabemos, essa abordagem cria alguns problemas
muito puros, mas não menos desagradáveis ...
1: Comunidade ... Como você pode verificar facilmente, especificar no
.prototype ParentConstructor ' a ou
AnotherConstructor' a é um
Contrato Social muito sério e estrito em nossa tribo. Ele cria uma referência às propriedades
ParentObject (
.foo ) dos Heirs:
ChildObject e
SequentialChildObject . E se nos livrarmos dessa indicação, esses links desaparecerão. Se planejarmos e reatribuirmos essa referência a outro objeto, nossos herdeiros herdarão instantaneamente outras propriedades. Portanto, combinando ancestrais por meio do
protótipo . Provavelmente poderíamos dizer que estamos criando algum tipo de
célula da sociedade , porque esses "ancestrais" podem reproduzir muitos descendentes idênticos toda vez que perguntamos a eles sobre o uso de
novos . E assim, tendo destruído a "família", estamos arruinando as qualidades hereditárias de seus descendentes, um drama como esse; ^)
Talvez isso seja tudo sobre
legado em nosso código; devemos cuidar disso quando criarmos um código
seguro e
suportado ! É claro que nenhum
princípio do SOLID ,
Liskov e
Design by Contract e
GRASP foi discutido em 1995, mas é óbvio que essas metodologias não foram criadas "do zero", tudo começou muito antes.
2: Família ... Podemos facilmente verificar que nosso
ParentObject pode ser muito frívolo com outros Constructos. Isso não é justo, mas podemos usar quantos Construtores quisermos na herança de nosso ParentObject e, assim, criar quantas famílias quisermos. Por outro lado, cada Construtor está intimamente associado ao ParentObject por meio da tarefa .prototype, e se não desejamos prejudicar nossos herdeiros, devemos manter essa conexão pelo maior tempo possível. Poderíamos chamá-lo de arte da tragédia na história de nossa tribo. Embora, é claro, isso nos proteja da amnésia - o esquecimento do que herdamos e de quem, e por que nossos herdeiros recebem esse
legado . E, elogiando o grande
Mnemosine !, Podemos realmente testar facilmente nossa
Árvore de Cadeias de Protótipos e encontrar
Artefatos do que fizemos de errado.
3: Velhice ... Nosso
ParentObject e
Constructor certamente
são suscetíveis a danos durante o ciclo de vida do nosso programa. Podemos tentar cuidar disso, mas ninguém está a salvo de erros. E todas essas mudanças podem prejudicar os herdeiros. Temos que cuidar de
vazamentos de
memória . Obviamente, podemos muito bem destruir partes desnecessárias do código em tempo de execução e, assim, liberar memória que não é mais usada em nosso
Ciclo de Vida . Além disso, devemos nos livrar de todas as possibilidades de criar
paradoxos temporários nas cadeias de protótipos, pois, de fato, é bastante simples herdar o ancestral de seu próprio descendente. Mas isso já pode ser muito perigoso, uma vez que essas técnicas de flertar com o passado do futuro podem dificultar a reprodução de montes inteiros de
Heisenbags , principalmente se tentarmos medir algo que pode mudar ao longo do tempo.
Crônica de Decisões
Seja simples, óbvio e não muito útil, mas em vez de pensar em nosso Constructor e ParentObject como mamãe e papai, vamos descrevê-los como um
ovo e ...
pólen . Então, no futuro, quando criarmos o
zigoto usando a palavra estimada "
novo ", ele não fará mais mal à nossa imaginação!
No momento em que fizermos isso, imediatamente nos livraremos dos três problemas acima! Obviamente, para isso, precisamos da capacidade de criar os zigotos, o que significa que precisamos da Fábrica de Designers. E agora chame como quiser, mães, pais, qual é a diferença, porque o essencial é que, se quisermos dizer "
novo ", devemos criar uma gaiola de designer de flores "nova", colocar pólen nela, e somente isso nos permitirá para cultivar um novo
Snowdrop “certo” nos distantes e nevados 2020m:
var Pollen = { season : 'Spring' };
(e sim, não se esqueça de verificar a estação com este floco de neve, caso contrário, os flocos de neve cairiam ou cairiam e um floco de neve seria uma flor de primavera ...)É claro que a complexidade ciclomática das decisões que você tenta criar usando essa abordagem será bastante comparável ao enigma Einstein . Portanto, aqui "cozinhei" uma biblioteca , ela pode ajudar na criação de cadeias de designers e memorização (nota do editor: bem, takoe, pegue uma torta de uma prateleira, bla-bla-bla ) ...E embora eu não possa provar isso, essa abordagem tem sido usada com êxito de tempos em tempos há duas décadas, se você precisa ter 146% de certeza de que tudo é normal com herança. Você pode facilmente ver por si mesmo que ele é testado, reproduzido e suportado elementarmente (Nota do editor : sim, agora, você deixou tudo e foi se certificar ).Obviamente, essa não é a história toda, simplesmente declaramos o fato: o JavaScript foi projetado bem o suficiente para descrever o Gráfico de Genealogia diretamente por meio da Herança. É claro que aqui não abordamos maliciosamente o tópico Degradação de classe , mas tenho certeza de que você mesmo pode substituir facilmente FlowerEggCell por FlowerEggCellClass dentro de FlowersFactory: a essência permanecerá a mesma se você quiser verificar suas flores através de uma instância em que verá que todas elas são descendentes de FlowerEggCell às quais você se refere por meio do FlowerZygote . E, é claro, agora você pode alterar as propriedades do FlowerZygote , porque isso não causará nenhum dano ao FlowersFactory , ele poderá criar outros novos construtores FlowerEggCell ou FlowerEggCellClass no futuro, de acordo com o design original de " referência " que você colocou lá.Espero que este artigo tenha dissipado todas as dúvidas sobre a importância da palavra .prototype na próxima vez que você vir null this ,
. call (null ,
. apply (null . bind (null code style
(. .:
Sorrow , , , ).
!
!
V