Javascript: Isto, aquilo e outras coisas

Tweet Like

Eu falei anteriormente sobre a variável especial this do Javascript. E eu disse que ela area mais complicada do que parece.

Agora, o this parece um conceito direto. Ele se refere o objeto que "é dono" da função, porém descobrir o que "ser dono" significa pode ser meio complicado.

Como vimos anteriormente, se a função for parte do objeto, então o objeto é o seu dono. Se não for parte de um objeto, então o objeto global (window nos navegadores) é o seu dono. Por exemplo:

> function f() {return this}
> f() == window
true
> o = {f: function f() {return this} }
> o.f() == window
false
> o.f() == o
true

See the Pen %= penName %> by Intel IDZ (@intelidz) on CodePen

E se não escrevermos o corpo da função no objeto, como isso aqui ?:

> o = {f:f}
> o.f() == window
false
> o.f() == o
true

See the Pen %= penName %> by Intel IDZ (@intelidz) on CodePen

Isto funciona como esperado. O Javascript essencialmente copia a função para o objeto, ou do lado da chamada ele considera quando está sendo chamada de um objeto ou não. É agora que as coisas ficam confusas. Suponha que dentro de f() nós tenhamos uma função aninhada g() que retorna this. Num primeiro momento, pode-se imaginar que ela irá herdar this da sua função pai, a não ser que tenha sido sobrescrito sendo um objeto local:

> function f() {
...   function g(){
...       return this;
...   }
...   return g()
...}
> o = {f:f}
> o.f() == window
true
> o.f() == o
false

See the Pen %= penName %> by Intel IDZ (@intelidz) on CodePen

Infelizmente isso não é o que você estava esperando. De fato o seu padrão é o objeto global, mesmo que ela não seja uma função disponível globalmente. Porque isso ? Bem, na verdade faz sentido quando você volta atrás um pouquinho. Acredito que a confusão vem ao pensar na função f() como uma classe, enquanto se pensa em g() como um membro, mas lembre-se: isso não é C++. Na verdade, tanto f() quanto g() são funções, e cada função tem um this (e arguments, mas volto a isso depois). Não existe nada especial sobre uma função aninhada, ele continua a precisar de uma variável this, baseada em como é chamada, e como qualquer outra função, se não for chamada de dentro de um objeto, terá como padrão o objeto global window. Quando você pensa desta forma, não faz nenhum sentido para as funções internas herdar o ponteiro this mais do que faria herdar a lista de argumentos. O this da função interna compartilha o mesmo nome e tem prioridade, então não existe uma forma de acessar o this da função externa. No entanto seria útil ter acesso a ele em muitas circunstâncias.

Bem, eu não sou a única pessoa que tem esperado alguma coisa como isso, e na verdade existe um pattern padrão que trata desta situação. Enquanto this não é herdado no escopo da função aninhada, variáveis locais são. Tudo o que você precisa fazer é atribuir this a uma variável local antes de chamar a função aninhada que precisa de acesso ao this. Por convenção, esta variável é comumente chamada de that:

> function f() {
...   var that = this;
...   function g(){
...       return that;
...   }
...   return g();
...}
> o = {f:f}
> o.f() == window
false
> o.f() == o
true

See the Pen %= penName %> by Intel IDZ (@intelidz) on CodePen

Mas claro que a história é ainda mais complicada quando você cava ainda mais fundo. Há pouco tempo atrás, eu mencionei que eu gostava de ler o código C que um compilador inicial de C++ produzia. Quando eu olhava o código C de uma função membro, o que eu via era uma função plana e antiga de C com um nome mutilado, mas ao invés de encontrar parâmetros esperados sendo passados, existia um parâmetro extra no início, correspondendo ao ponteiro this. É o mesmo em Javascript. Não existe mágica, o this é passado como o primeiro parâmetro para cada função, é normalmente determinado pelo sistema de runtime, e tipicamente ele se torna o objeto default se não houver nenhum outro dono. Acontece que no Javascript, se você estiver mesmo desesperado, existem outras formas de controlar o valor de this do lado da chamada, ao invés de deixar isso a cargo do sistema de runtime.

