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:
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:
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:
point.serialize = function() {
  return '(' + this.x + ',' + this.y + ')'
}
E invocamos log fornecendo uma referência à serialize:
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:
point.move(10, 10)
move(20, 20)
Será convertido para:
move(point, 10, 10) // Point é o `this`
move(window, 10, 10) // Quando invocado diretamente
E a função move será convertida para:
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:
function serializeWrapper() {
  return point.serialize()
}
E fornecer a versão empacotada:
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:
log(point.serialize(point))
Devemos tomar cuidado também quando formos definir funções inerentes:
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 dethis 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:
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
    
Top comments (0)