A collaborative website about the latest JavaScript features and tools.
Send a Post

*using github

Felipe N. Moura
Posted by
Wed Oct 01 2014 04:01:41 GMT+0000 (UTC)

Arrow Functions and their scope

Entre as tantas novas features presentes no ES6, Arrow Functions (ou Fat Arrow Functions), é uma que merece boa atenção! É muito legal, ótima para trabalhar com escopos, serve como "atalho" para algumas tecnicas que utilizamos hoje, diminui o código... Mas pode ser um pouco mais difícil de ler caso não esteja a par de como ela funciona. Então, vamos mergulhar no assunto!

Começando

Para estudar e tester por você mesmo, você pode simplesmente copiar alguns dos exemplos deste artigo, e colar no console de seu browser. Até o momento, você pode usar o Firefox(22+) Developer Tools, que já oferece suporte a arrow functions. No Google Chrome, você precisará habilita-lo:

  • - Habilite: Vá até "about:flags", e habilite a opção "Experimental JavaScript"
  • - Use sempre em uma função em strict mode, por tanto, para rodar no console do Google Chrome, use:
(function(){
    "use strict";
    // use arrow functions aqui
}());

Com tempo, felizmente, mais browsers suportarão as features do ES6. Agora que tudo está preparado, vamos começar!

Um novo Token

Um novo Token foi adicionado ao ES6, e é chamado "fat arrow", representado por:

=>

A nova sintaxe

Com este novo token, entra uma nova sintaxe:

param => expression

Sintaxe a qual podemos usar algumas variações, de acordo com o número de statements na expressão, e número de parâmetros passados para a função:

// single param, single statement
param => expression;

// multiple params, single statement
(param [, param]) => expression;

// single param, multiple statements
param => {
    statements;
}

// multiple params, multiple statements
([param] [, param]) => {
    statements
}

// with no params, single statement
() => expression;

// with no params
() => {
    statements;
}

// one statement, returning an object
([param]) => ({ key: value });

Como funciona

Se fossemos "traduzir" arrow functions para algo que já usamos hoje em dia, seria algo assim:

// esta função
var func = function (param) {
    return param.split(" ");
}

// se tornaria:
var func= param => param.split(" ");

Isto quer dizer que esta sintaxe realmente retorna uma nova função, com os parâmetros e statements. Ou seja, podemos chamar esta função do mesmo modo que já estamos acostumados:

func("Felipe Moura"); // retorna ["Felipe", "Moura"]

Immediately-invoked function expression (IIFE)

Sim, você pode invocar funções imediatamente, já que elas são na verdade, expressões.

( x => x * 2 )( 3 ); // 6

Uma função será criada. Esta função recebe o parâmetro x, e retorna x * 2, então, é imediatamente executada passando o valor 3 como parâmetro.

Caso tenha mais statements ou parâmetros:

( (x, y) => {
    x= x * 2;
    return x + y;
})( 3, "A" ); // "6A"

Considerações Relevantes

Considerando:

var func = x => {
    return x++;
};

Podemos apontar algumas considerações relevantes:

- arguments não será criado ou definido para sua função

console.log(arguments); // not defined

- typeof e instanceof também

func instanceof Function; // true
typeof func; // function
func.constructor == Function; // true

- Usando parênteses internos, como sugerido pelo jsLint, NÃO funciona

// funciona, como sugerido pelo JSLint
(function (x, y){
    x= x * 2;
    return x + y;
} (3, "B") );

// não funciona
( (x, y) => {
    x= x * 2;
    return x + y;
} ( 3, "A" ) );

// mas funcionaria, se a última linha fosse
// })(3, "A");

- Apesar de ser uma função, não funciona como um Constructor

var instance= new func(); // TypeError: func is not a constructor

- Não tem um prototype

func.prototype; // undefined

Escopo

O this no escopo de arrow functions funciona de uma forma diferente. No modo como estamos acostumados, this pode referenciar-se a: window (se for acessado globalmente), undefined (se acessado globalmente, em strict mode), uma instância (se em um construtor), um objeto (se for um método ou função dentro de um objeto ou instância) ou em um .bind/.apply. Pode ser também um DOMElement, por exemplo, quando usado em um addEventListener. Algumas vezes, isto incomoda bastante, ou pode até mesmo nos pegar de surpresa e causar algum problema! Além disso, this é referenciado como "scope-by-flow" (fluxo-escopo). O que quero dizer com isto?

