Javascript e as diferenças entre `this` léxico e dinâmico
Tags: javascript
Published: 2020-01-22 04:37:27

Diferente de linguagens como C++, Java e PHP, em Javascript o identificador `this` pode ser usado em qualquer função e não apenas em métodos de classes. O comportamento exato depende da forma como a função é declarada e invocada. Considere o seguinte código:

```javascript
var point = { x: 0, y: 0 }

function move(x, y) {
  this.x = x
  this.y = y
}

point.move = move

point.move(10, 10)
move(20, 20)

```

A função `move` está sendo declarada na forma padrão até a versão __ES5__, utilizando a palavra reservada `function`. Nesta forma de declaração, o escopo de `this` é dinâmico e será o objeto ao qual a função estiver atribuída no momento em que for invocada. Neste caso, `this` será o objeto `point` na instrução `point.move(10, 10)`. Já na instrução `move(20, 20)`, a função está sendo invocada diretamente sem ter sido atribuída a algum objeto. Como todo identificador _top-level_ não atribuído a qualquer objeto pertence à `window`, então `this` é uma referência à `window` (e poderia ser invocada com `window.move(20, 20)` tendo o mesmo efeito).

Agora, suponha que haja uma função chamada `log` que imprime a data e hora atual e receba como argumento um callback que deve retornar uma `string` com a mensagem que será concatenada no final:

```javascript
function log(callback) {
  var date = new Date().toUTCString()
  var message = callback()

  console.log(date, message)
}

```

Então, definimos uma função para serializar o objeto `point` da seguinte forma:

```javascript
point.serialize = function() {
  return '(' + this.x + ',' + this.y + ')'
}
```

E invocamos `log` fornecendo uma referência à `serialize`:

```javascript
log(point.serialize)
```

Isso não funciona, pois é na chamada ao callback, dentro da função `serialize`, em `var message = callback()` que será determinado quem será o `this`. Nesse caso, como visto antes, `this` será `window`. Para entender melhor é necessário saber que, tecnicamente, não existem métodos de classes em um nível mais baixo de execução, e o `this` não passa de um argumento a mais na função. O seguinte trecho:

```javascript
point.move(10, 10)
move(20, 20)

```

Será convertido para:

```javascript
move(point, 10, 10) // Point é o `this`
move(window, 10, 10) // Quando invocado diretamente
```

E a função `move` será convertida para:

```javascript
function move(point, x, y) {
  point.x = x
  point.y = y
}
```

Como visto no exemplo anterior, estamos fornecendo apenas uma referência à função serialize (que não possui nenhuma referência à `point`) e dentro de `log` o callback é invocado diretamente. Logo, o objeto `window` será fornecido como primeiro argumento na forma convertida da chamada.

Para resolver o problema, podemos criar uma função que empacota a chamada à `serialize`:

```javascript
function serializeWrapper() {
  return point.serialize()
}
```

E fornecer a versão empacotada:

```javascript
log(serializeWrapper)
```

Ou usar o método `bind` pertencente ao protótipo de `Function`, o qual recebe como argumento um objeto e retorna uma cópia da função com o `this` vinculado ao objeto fornecido:

```javascript
log(point.serialize(point))
```

Devemos tomar cuidado também quando formos definir funções inerentes:

```javascript
point.serialize = function() {
  function innerSerialize() {
    return '(' + this.x + ',' + this.y + ')'
  }

  return innerSerialize()
}
```

Aqui, temos o mesmo problema visto anteriormente. A função `innerSerialize` está sendo invocada diretamente e `this` será `window`.

A partir da versão `ES6`, podemos usar as __arrow functions__, que mantém o escopo léxico de`this` na definição da função, independentemente de como é invocada, e __classes__ para definirmos as estruturas dos objetos. O problema da função inerente acima é resolvido se a declararmos como uma __arrow function__ e usarmos classes:

```javascript
class Point {
  serialize() {
    const innerSerialize = () => {
      return '(' + this.x + ',' + this.y + ')'
    }

    return innerSerialize()
  }
}

const p1 = new Point()
const p2 = new Point()

p1.serialize()
p2.serialize()

```

A diferença entre as sintaxes das declarações está no uso da flecha (`=>`), além de nunca poderem ser nomeadas. Quando houver apenas um argumento, os parênteses podem ser omitidos e quando o corpo da função se limitar a apenas uma expressão, as chaves e o valor de retorno também podem ser omitidos.

Dentro da definição de `innerSerialize`, `this` mantém o escopo léxico da função `serialize`. Nesse caso, a instância da classe `Point` que estiver invocando o método. Mesmo que `innerSerialize` seja atribuida à algum outro objeto, `this` permanecerá sendo o objeto `point`