Entendendo POO em JavaScript
Tags: javascript, programming, tutorial, braziliandevs
Published: 2023-01-17 17:09:30

Objetos estão em toda parte em JavaScript. Funções são objetos, arrays são objetos. Inferno, até as classes são objetos, bem, não exatamente. Classes são funções, mas, novamente, funções são objetos.

## Bem-vindo á gerança prototípica:

JavaScript não funciona da maneira tradicional de usar classes como plantas para criar objetos, mas sim objetos como plantas. Basta dizer que isso feriu os sentimentos de muitos. Mas como isso funciona?

Todo objeto em JavaScript tem uma cadeia de Prototype anexada, que é um conjunto de objetos vinculados á propriedade oculta interna `[[Prototype]]`. Toda vez que uma pesquisa de propriedade ou método é feita, toda a cadeia de Prototype é vasculhada de cima para baixo, para encontrar o valor que é essencialmente a Herança Prototypal.

Essa cadeia de protótipos pode ser acessada usando a propriedade `__proto__`, que é um getter para a propriedade oculta `[[Prototype]]`. O `__proto__` geralmente não é usado, pois existe apenas por motivos legados. Se você deseja acessar o protótipo em seu código, a melhor alternativa é `Object.getPrototypeOf`.

Cada novo objeto criado possui `Object.prototype` em sua cadeia protótipo, e cada array possui `Array.prototype` em sua cadeia protótipo, que permite herdar métodos como map(), filter() etc:


![Gráfico de proto](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2ieucocjgzzy4br0bio6.png)

Também podemos adicionar a esta cadeia usando o método `Object.create()`, que nos permite criar um novo objeto com seu 
 conjunto `[[Prototype]]` como o objeto passado para o método no primeiro @parameter:

```jsx
const person={

run(){ return `${this.name} is running.`},

sleep(){return `${this.name} is sleeping.`}

}

let woman=Object.assign( Object.create(person) , {name:'She'} ); /* @returns {
                                                                            name: "She"
                                                                          __proto__: Object <person>
                                                                          }
                                                               */

let man=Object.assign( Object.create(person) , {name:'He'} ); /* @returns {
                                                                            name: "He"
                                                                          __proto__: Object <person>
                                                                          }
                                                               */
woman.run(); // @returns "She is running."
man.sleep(); // @returns "He is sleeping."
```

Embora `Object.create()` seja muito legal, seria mais legal se uma função pudesse fazer isso, dando-nos mais flexibilidade na definição do objeto que está sendo criado e é aí que as funções do construtor entram em cena.

Toda função definida em JavaScript com a palavra-chave `function`, possui uma propriedade chamada prototype. Esta propriedade refere-se ao objeto container que contém a função definida como sua `constructor`:

```jsx
function(){}).prototype
@returns {
  constructor: ƒ () //function on which prototype property was accessed.
  __proto__: Object
}
```

Essas funções podem ser chamadas com uma palavra-chave `new` que faz o seguinte:

- Um novo objeto é criado com seu `[[Prototype]]` atribuído ao objeto protótipo da função do construtor.
- A palavra-chave `this` dentro da função, aponta para o novo objeto que está sendo criado.
- O objeto criado é retornado automaticamente pela função, embora possa ser substituído por um arquivo `return`.

O exemplo acima pode ser recriado assim:

```jsx

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

Person.prototype.run=function (){ return `${this.name} is running.`};

Person.prototype.sleep=function (){return `${this.name} is sleeping.`};

let woman=new Person('She'); 
let man=new Person('He');

woman.run(); // @returns "She is running."
man.sleep(); // @returns "He is sleeping."
```

## Classes em JavaScript:

Uma classe é uma versão muito mais padronizada do construtor Functions. Ele oferece recursos adicionais como:

