Entendendo Prototypes no JavaScript: Herança e Eficiência

Em JavaScript, objetos possuem características únicas, mas quando uma propriedade específica não é encontrada, o objeto busca essa propriedade em outro objeto referenciado pelo seu protótipo. Essa funcionalidade é essencial para entender como a herança e o compartilhamento de funcionalidades funcionam na linguagem. Vamos explorar como essa busca ocorre e como otimizar o uso de Protótipos em JavaScript para economizar memória e evitar duplicação de código.

Objetos possuem uma propriedade interna e invisível chamada [[Prototype]], que aponta para outro objeto. Essa ligação permite que um objeto herde propriedades e métodos de seu protótipo, criando uma cadeia de herança.

Um exemplo prático dessa funcionalidade é o seguinte código:


let animal = { 
    eats: true,   
    walk() { 
        alert("Animal walk"); 
    } 
}; 
let rabbit = { 
    jumps: true, 
    __proto__: animal 
}; 

// walk is taken from the prototype 
rabbit.walk(); // Animal walk

Neste caso, o objeto rabbit herda do objeto animal, permitindo que rabbit utilize o método walk definido em animal.

## Cadeia de Protótipos em JavaScript

Quando tentamos acessar uma propriedade de um objeto que não existe, o JavaScript continua procurando essa propriedade na cadeia de protótipos. Esse comportamento pode ser estendido ainda mais, como no exemplo abaixo:


let animal = { 
    eats: true, 
    walk() { 
        alert("Animal walk"); 
    } 
}; 

let rabbit = { 
    jumps: true,   
    __proto__: animal 
}; 

let longEar = { 
    earLength: 10,   
    __proto__: rabbit 
}; 

// walk is taken from the prototype chain 
longEar.walk(); // Animal walk 
alert(longEar.jumps); // true (from rabbit)

Aqui, o objeto longEar herda de rabbit, que por sua vez herda de animal. Assim, longEar pode acessar tanto walk (de animal) quanto jumps (de rabbit).

### A Importância do this

O uso da palavra-chave this é crucial para entender como os protótipos funcionam em JavaScript. Veja este exemplo:


let user = { 
    name: "John", 
    surname: "Smith", 

    set fullName(value) { 
        [this.name, this.surname] = value.split(" "); 
    }, 

    get fullName() { 
        return `${this.name} ${this.surname}`; 
    } 
}; 
let admin = { 
    __proto__: user, 
    isAdmin: true 
}; 
alert(admin.fullName); // John Smith 
admin.fullName = "Alice Cooper"; 
alert(admin.fullName); // Alice Cooper, state of admin modified alert(user.fullName); // John Smith, state of user protected

Neste caso, this sempre se refere ao objeto antes do ponto quando uma propriedade ou método é acessado. Em admin.fullName, this se refere a admin, e os getters e setters de fullName operam nas propriedades de admin.

Mesmo que fullName seja herdado de user, a lógica no getter/setter se aplica ao objeto (admin) que o invocou. Isso significa que podemos personalizar o comportamento de objetos derivados sem modificar o objeto original.

### Alternativa ao __proto__: Object.create

O uso de __proto__ é desencorajado no JavaScript moderno devido a preocupações de desempenho e possíveis problemas. A mesma funcionalidade pode ser alcançada utilizando Object.create, que oferece uma maneira mais eficiente e clara de estabelecer a relação de protótipo.


let user = { 
    name: "John", 
    surname: "Smith", 

    set fullName(value) { 
        [this.name, this.surname] = value.split(" "); 
    }, 

    get fullName() { 
        return `${this.name} ${this.surname}`; 
    } 
}; 

let admin = Object.create(user); 

// Output logged to the console
console.log(admin.fullName); // John Smith 
admin.fullName = "Alice Cooper";
console.log(admin.fullName); // Alice Cooper
console.log(user.fullName); // John Smith

Utilizando Object.create, criamos um novo objeto admin que herda as propriedades e métodos de user, sem modificar o objeto original.

## Protótipos embutidos e Wrappers

JavaScript possui objetos embutidos como Array, Function e Map. Esses objetos armazenam seus métodos em seus protótipos, como slice() para arrays e size() para maps. Quando criamos um novo objeto usando new Object() ou let obj = {}, o [[Prototype]] é automaticamente definido como Object.prototype.

Todos os objetos embutidos herdam, em última instância, de Object prototype.

É importante notar que, embora JavaScript tenha objetos wrapper para primitivos como Number, String e Boolean, os valores null e undefined não possuem esses objetos. Esses wrappers permitem que os primitivos acessem métodos como se fossem objetos, mas desaparecem logo após o uso.

### Limitações e Estruturas Lineares

Objetos em JavaScript não apenas armazenam suas próprias propriedades, mas também possuem protótipos. Os protótipos permitem que objetos herdem propriedades e métodos de outros objetos, o que ajuda a economizar memória compartilhando funcionalidades em vez de duplicá-las.

Uma limitação importante é que os objetos não podem referenciar-se circularmente ou referenciar dois objetos simultaneamente na cadeia de protótipos. Por exemplo, a seguinte estrutura não é permitida: B <-- A --> C. A estrutura deve sempre seguir uma linha linear, como A –> B –> C.

Em resumo, os protótipos em JavaScript são um mecanismo poderoso para herança e compartilhamento de funcionalidades, permitindo que os desenvolvedores criem código mais eficiente e reutilizável. Compreender como funcionam os protótipos e como utilizá-los corretamente é fundamental para escrever código JavaScript de alta qualidade.
Este conteúdo foi auxiliado por Inteligência Artificiado, mas escrito e revisado por um humano.

Leave a Comment

Exit mobile version