Você realmente entende as Funções em JavaScript?
Funções em JavaScript
Oi? Funções?!
Funções são uma daquelas coisas que nós mais usamos em nossa vida de desenvolvedor JavaScript. Algo que usamos no dia-a-dia, algo que sempre usaremos nesta linguagem, para os mais variados paradigmas.
Mas será que realmente conhecemos as entranhas delas? O funcionamento real e cada macete ao usar as funções em nosso dia-a-dia?!
Comportamento
Funções em uma instância, na Orientação a Objetos, são chamadas de _métodos_. Já quando passadas por parâmetro ou retornadas, encadeadas ou recursivas, vemos o paradigma funcional entrando em ação. No paradigma procedural, as funções podem ser chamadas e criadas sob demanda.
Funções podem ser _Globais_ ou pertencer a um _Escopo_, e por serem de objetos de primeira classe (first class objects) podem ser passadas por parâmetro ou retornadas por outra função/método.
Diversas linguagens tem suporte a First class objects, leia mais a respeito neste artigo sobre First class objects.
Uma linguagem que tem suporte a First Class Functions, ou function literal, permite que funções sejam criadas a qualquer momento, armazenadas em memória como uma variável qualquer e podem ser referenciadas como tal, até mesmo no retorno de funções.
Tipos de uma função
Uma função pode ser criada por instrução (ou expressão) ou por definição (ou declaração).
Quando a engine interpreta o código JavaScript, ela antes de tudo, procura por tokens válidos, entre eles, estão alguns tokens como var e function, no início de uma expressão.
Por exemplo:
Definição (declaration)
No caso de uma definição, isto causa o efeito de hoisting.
Para inteirar-se mais sobre o hoisting, você pode ler o artigo Hoisting no JavaScript no meu site pessoal, onde falo de maneira mais aprofundada sobre o assunto. Ou seja, a engine encontrará estas declarações, e definirá antes de tudo.
`\
js
console.log(func); // [Function] console.log(nome); // undefined console.log(idade); // Error
function func () { }
var nome = "Felipe"; `\
O que aconteceu?
a engine procura por tokens de variáveis
o token var é encontrado com
nome
a variável nome é criada na memória, com o valor undefined
o token function é encontrado
a função func é criada
agora sim...as instruções são executadas
console.log são chamados
No caso de idade
, a variável não existe, e isto causará um erro.
Há uma diferença entre uma variável não ter sido definida, o que causa um erro, e uma variável que foi declarada, mas tem um valor indefinido. Note que a variável nome
só recebe um valor na linha última linha. Até lá, seu valor segue como undefined.
Instrução (expression)
Quando uma função é criada dentro de uma instrução, ela não sofre o efeito de hoinsting, e passa a existir apenas quando a instrução é executada.
`\
js console.log(sayMyName); // undefined
var sayMyName = function sayingTheName(name) { console.log(sayingTheName); // [Function] return name; }; // note que aqui, o ; é permitido(e aconselhado)
console.log(sayMyName("Felipe")); // Felipe console.log(sayingTheName); // ERROR `\
Neste caso:
a engine não encontrou o token function no início de uma instrução
a engine criou a variável
sayMyName
com o valor de undefinedas instruções começam a ser executadas
sayMyName passa a ter uma função como valor
sayMyName("Felipe")
é executadoos comandos de log são executados dentro da função
sayingTheName
...note que aqui, esta função existe e podemos referenciar a ela pelo nome.sayingTheName
existe apenas aqui, dentro da própria função.a variável name é retornada
o console.log na linha executado na linha
8
é executadoo console.log da linha
9
é executado e aqui,sayingTheName
volta a ser undefined.
Vale notar que para que uma função ser criada em uma instrução, bas que seu token não seja o primeiro, desta instrução. Ou seja, qualquer coisa(válida) que venha antes de seu token, a tornará uma função a ser criada em tempo de execução.
Isto pode ser uma atribuição a uma variável(como no exemplo acima) ou uma instrução a ser feita com o valor retornado desta função.
Por exemplo:
js console.log('antes'); (function sayMyName(name){ }); console.log('depois');
Isto fez com que durante a execução das instruções (visto que o token function não foi encontrado para declarações), na linha 2
, a função fosse criada.
Mas ela nunca foi executada, e mesmo tendo um nome, ela nunca poderia ser invocada posteriormente, pois ela existe apenas dentro do escopo criado pelos (
e )
em sua volta.
Isto nos permite:
`\
js // executar esta instrução automaticamente (function sayMyName(name){ })("Felipe"); // usando () para invoca-la, passando seus parâmetros
//ou atribuí-la a uma variável var foo = (function sayMyName(name){ });
foo("Felipe"); // executamos foo, quando quiser-mos `\
Chamamos de IIFE (Immediately-Invoked Function Expression), uma função que é invocada imediatamente. Ben (Cowboy) Alman tem um artigo bem bacana (em inglês) sobre o assunto aqui. Quando usamos uma IIFE, nós podemos manipular o retorno desta função, também imediatamente. Podemos até mesmo abrir mão do uso dos ()
.
`\
js !function doingSomething () { return true; }(); // false
!!function doingSomething () { return true; }(); // true
-function doingSomething () { return 5; }(); // -5
+function doingSomething () { return 5; }(); // 5
(function doingSomething () { return 5; }()); // parênteses do lado de fora `\
Uma curiosidade sobre isto, é um erro que deveria ser lançado pela engine, quando não usamos os (), mas que em uma situação específica, não é lançado!
js function example (val){ return val * 2; }(); // Syntax ERRO function example (val){ return val * 2; }(123); // não dá erro
O exemplo em que não é disparado um erro, é porque, quando a engine encontra uma instrução entre parênteses, ela a trata como uma nova instrução.
Ou seja, para a engine, aquela segunda linha ali, na verdade seria interpretada assim:
js function example (val){ return val * 2; }; (123); // não faz nada, mas não está errado
A função example é uma declaração, e (123)
são uma segunda instrução, sem relação alguma com a anterior.
Acho que devemos agradecer isto ao fato de o ;
ser opcional!
Podemos também criar estas funções, de forma anônima, são as funções anônimas (ou funções literais).
js (function (val) { return val; })(123); // 123
Dica: Eu aconselho a usar nomes nas funções, sempre.
Isto vai ajudar em especial, na hora de depurar o teu código. O debugger consegue mostrar o nome da função no stack de erro. Caso sejam funções anônimas, ele mostrará uma lista de "anonymous function", dificultando a localização real do erro.
Escopo, Fluxo e Binding
Quando a engine efetivamente CRIA uma função, ela também a prepara, antes da execução. A este contexto, criado para a execução desta função, chamamos de closure.
Escopo
A engine criará um escopo onde a função terá suas instruções executadas. Quando a função é chamada, o processo citado acima será executado. Será feita uma procura por tokens de var
e function
, acontece o hoisting, etc... As variáveis e funções criadas nela, existirão e estarão acessíveis apenas dentro do escopo desta função, durante execução.
Fluxo
Em um browser, cada aba representa um processo, uma thread no processador.
Isto quer dizer que absolutamente tudo que há nesta aba é processado no mesmo processo (css, imagens, html, svg, javascript, etc).
A engine irá executar suas funções através de fluxos. Com um fluxo inicial, global.
Isto garante que as funções aconteçam sincronamente, uma depois da outra, usando os respectivos retornos.
Mas podemos iniciar novos fluxos, criando uma assincronicidade.
Para isto, usamos:
setTimeout / setInterval / setImmediate / requestAnimationFrame
Promises
WebWorkers
Ajax
eventListeners
No caso dos WebWorkers, o acesso ao DOM é um pouco limitado. Nas últimas versões dos browsers, eles podem manipular apenas WebGL, e em breve, Canvas.
Binding
Outra coisa que a engine faz(vide exceção abaixo), é atrelar um valor à execução de sua função, no this
. Por padrão, antes de executar a função, a engine atrela ao this:
undefined, se estiver em
strict mode
window, se não estiver em
strict mode
o elemento que disparou um evento do usuário (em eventListeners)
o objeto ao qual o método executado pertence
uma nova instância, quando
new
é usadoa instância ao qual o método executado pertence
um valor, explicitamente atrelado por meio de bind, apply e call
Para saber mais sobre o this e suas formas de acesso, leia o artigo Escopo, this e that no meu site pessoal.
A exceção está em arrow functions
. Mais detalhado na sessão sobre Arrow Functions.
Vamos ao código!
Em modo estrito:
js "use strict" console.log(this); // undefined
Sem modo estrito:
js console.log(this); // window
Em eventListeners:
js document.getElementById('someField').addEventListener('click', function(event){ console.log(this.value); // o valor do campo com id someField });
Em objetos:
`\
js var person = { name: "Felipe", sayMyName: function () { return this.name; } };
person.sayMyName(); // "Felipe" `\
Em instâncias:
`\
js function Person (name) { this.name = name; // this é a instância para o new Person
this.getName = function () { return this.name; // this, é a mesma instância };
}
var me = new Person("Felipe"); me.getName(); // "Felipe" `\
Note que aqui, quando getName
é chamada, a engine seta, atrela a este método, o this, como sendo a instância de Person.
Em Classes
`\
js class Person { constructor (name) { this.name = name; } getName () { return this.name; } }
let p = new Person("Felipe"); p.getName(); // "Felipe"
`\
Para aprofundar um pouco mais nos métodos que aplicam um valor ao this de uma função, tenho este artigo bastante detalhado sobre call, bind e apply.
Arrow Functions
As arrow functions
aparentam ser um syntax _sugar, mas na verdade elas são uma implementação diferente de função, na linguagem.
A engine cria as arrow functions sem atrelar a elas, um valor para o this. Leia mais sobre arrow functions no artigo Arrow Functions and their scope que escrevi para o <jsrockrs.org>.
Em sua sintaxe, não precisamos do token function, por consequência, ela não pode ser declarada/definida.
As arrow function também não usam nome, e nelas, os parênteses e as chaves podem ser opcionais, assim como o token return
.
`\
js // modo tradicional var f = function (v1, v2) { return v1 - v2; }; f(5, 1); // 4
// com arrow function var f = (v1, v2) => v1 - v2; f(5, 1); // 4
// com um único parâmetro e apenas retorno var toUpper = (str) => str.toUpperCase();
`\
Em instâncias: `\
js function Person (name) { this.name = name; // this é a instância para o new Person
this.getName = () => { return this.name; // leia abaixo };
}
var me = new Person("Felipe"); me.getName(); // "Felipe" `\
Na linha 5
, o this vai funcionar, referenciando o mesmo this da instância, porém, o próprio método getName não tem um this que seja seu próprio. A engine não especificou um this para ele, por tanto, ele tenta acessar o this mais próximo em seu escopo, que neste caso, é a instância.
Mas quando nós saímos do fluxo, as coisas complicam um pouco mais.
`\
js function Person (name) { this.name = name; // this é a instância para o new Person
this.getName = () => { setTimeout(function () { console.log(this); }, 1000); };
}
var me = new Person("Felipe"); me.getName(); // depois de 1 segundo // Window `\
Após 1 segundo, em um novo fluxo, a engine cria aquela função e, especifica nela, o this atual que, neste caso, não existe! Ele não foi definido na função getName, por tanto, a engine usará Window (ou undefined, se em strict mode).
`\
js function Person (name) { this.name = name; // this é a instância para o new Person
this.getName = () => { setTimeout(()=>{ console.log(this); }, 1000); };
}
var me = new Person("Felipe"); me.getName(); // depois de 1 segundo // Person {name: "Felipe"} `\
Neste caso, quando a engine for executar a função passada ao setTimeout, ela novamente, não especificará quem é o this dela, por tanto, o this acessado aqui, trata-se do mesmo this acessado na getName, o this da própria instância.
Para arrow functions, não é possível usar bind, call ou apply.
Concluindo
Funções são fundamentais no desenvolvimento com JavaScript, e é importante conhecermos elas e compreender realmente como elas funcionam.
Apesar de ser um tema que todos pensamos de cara "ah, isto eu já sei", sempre há um ou outro detalhe que acabamos usando sem compreender de verdade.
Em um próximo artigo, vamos discutir sobre as novas formas de usar os parâmetros nas funções!
Se tu leu até aqui, meus parabéns! Deixe tua opinião nos comentários para iniciarmos uma conversa sobre o assunto!