- Ela só pode ser chamada com uma palavra-chave `new`, caso contrário, gera um erro.
- constructor e outros métodos podem ser adicionados diretamente por meio de declarações de classe `constructor.prototype`.
- Métodos e propriedades estáticos podem ser adicionados à palavra-chave constructor usando `static`.
Podemos usar a palavra-chave `extends` para estender a cadeia de protótipos.
- Atualizações recentes também permitem que propriedades/métodos privados sejam adicionados.
- Ela é executada em modo estrito.

O exemplo acima pode ser escrito como uma classe como essa:

```jsx
class Person{

constructor(name){
 this.name=name;
};

run(){ return `${this.name} is running.`};
  
sleep(){return `${this.name} is sleeping.`};

}

let woman = new Person('She'); 
let man = new Person('He');

woman.run(); // @returns "She is running."
man.sleep(); // @returns "He is sleeping."
```

## Métodos e propriedades estáticos:

Métodos e propriedades `static` são realmente adicionados à própria função do construtor, pois a função é um objeto e os objetos podem ter suas próprias propriedades.

Dessa forma, esses métodos e propriedades só podem ser acessados ​​por meio da função construtora e não podem ter acesso direto aos métodos e propriedades da instância:

```jsx
/** Constructor Functions **/
function Person(name){
  this.name=name;
}
Person.isNamed=function(instance){return !!instance.name}

var you=new Person(); /** @returns 
                          Person { name: undefined
                                 __proto__: constructor: ƒ Person{
                                                                    isNamed: ƒ (instance)
                                                                }  
                                  } 
                       **/                         
Person.isNamed(you); //@returns false;

/** class Constructor **/
class Person{

constructor(name){
  this.name=name;
  }
static isNamed(instance){
      return !!instance.name;
  }
}
```

## Estendendo uma classe:

Uma extensão de classe é basicamente estender a cadeia de protótipos. Sem usar uma classe, isso pode ser feito por:

- Criando uma função construtora e modificando seu objeto prototype para herdar do pai `constructor.prototype` para criar a cadeia de protótipos.
- Chamando a função do construtor pai dentro da nova função do construtor com a referência `this` certa para que a nova instância também seja instanciada pelo construtor pai.
- Adicionando a função de construtor pai à cadeia de protótipo da nova função de construtor para garantir que todas as propriedades/métodos estáticos sejam herdados. Isso também garante a referência correta de `super` como veremos.

```jsx
function Life(medium){
    this.medium=medium;
}
Life.prototype.eat=function(){return `get food from ${this.medium}`};


function Human(identity,medium){
     Object.setPrototypeOf(Human,Life);  
     Life.call(this,medium);
     this.identity=identity;  
}

Human.prototype=Object.assign(Object.create(Life.prototype),{constructor:Human});

var developer=new Human('coder','soil');
```

Isso na classe se tornaria:

```jsx
class Life{
  constructor(medium){
    this.medium=medium;
   }
  
  eat(){return `get food from ${this.medium}`};
}

class Human extends Life{
  constructor(identity,medium){
    super(medium);
    this.identity=identity;
    }
}


var developer=new Human('coder','soil');
```

## super()

A palavra-chave `super` é uma palavra-chave especial que só pode ser usada dentro de declarações de classe e literais de objeto. A razão é que ele depende de uma propriedade interna `[[HomeObject]]`, que aponta para o objeto pai de um método durante a declaração do método.

O `[[HomeObject]]` pode nem sempre ser o mesmo que `this` está apotando em um método, como chamar o método de um objeto diferente ou usar `function.bind()`. Assim, o uso de tais métodos como o `super`, os torna dependentes do objeto pai.

A palavra-chave super, portanto, refere-se ao `[[Prototype]]` de `[[HomeObject]]` em um método:


```jsx
const one={
    who(){return 'I am the one'}
}

const two={

    getRoots(){
      return super.who();
    }

}

Object.setPrototypeOf(two,one);
```

Aqui, o método `getRoots()` tem seu `[[HomeObject]]` apontamento para two e o `super` refere-se ao `[[Prototype]]` qual `two` é definido como `one`.

O construtor `super` interno chamado `when`  refere-se ao construtor pai, no entanto, a palavra-chave `super` também pode ser usada para acessar métodos na cadeia de protótipos.

