Menu fechado

O Poderoso Efeito do Prototype Chain em JavaScript

prototype

🔹 Introdução ao Problema do Prototype

O problema do prototype em JavaScript é um conceito fundamental que pode afetar a forma como as instâncias de um objeto são criadas e manipuladas. Para entender melhor, vamos analisar um exemplo de código que ilustra a questão:


function User(name) {
  this.name = name;
}
User.prototype.settings = { theme: 'light', notifications: true };
const alice = new User('Alice');
const bob = new User('Bob');
alice.settings.theme = 'dark';
console.log(bob.settings.theme);
console.log(alice.hasOwnProperty('settings'));
console.log(alice.settings === bob.settings);

Nesse exemplo, a função `User` é um construtor que cria instâncias do objeto `User`. A propriedade `settings` é definida no prototype do objeto `User`, o que significa que todas as instâncias criadas a partir desse objeto compartilharão a mesma referência para a propriedade `settings`.

Quando a linha `alice.settings.theme = ‘dark’` é executada, parece que apenas a instância `alice` está sendo afetada. No entanto, ao verificar o valor de `bob.settings.theme`, vemos que também foi alterado para `’dark’`. Isso ocorre porque a mudança feita em `alice.settings.theme` afetou a propriedade `settings` no prototype do objeto `User`, o que é compartilhado por todas as instâncias.

Essa é a essência do problema do prototype em JavaScript: quando uma propriedade é definida no prototype de um objeto, todas as instâncias criadas a partir desse objeto compartilharão a mesma referência para essa propriedade. Se a propriedade for uma referência para um objeto, mudanças feitas em uma instância afetarão todas as outras instâncias que compartilham a mesma referência.

🧠 Mecanismo do Prototype Chain

Leitura e Escrita de Propriedades

O mecanismo de prototype chain em JavaScript permite que as instâncias de objetos herdem propriedades de um objeto pai, chamado prototype. Quando uma instância tenta acessar uma propriedade que não existe em si mesma, o motor de JavaScript percorre a cadeia de protótipos em busca da propriedade.


function User(name) {
  this.name = name;
}
User.prototype.settings = { theme: 'light', notifications: true };
const alice = new User('Alice');
const bob = new User('Bob');

Quando você acessa uma propriedade em uma instância, como alice.settings, o motor de JavaScript primeiro verifica se a instância tem uma propriedade com esse nome. Se não tiver, ele percorre a cadeia de protótipos em busca da propriedade. Nesse caso, a propriedade settings existe no objeto pai User.prototype, então o motor de JavaScript retorna essa propriedade.

Escrita de Propriedades

Quando você tenta escrever uma propriedade em uma instância, como alice.settings.theme = 'dark', o motor de JavaScript primeiro acessa a propriedade settings na instância. Se a instância não tiver essa propriedade, o motor de JavaScript percorre a cadeia de protótipos em busca da propriedade. Nesse caso, a propriedade settings existe no objeto pai User.prototype, então o motor de JavaScript retorna essa propriedade e a modifica.


alice.settings.theme = 'dark';
console.log(bob.settings.theme); // imprime 'dark'

Essa é a diferença fundamental entre leitura e escrita de propriedades em JavaScript: a leitura percorre a cadeia de protótipos em busca da propriedade, enquanto a escrita cria uma nova propriedade na instância ou modifica uma propriedade existente no objeto pai.

Tipos de Referência Mutáveis

Quando você trabalha com tipos de referência mutáveis, como objetos ou arrays, é importante entender que esses tipos são compartilhados entre as instâncias. Isso significa que se você modificar uma propriedade em uma instância, a mudança afetará todas as instâncias que compartilham o mesmo objeto pai.


User.prototype.settings = { theme: 'light', notifications: true };
const alice = new User('Alice');
const bob = new User('Bob');
alice.settings.theme = 'dark';
console.log(bob.settings.theme); // imprime 'dark'

Para evitar esse problema, é recomendável criar uma cópia da propriedade em cada instância, em vez de compartilhar o mesmo objeto pai.

🔍 Explicação Passo a Passo

Leitura e Escrita de Propriedades


function User(name) {
this.name = name;
}
User.prototype.settings = { theme: 'light', notifications: true };
const alice = new User('Alice');
const bob = new User('Bob');
alice.settings.theme = 'dark';
console.log(bob.settings.theme);
console.log(alice.hasOwnProperty('settings'));
console.log(alice.settings === bob.settings);

Ao executar a linha `alice.settings.theme = ‘dark’;`, o JavaScript realiza duas operações:

1. Leitura: `alice.settings` é lido. Como `alice` não tem uma propriedade `settings` própria, o motor do JavaScript segue a cadeia de protótipos e encontra a propriedade `settings` no protótipo `User.prototype`. A referência para essa propriedade compartilhada é retornada.
2. Escrita: A propriedade `theme` é definida como `’dark’` na referência retornada na etapa de leitura. Como a referência é compartilhada entre `alice` e `bob`, essa mudança afeta ambas as instâncias.

Compartilhamento de Propriedades através do Prototype Chain

Ao criar as instâncias `alice` e `bob`, ambas compartilham a propriedade `settings` do protótipo `User.prototype`. Quando a propriedade `theme` é definida como `’dark’` em `alice.settings`, a mudança afeta a propriedade compartilhada `settings` do protótipo, o que significa que a mudança também é refletida em `bob.settings`.




