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)