Como qualquer outra coisa em Javascript, funções são objetos, e elas possuem propriedades. Como matrizes, que possuem uma propriedade especial length, objetos Function possuem algumas propriedades especiais pré definidas. Em particular elas tem uma propriedade call, que pode ser usada para tornar este primeiro parâmetro oculto, explícito. Quando você invoca a propriedade call de uma função, é como chamar a função, mas você provê um parâmetro inicial extra que a função recebe como a variável this. Existem alguns alertas importantes, e um deles é que o runtime realmente quer um objeto ali, portanto em outros contextos ele vai criar um se você não fornecer um objeto explicitamente. Por exemplo, se você passar o número 3 como seu primeiro parâmetro, ele irá colocar um objeto Número ali, mas eu prefiro acreditar que você pode fazer mais do que isso. (E se você tiver mesmo empolgado, dê uma olhada nas propriedades apply e bind, mas não vou falar delas agora.)

Se você evitar passar qualquer coisa para call() (ou se você passar undefined), o runtime ainda irá inserir um valor padrão, o objeto global (window nos navegadores), senão qualquer valor que você colocar primeiro na linha será o valor this:

> function f() {return this;}
> f()
true
> f.call() == window
true
> f.call("hello") // actually creates a string object
[object String]
> o = f.call({a:1})
> o.a
1

See the Pen %= penName %> by Intel IDZ (@intelidz) on CodePen

Se você ainda está lendo neste ponto, você deve estar imaginando que mesmo sendo fascinante, tudo isso parece um pouco esotérico e confuso. Mas se você está escrevendo aplicativos em HTML5, existe ao menos um caso comum onde você pode encontrar esta confusão com maior frequência.

Quando eu comecei a brincar com interação simples entre apps em HTML e páginas, eu não conseguia entender direito quando eu podia usar o this de forma confiável ou não. Eu queria fazer alguma coisa como isso:

function turnRed() {
    this.style.color = "red";
}
b = document.getElementById('clickme');
b.onclick = turnRed;

See the Pen %= penName %> by Intel IDZ (@intelidz) on CodePen

<div id="clickme">Click Me!</div>

See the Pen %= penName %> by Intel IDZ (@intelidz) on CodePen

E eu clicava e o texto ficava vermelho, tudo perfeito no universo. Em outros momentos, eu fazia isso:

<div onclick="turnRed()">Click Me!</div>

See the Pen %= penName %> by Intel IDZ (@intelidz) on CodePen

E eu clicava e nada acontecia. As vezes eu trocava o código para 'onclick="turnRed(this)"' e então eu tinha que alterar a função turnRed para receber um argumento ao invés de usar o this e tudo funcionava, mas eu ficava meio confuso. Acredito que agora fica óbvio o que estava acontecendo. Quando b.onclick é atribuído a função turnRed, o seu dono é b, que é o elemento div que nos interessa, então this.style significa alguma coisa. Quando a função é chamada através do atributo onclick, é como chama-la sem nenhum objeto dono, portanto ela adotará como padrão o objeto global window, que não possui nenhum atributo style. Juntando tudo isso, se eu realmente quiser usar o atributo onclick do HTML sem bagunçar com a minha função, eu posso fazer isso:

<div onclick="turnRed.call(this)">Click Me!</div>

See the Pen %= penName %> by Intel IDZ (@intelidz) on CodePen

Finalmente uma outra coisa. Existe uma outra variável especial que toda função possui e, como this, ela não é herdada por nenhuma função aninhada: a variável arguments. Para cada chamada de função, além da variável this que descreve o dono da função, a variável arguments descreve os argumentos que foram passados para a função, independente dos parâmetros formais. Ela pode ser usada quando você quer um número variável de argumentos em uma função, e portanto você pode usar este objeto para acessar todos os argumentos que foram passados na chamada da função, mesmo que existam mais ou menos argumentos do que a lista formal. Se quiser acessar este objeto de dentro de uma função aninhada, você vai precisar copia-lo para uma variável local, como foi feito com 'that = this'. Você pode chama-la de qualquer coisa (o que é razoável), mas args é provavelmente uma boa sugestão.

Reportez-vous à notre Notice d'optimisation pour plus d'informations sur les choix et l'optimisation des performances dans les produits logiciels Intel.