Diagnóstico com `hasOwnProperty()`

A função `hasOwnProperty()` pode ser usada para diagnosticar se uma propriedade é própria da instância ou é compartilhada através do protótipo. No exemplo, `alice.hasOwnProperty(‘settings’)` retorna `false`, pois a propriedade `settings` é compartilhada através do protótipo.

Conclusão

A explicação passo a passo mostra como as operações de leitura e escrita afetam as propriedades do objeto e como isso se relaciona com o compartilhamento de propriedades através do prototype chain. É importante entender esses conceitos para evitar problemas de compartilhamento de estado entre instâncias.

🔹 Impacto no Mundo Real

Esse comportamento do JavaScript pode ter impactos significativos em cenários do mundo real, especialmente em projetos que utilizam prototypes ou classes para compartilhar estado ou configurações.

Configurações Compartilhadas

Quando desenvolvedores colocam objetos de configuração padrão em um prototype (ou uma propriedade estática de uma classe) e mais tarde mutam valores aninhados, eles podem acidentalmente alterar as configurações padrão para todos os existentes e futuros instâncias. Isso é especialmente comum em arquiteturas de plugin onde uma classe base fornece opções padrão.


// Exemplo de configuração compartilhada
class User {
  static settings = { theme: 'light', notifications: true };
}

// Mutar a configuração compartilhada pode afetar todos os instâncias
User.settings.theme = 'dark';

Estado de Componentes em Frameworks Legados

Antes de frameworks modernos imporem padrões de imutabilidade, era comum colocar objetos de estado padrão em prototypes. Mutar estado aninhado em uma instância de componente pode silenciosamente corromper todos os componentes irmãos que compartilham o mesmo prototype.


// Exemplo de estado compartilhado em um framework legado
class Component {
  static state = { foo: 'bar' };
}

// Mutar o estado compartilhado pode afetar todos os componentes
Component.state.foo = 'baz';

Modelos de Dados com Objetos Aninhados

Classes de modelo de dados com objetos aninhados (como permissions: { read: true, write: false }) em um prototype compartilharão estado entre instâncias de modelo se algum código mutar um campo aninhado em vez de substituir o objeto inteiro.


// Exemplo de modelo de dados com objetos aninhados
class User {
  static permissions = { read: true, write: false };
}

// Mutar um campo aninhado pode afetar todos os modelos
User.permissions.read = false;

Padrões de Mixin

Quando utilizando Object.assign ou extensão manual de prototype para misturar comportamentos com objetos de opção padrão, qualquer mutação aninhada em uma instância afetará todas as instâncias que compartilham o mixin.


// Exemplo de mixin com objetos de opção padrão
const mixin = {
  settings: { theme: 'light' },
};

class User {
  static extend = Object.assign({}, mixin);
}

// Mutar o objeto de opção padrão pode afetar todos os usuários
User.extend.settings.theme = 'dark';

🔹 Conclusões e Dicas

Evite Problemas com o Prototype Chain

Para evitar problemas relacionados ao prototype chain, siga as seguintes práticas:

* Não coloque tipos de referência mutáveis no prototype. Isso inclui objetos e arrays.
* Inicialize propriedades mutáveis dentro do construtor, em vez de no prototype.
* Entenda a diferença entre atribuições diretas e mutações aninhadas.

Exemplo de Código


class User {
  constructor(name) {
    this.name = name;
    this.settings = { theme: 'light' }; // Inicialize settings dentro do construtor
  }
}

const alice = new User('Alice');
const bob = new User('Bob');

alice.settings.theme = 'dark'; // Atribuição direta, não afeta o prototype
console.log(bob.settings.theme); // Imprime 'light'

alice.settings = { theme: 'dark' }; // Mutação aninhada, afeta o prototype
console.log(bob.settings.theme); // Imprime 'dark'

Práticas Recomendadas

* Use a sintaxe de classes moderna para evitar problemas com o prototype chain.
* Inicialize propriedades mutáveis dentro do construtor, em vez de no prototype.
* Use a função `hasOwnProperty()` para verificar se uma propriedade é própria do objeto ou herdada do prototype.

Exemplo de Código


class User {
  constructor(name) {
    this.name = name;
    this.settings = Object.assign({}, User.defaults); // Inicialize settings dentro do construtor
  }

  static defaults = { theme: 'light', notifications: true }; // Defina as configurações padrão como static
}

const alice = new User('Alice');
const bob = new User('Bob');

alice.settings.theme = 'dark'; // Atribuição direta, não afeta o prototype
console.log(bob.settings.theme); // Imprime 'light'

alice.settings = { theme: 'dark' }; // Mutação aninhada, afeta o prototype
console.log(bob.settings.theme); // Imprime 'dark'

Fonte de Referência: dev.to.
Curadoria e Adaptação: Redação Yassutaro Developers.



Redação YTI&W-News

Redação Developers | Yassutaro TI & Web

Notícias do universo do Desenvolvimento Web, dicas e tutoriais para Webmasters.

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Publicado em:Desenvolvimento Web
Fale Conosco
×

Inscreva-se em nossa Newsletter!


Receba nossos lançamentos e artigos em primera mão!