# Contextos em Javascript

Uma das linguagens de programação mais populares desde o início dos anos 2000 (se não a mais popular), Javascript é considerada uma linguagem orientada a objetos. Para aqueles que não estão familiarizados com o termo, isso significa que em Javascript podemos aplicar naturalmente alguns dos conceitos principais da programação orientada a objetos, que utiliza a declaração de classes e objetos e usa suas propriedades e métodos (funções internas de objetos).

Vamos observar a classe Vehicle como exemplo:

```javascript
class Vehicle {
    name;
    numberOfPassengers;
    move;

    constructor(name, numberOfPassengers, move){
        this.name = name;
        this.numberOfPassengers = numberOfPassengers;
        this.move = move;
    }
}
```

Vemos que um Vehicle tem algumas variáveis definidas: `name`, `numberOfPassengers`, e `move`. Por padrão, todas as classes têm um método construtor, que serve para construir um novo objeto da classe fornecida. O método construtor de nossa classe Vehicle recebe parâmetros que são atribuídos às suas variáveis internas do objeto.

Observe que, para atribuir os parâmetros às variáveis do objeto, o construtor usa a palavra `this.` antes do nome das variáveis. Essa palavra-chave `this` se refere ao contexto que envolve aquela função, e engloba tudo dentro das chaves de definição da classe.

A maneira de construir um veículo é usando a palavra-chave `new` seguido de uma chamada ao método construtor da classe(No nosso caso `Vehicle()`) com os argumentos necessários, atribuindo o valor a uma variável, como no exemplo abaixo:

```javascript
function drive(){
    console.log("dirigindo um " + this.name)
}
var car = new Vehicle("Porshe", 2, drive)

console.log(car.name)
console.log(car.numberOfPassengers)
console.log(car.move())

// Saída esperada:
// Porshe
// 2
// dirigindo um Porshe
```

Podemos ver que nosso novo objeto Vehicle é um Porshe que pode transportar até 2 passageiros. Ele também recebe uma função, `drive`, que imprime no console. Podemos acessar as propriedades do carro e imprimi-las no console, assim como podemos chamar seu método `move` e o resultado também é impresso no console.

No entanto, vamos considerar a função `ride` abaixo. Ela é muito semelhante à função `drive` do código anterior.

```javascript
function ride(){
    console.log("passeando de " + this.name)
}
ride()

// Saída esperada:
// passeando de undefined
```

Se chamarmos a função `ride` da forma como está, obtemos a mensagem "passeando de undefined" no console. Isso acontece porque, quando a chamamos, não há uma variável `name` nesse contexto para ser referenciada. Ao construir um objeto e passar para ele uma função que usa o contexto envolvente, a função o detecta automaticamente e o utiliza. É assim que nosso objeto `plane` abaixo funciona corretamente, assim como o nosso objeto `car` anterior.

```javascript
function fly(){
    console.log("pilotando um " + this.name)
}

var plane = new Vehicle("Boeing 777-300", 368, fly)
plane.move()

// Saída esperada:
// pilotando um Boeing 777-300
```

Essa é uma maneira muito simples de entender como contextos funcionam no Javascript, mas às vezes encontramos situações mais complexas. Por exemplo, digamos que você queira observar alterações em um arquivo e exibir o conteúdo desse arquivo sempre que ele for alterado. Para isso, usamos a função `watch` do módulo `fs` do Node. Digamos que sua classe `FileHandler` seja semelhante à apresentada abaixo:

```javascript
import { watch, promises } from "fs";

class FileHandler {
    watch(event, filename) {
        this.showContent(filename);
    }

    async showContent(filename) {
        // o conteúdo é retornado como um buffer, então convertemos para string
        const content = await promises.readFile(filename).toString()
        console.log(content);
    }
}

const fileHandler = new FileHandler()
watch(__filename, fileHandler.watch)
```

Se tentarmos passar o método `fileHandler.watch` como no trecho acima, ele será ignorado pela linguagem. Isso ocorre devido a outro conceito da programação orientada a objetos que é aplicado naturalmente em Javascript, que é a herança ( segundo a herança uma classe pode herdar propriedades e métodos de outra classe, permitindo o reuso de código e relacionamentos hierárquicos entre classes).

Em vez disso, a linguagem tentará usar o método `watch` do objeto `FSWatcher`, que está dentro do módulo `fs`, e possui um método `watch` interno. No entanto, esse objeto não possui um método `showContent` como nosso `fileHandler`, o que resultará em um erro.

Esse é um problema de contexto que pode ser resolvido especificando em qual contexto a função que estamos tentando chamar está. Para isso, temos três métodos herdados por todos os objetos Javascript: `bind`, `call` e `apply`.

### Bind

O método bind cria uma cópia de uma função vinculada ao contexto fornecido como primeiro argumento. Se quisermos resolver nosso problema com esse método, podemos fazer da seguinte maneira:

```javascript
watch(__filename, fileHandler.watch.bind(fileHandler));
```

Pode parecer redundante à primeira vista, mas dessa forma nos certificamos de que, durante a execução, o método `watch` que estamos chamando será aquele contido no objeto `fileHandler`. Como o método `bind` cria uma cópia da função, também podemos atribuí-lo a uma nova variável passando-a logo em seguida, assim:

```javascript
var boundFileHandlerWatch = fileHandler.watch.bind(fileHandler)
watch(__filename, boundFileHandlerWatch);
```

### Call

O método call chama a função alterando o contexto para o contexto passado como primeiro argumento. Resolver nosso problema com esse método ficaria assim:

```javascript
watch(__filename, fileHandler.watch.call(fileHandler));
```

### Apply

O método `apply`, de forma semelhante ao método anterior `call`, chama a função alterando o contexto para o contexto passado como primeiro argumento.

A única diferença aqui é como passamos os argumentos para o método que estamos alterando o contexto. Enquanto o método `call` recebe os argumentos individualmente, o método `apply` recebe um array de valores como segundo argumento.

Como exemplo, observe nosso `fileHandler`. Seu método `watch` recebe dois argumentos: o evento e o nome do arquivo:

```javascript
class FileHandler {
    watch(event, filename) {
        this.showContent(filename);
    }

    async showContent(filename) {
        const content = await promises.readFile(filename).toString();
        console.log(content);
    }
}
```

Para simular a forma como `fs.watch` chama nosso método `fileHandler.watch`, podemos alterar o contexto do nosso método para outro contexto onde há um método `showContent` e passar os argumentos como nos exemplos abaixo:

```javascript
var callContext = { showContent: () => console.log("hello call!") };
fileHandler.watch.call(callContext, "change", __filename);

var applyContext = { showContent: () => console.log("hello apply!") };
fileHandler.watch.apply(applyContext, ["change", __filename]);

// Saída esperada:
// hello call!
// hello apply!
```

Vemos que tanto `call` quanto `apply` são muito semelhantes. Ao usá-los, basta ter cuidado para passar os argumentos na ordem correta, pois os parâmetros serão recebidos na ordem respectivamente.

É isso aí! Da próxima vez que você passar métodos entre contextos, certifique-se de especificá-los corretamente para evitar problemas como esse.