Vejamos primero, como this se comporta em diferentes situações:

Em um EventListener:

document.body.addEventListener('click', function(evt){
    console.log(this); // o próprio HTMLBodyElement
});

Em instâncias:

function Person () {
    let fullName = null;
    this.getName = function () {
        return fullName;
    };
    this.setName = function (name) {
        fullName = name;
        return this;
    };
}

let jon = new Person();
jon.setName("Jon Doe");
console.log(jon.getName()); // "Jon Doe"

Nesta situação em particular, uma vez que Person.setName é "chainable" (retornando a própria instância), poderíamos também usar assim:

jon.setName("Jon Doe")
   .getName(); // "Jon Doe"

Em um objeto:

let obj = {
    foo: "bar",
    getIt: function () {
        return this.foo;
    }
};

console.log( obj.getIt() ); // "bar"

Mas então, vem a situação que citei acima, do formato escopo/fluxo. Se tanto o fluxo ou o escopo mudam, this muda com ele.

function Student(data){

    this.name = data.name || "Jon Doe";
    this.age = data.age>=0 ? data.age : -1;

    this.getInfo = function () {
        return this.name + ", " + this.age;
    };

    this.sayHi = function () {
        window.setTimeout( function () {
            console.log( this );
        }, 100 );
    }

}

let mary = new Student({
    name: "Mary Lou",
    age: 13
});

console.log( mary.getInfo() ); // "Mary Lou, 13"
mary.sayHi();
// window

Uma vez que setTimeout muda o fluxo da execução, a referência ao this se torna a referência "global" (neste caso, window), ou undefined se em "strict mode". Por causa disto, acabamos usando algumas técnicas como o uso de variáveis como "self", "that", ou alguma coisa assim, ou tendo que usar .bind.

Mas não se preocupe, arrow functions estão aqui para ajudar! Com arrow functions, o escopo é mantido com ela, de onde ela foi chamada.

Vejamos o MESMO exemplo acima, mas usando arrow function, passada para a chamada do _setTimeout.

function Student(data){

    this.name = data.name || "Jon Doe";
    this.age = data.age>=0 ? data.age : -1;

    this.getInfo = function () {
        return this.name + ", " + this.age;
    };

    this.sayHi = function () {
        window.setTimeout( ()=>{ // a única diferença está aqui
            console.log( this );
        }, 100 );
    }

}

let mary = new Student({
    name: "Mary Lou",
    age: 13
});

console.log( mary.getInfo() ); // "Mary Lou, 13"
mary.sayHi();
// Object { name: "Mary Lou", age: 13, ... }

Abordagens úteis e interessantes

Já que é muito fácil criar arrow functions, e seus escopos funcionam como o mensionado, podemos usá-las de várias formas.

Por exemplo, podemos usa-las diretamente na chamada de um forEach, em uma Array:

var arr= ['a', 'e', 'i', 'o', 'u'];
arr.forEach(vowel => {
console.log(vowel);
});

Ou em um Array#map:

var arr= ['a', 'e', 'i', 'o', 'u'];
arr.map(vogal => {
    return vogal.toUpperCase();
});
// [ "A", "E", "I", "O", "U" ]

Ou em uma função recursiva:

var fatorial = (n) => {
    if(n==0) {
        return 1;
    }
    return (n * fatorial (n-1) );
}

fatorial(6); // 720

Também, digamos, ordenando uma Array de trás para frente:

let arr= ['a', 'e', 'i', 'o', 'u'];
arr.sort( (a, b)=> a < b? 1: -1 );

Ou em listeners:

document.body.addEventListener('click', event=>console.log(event, this)); // EventObject, BodyElement

Aqui, pegue alguns links úteis para dar uma olhada:

Conclusão

Mesmo que Arrow Functions tornem seu código um pouco mais complicado de ler (mas que você acaba se acostumando rápido), é uma grande solução para referências ao this em escopos e fluxos diferentes, um modo rápido para colocar as coisas para funcionar, e em parceria com o keyword let, levará nosso JavaScript para um próximo nível! Experimente você mesmo, crie alguns testes, rode em seus browsers e deixe alguma solução ou uso interessante que encontrou, nos comentários! Espero que tenha apreciado este artigo tanto quanto apreciará utilizar arrow functions em um futuro muito próximo!

icon comments

Comments

comments powered by Disqus