Mas aqui está o problema, em uma classe, se `super` for chamado a partir de métodos estáticos, o `[[HomeObject]]` aponta para a função construtora e, portanto, `super` aponta para a função construtora pai conforme ela é definida na cadeia de protótipos por classe. No entanto, para métodos normais, o `[[HomeObject]]` aponta para `constructor.prototypee`, portanto, `super` refere-se ao `constructor.prototype` pai:

```jsx
class Home{

sleep(){ console.log(`${this.name} is sleeping.`) }

resting(){console.log(`${this.name} is resting`)}

static atHome(){console.log('I am at Home.')}

}

class HomeWork extends Home{

constructor(name){
  super();
  this.name=name;
  super.resting();
}
sleep(){
    super.sleep();
    console.log('no work.')
}

static workingAt(){super.atHome();}

}

var lad=new HomeWork('lad'); //@logs "lad is resting"
lad.sleep(); //@logs "lad is sleeping." & "no work."
HomeWork.workingAt(); //@logs "I am at Home."
```

O importante a observar aqui é que `super.atHome()` não pode ser acessado pelo método `sleep()` e `super.sleep()` não pode ser acessado pelo método `workingAt()`.

## Propriedades privadas

O encapsulamento tem sido um problema de longa data em JavaScript, pois não há campos privados ou protegidos. As soluções alternativas geralmente têm sido com variáveis ​​sublinhadas, símbolos ou fechamentos poderosos.

As atualizações recentes, no entanto, têm a proposta de avançar para campos de classe privada que podem ser usados ​​assim:

```jsx
class MI{

#secretcode=123;

getAccess(value){
  if(value===this.#secretcode)return 'granted access.';
  else return 'access denied.'
}

}

let agent=new MI();
agent.#secretcode=0; //@throws Uncaught SyntaxError: Private field '#secretcode' must be declared in an enclosing class.
agent.getAccess(0); //@returns "access denied."
```

No entanto, se os encerramentos tiverem que ser usados ​​para obter o encapsulamento, a única maneira de fazer isso é:

```jsx
const Matrix=(id)=>{

    const STATE={
        human_id:id,
        current:'IDLING',
        loaders:[]
    }

    const drive=()=>{
            STATE.loaders.push('driving');
            STATE.current='DRIVING';
    }

    const work=()=>{
           STATE.loaders.push('working'); 
           STATE.current='WORKING' 
    }

    const getActivity=()=>(STATE);

    return {work, drive, getActivity}

}

const getHumanConstructor=()=>{

  let id=0;  
  let matrix={};  

  return class Human{

      constructor(name){
          this.name=name; 
          Object.defineProperty(this,'id',{
                value:++id,
                writable:false,
                configurable:false,
                enumerable:false,    
            })   
          matrix[this.id]=Matrix(this.id);
          } 
      drive(){
          matrix[this.id].drive();
          console.log(`${this.name} is driving.`)
          }
      work(){
          matrix[this.id].work();
          console.log(`${this.name} is working.`)
          }

      __checkActivity(){return matrix[this.id].getActivity()}

  }

}

const Human=getHumanConstructor();

var Neo=new Human('Neo');
Neo.drive(); //@logs "Neo is driving."
Neo.work(); //@logs "Neo is working."
Neo.__checkActivity(); //@returns {human_id: 1, current: "WORKING", loaders: ["driving", "working"]}

var Trinity=new Human('Trinity');
Trinity.drive(); //@logs "Trinity is driving."
Trinity.__checkActivity(); //@returns {human_id: 2, current: "DRIVING", loaders: ["driving"]}

```

## Conclusão:

POO é um poderoso paradigma de programação que pode ser usado para criar sistemas independentes e a linguagem nos fornece ferramentas suficientes para implementar com sucesso tais sistemas. Tudo o que pude fazer foi dar uma pequena olhada no mesmo. Espero que você tenha gostado. Obrigado por ler.