JavaScript

3 jun, 2020

O que é Prototype Chain em JavaScript.

Publicidade

Hoje o tema é um pouco complexo para quem ta iniciando na linguagem, contudo se faz fundamental para o seu entendimento, e sinceramente vejo que muitas pessoas ainda tem dificuldades ou até mesmo realmente nunca ouviram falar. Neste post eu vou tentar compartilhar um pouco do meu entendimento sobre Heranças e Protótipos em JavaScript, contudo se você ainda esta começando na linguagem sugiro você dar uma lida neste meu post no qual fala sobre hoisting, escopo etc.

Quando falamos de Herança o JavaScript tem somente um Construtor (Objeto), no qual é projetado no que chamamos de Prototype Chain (Cadeia de Protótipos).

Cada , criado no JavaScript tem sua própria cadeia de protótipos, através da propriedade . Contudo uma coisa tem que ficar clara, toda  tem o mesmo nível hierárquico que um . Ou seja, é preciso entender como funciona tudo isso para poder entender qual o papel de cada um dentro da linguagem.

Com isto cada function, Object ou Array tem um link para outro objeto, que é chamado seu prototype, assim seguindo esta cadeia até que este objeto chegue no valor null, sendo este o último link da prototype chain (cadeia de protótipos).

Todo novo desenvolvedor que começou a aprender a programar, pode ter encontrado o termo POO (programação orientada a objetos). Você procurou o que significava e descobriu que é simplesmente para agrupar dados em “objetos” com atributos. POO é um paradigma de programação que usa abstração para criar modelos baseados no mundo real. POO usa várias técnicas vindas de paradigmas previamente estabelecidos, incluindo modularidade, polimorfismo e encapsulamento. Atualmente, muitas linguagens de programação populares (como Java, JavaScript, C #, C ++, Python, PHP, Ruby e Objective-C) permitem a programação orientada a objetos (POO).

A palavra-chave usada para criar esses objetos em muitas linguagens de programação é a . Você define uma classe com um construtor e várias funções públicas e privadas. Se você deseja que uma classe seja herdada de outra, escreve uma sintaxe de herança simples e (wala!), criou uma cadeia de herança.

Contudo JavaScript é conhecida como Programação baseada em protótipos é um estilo de programação orientada a objetos na qual não temos presença de classes. Em vez disso, a reutilização de comportamento (equivalente à herança das linguagens baseadas em classes) é realizada através de um processo de decorar (ou expandir) objetos existentes que servem como protótipos. Este modelo também é conhecido como sem classesorientado a protótipo, ou programação baseada em exemplares.

O exemplo original (e o mais canônico ) de uma linguagem baseada em protótipo é a linguagem de programação Self desenvolvida por David Ungar e Randall Smith. No entanto, o estilo de programação sem classes tem se tornado mais popular, e foi adotado por linguagens de programação como JavaScript, Cecil, NewtonScript, lo, MOO, REBOL, Kevo, Squeak.

Neste ponto temos diversos pontos de debates, pois muitos consideram um ponto fraco, contudo no meu ponto de vista este modelo de Herança acaba sendo mais potente que o clássico. Cada objeto é como se fosse um baú cheio de propriedades, e cada um deles linka para o seu proprio prototype.

Vale lembrar que hoje temos  em JavaScript, contudo não passa apenas de uma sintaxe sugar, no qual tem algumas diferenças sugiro você dar uma lida aqui. Ou seja em JavaScript temos Pseudoclasses

Eu imagino que sua mente esteja assim rs. Bem vamos tentar começar a aquecer um pouco e vamos analisar o exemplo abaixo:

const myObject = {
  name: 'Celso',
  sign: 'capricorn'
}

function mySign() {
  return myObject.sign
}

Temos declarados um objecto chamado myObject e uma função com o nome de mySign. Veja abaixo se executarmos isto no console do navegador:

Notem dois pontos importante aqui, quando chamamos o , ele retorna um objeto (com alguns parametros que iremos analisar mais para frente), outro ponto importante é: ele retorna uma função.

Vamos analisar a imagem abaixo e entender um pouco melhor esta ligação.

O que esta acontecendo por debaixo dos panos, é quea constante  tem um link com o prototype do  e ele é o último item desta cadeia, pois por definição null não possiu um  e acaba atuando como o elo final desta prototype chain.

Mas Celso como fica a função ?

Vamos olhar um pouco mais a fundo como ficaria a cadeia com ambos juntos.

Quando utilizamos a função ela retorna um outro objeto de acordo com os parametros passados. Contudo da onde vem a função map?

Temos o seguinte Array abaixo:

const myArray = [1, 2, 3, 'Celso']

Vamos analisar a imagem acima, ao fazer a chamada desta constante no console do navegador o que encontramos.

Temos todos os nós que foi declarado no , contudo temos um ponto especial alí, temos mais um nó chamado . Dentro deles temos todos os métodos disponíveis para o uso com , dentre eles temos o ,, etc.

Se você notar dentro do  temos a função Array, quando criamos um array, seja utilizando  ou , ambos iram ser linkados para o  do método array, no qual tem os métodos expostos como já foi citado acima e mostra também na imagem.

Com isto o prototype chain verifica se o objeto contém aquele método nele, se não tiver procura no próximo e assim por diante.

Normalmente trabalhamos sempre com muitos objetos ou arrays, com isto uma boa prática é mantermos a imutalibilidade de determinados elementos, vamos abordar um exemplo sobre isto:

const myObject = {
  ownersName: 'Celso',
  dogName: ['Maya', 'Bacon', 'Paçoca', 'Pandora', 'Rex'],
};

myObject.amount = myObject.dogName.length;

const newObject = myObject;
newObject.amount = 2;
console.log(myObject.amount + newObject.amount);

Analisando o código acima, na linha 1 criamos a constante , com duas propriedades,  e . Na linha 6 criamos a propriedade amount que recebe a quantidade total da propriedade , que temos um total de 5 items. Na linha 8 criamos uma nova constante  no qual recebe , na linha 9 atribuimos o valor de  da nova constante  para 2.

Aqui é que surge muita confusão para muitos programadores, pois muitos aqui imaginam que o resultado que o console.log irá retornar na linha 10, seria de valor 7certo? Bem se este for seu pensamento, você esta errado(a).

O resultado que acontece neste cenário é o valor de 4, e porque isto acontece?, na linha 8 ao criar a constante  e atribuirmos , no Javascript ele não cria uma nova referencia para , e na linha 9 fazemos a modificação do  para 2, com isto tanto o  do  e  recebem o valor 2.

Se você já lidou com qualquer estrutura de gerenciamento de estado. Você saberá que a imutabilidade é super importante. Deixe-me explicar brevemente. Um objeto imutável é um objeto em que o estado não pode ser modificado após a criação. O problema em JavaScript, é que são mutáveis.

No ES5 temos um novo método chamado create, vamos fazer esta modificação:

const myObject = {
  ownersName: 'Celso',
  dogName: ['Maya', 'Bacon', 'Paçoca', 'Pandora', 'Rex'],
};

myObject.amount = myObject.dogName.length;

const newObject = Object.create(myObject);

newObject.amount = 2;
console.log(myObject.amount + newObject.amount);

Com a modificação na linha 8, é criado uma nova referencia para  e com isto o valor passa a ser 7.

// outras formas de fazer o clone de um elemento
const newObject = myObject => [...myObject];

const newObject = myObject => myObject.slice(0);

const newObject = myObject => myObject.from(arr);

const newObject = myObject => myObject.map(x => x);

const newObject = myObject => JSON.parse(JSON.stringify(myObject));

const newObject = myObject => myObject.concat([]);

Aqui tem um post falando mais sobre clone de array.

Para entendermos melhor sobre dados mutaveis e imutáveis.

Mutavel: ,.
Imutáveis: todos os primitivos, .

Contudo com o , temos que tomar cuidado ao fazer clones, ele não funciona muito bem para arrays multidimencional. Devemos utilizar outras alternativas.

const arrayMult = [[1, 2, 3], [4, 5, 6]];
const clone = array.from(arrayMult);

Nem tudo são flores né

Sim, a vida é uma caixinha de surpresa rs, dependendo do tamanho da árvore do prototype chain, podemos ter problemas de performance, e como isto pode acontecer? Ao fazer qualquer busca de métodos ele pode não encontrar o método dentro do objeto, ele passa para o próximo e assim consequentemente até chegar no último nó desta cadeia. Pode acontecer também de declararmos vários outros objetos um herdando do outros também, criando assim uma cadeia muito grande, com isto gerando muito gargalho em sua execução trazendo problemas de performance.

E como saber se este método existe dentro do seu objeto e não do prototype chain. No próprio objeto temos um método chamado hasOwnProperty, no qual verifica se aquela propriedade faz parte daquele objeto.

Veja o exemplo abaixo:

const array = [1, 2, 3, 4, 5];

console.log(array.hasOwnProperty('1'));
console.log(array.hasOwnProperty('map'));
console.log(array.__proto__.hasOwnProperty('map'));

Na linha 3, estamos verificando se temos uma propriedade “1”, dentro deste objeto e retorna true, na linha 4 verificamos se temos a propriedade  e retorna false e na linha 5 retorna true, pois a propriedade  não existe dentro deste objeto e sim no prototype do proximo que seria o .

Creio que temos muita informação por hoje né, vou dividir este post, com isto vamos ficando por aqui. No próximo post vamos ver outras abordagens de utilizando POO.

Thanks to Filipe M. Silva.