JavaScript: funções de alta ordem e escopo
JavaScript (JS) é uma linguagem de programação dinâmica, de tipificação fraca e interpretada. Ela foi criada em 1995 pela Netscape Communications, empresa que desenvolveu um dos primeiros navegadores web. Mais tarde, a linguagem foi formalizada na especificação ECMAScript (ES), que vem evoluindo desde então. No momento em que este texto foi escrito, a versão mais recente da especificação é a ECMAScript 2016 (anteriormente conhecida como ECMAScript 7), mas mesmo o ECMAScript 2015, ou ECMAScript 6 não é suportado completamente por todos os navegadores. Projetos como o Babel permitem transformar código de uma versão mais recente para uma versão mais antiga da especificação.
Estruturas básicas
Strings
Strings podem ser escritas com ", ' ou `. Em qualquer caso, você pode usar a barra invertida (\) para “escapar” um caractere que do contrário teria um significado especial. Exemplo:
function(str, info) { return simplesEval(str, info); }
Apenas as strings com acento grave (`) permitem interpolação de strings:
function(str, info) { return simplesEval(str, info); }
Se o let e o ` não funcionarem, seu navegador pode estar desatualizado. Você pode conseguir rodar trocando por var e " (embora o resultado não seja exatamente igual).
Objetos
Objetos em JavaScript são estruturas chave-valor, similar a estruturas que em outras linguagens são chamadas de hash, mapa ou dicionário. As chaves são chamadas propriedades do objeto. Exemplo:
function(str, info) { return simplesEval(str, info); }
Para saber mais: Working with objects (Mozilla Developer Network).
Arrays
Um array em Javascript (ex.: [1, 2, 3]) é um objeto mutável, isto é, ele pode ser modificado. As funções que modificam o array são chamadas de funções destrutivas. As funções não-destrutivas são aquelas que não modificam o array; em vez disso, elas retornam um novo array que é construído a partir de um array pré-existente.
Do ponto de vista das linguagens funcionais, uma função deve apenas receber valores como parâmetro e retornar um valor. Se a função modifica algum parâmetro, altera variáveis globais, ou acessa entrada/saída (ex.: modifica um arquivo), esses comportamentos são considerados efeitos colaterais de se chamar a função, e a função é dita não-pura.
Uma função pura, sem efeitos colaterais, vai sempre retornar o mesmo resultado para uma determinada entrada, não importa quantas vezes a função seja chamada.
A seguir, algumas operações sobre arrays. Nos exemplos, considere que a é um array.
| Operação | destrutiva | não-destrutiva |
|---|---|---|
| Obter primeiro elemento | x = a[0] |
|
| Obter restante da lista | a.shift() |
l = a.slice(1) |
| Adicionar x ao final | a.push(x) |
l = a.concat([x]) |
| Comprimento do array | x = a.length |
|
| Criar uma cópia da lista | l = a.slice() |
Funções como cidadãos de primeira classe
Como já vimos, JavaScript é uma linguagem na qual funções são cidadãos de primeira classe, isto é, elas podem ser atribuídas a variáveis, passadas como parâmetro e retornadas de outras funções.
Exemplo:
function cumprimenta() {
alert('Oi, tudo bom?');
}
let botao = document.getElementById('botao1');
botao.addEventListener('click', cumprimenta);
Nesse exemplo, a função cumprimenta foi passada como parâmetro para a função addEventListener (tecnicamente, addEventListener é um método – já que JavaScript é orientada a objetos – mas na prática é muito semelhante a uma função).
Funções de alta ordem
Exemplo de função que recebe função como parâmetro, executando a função 3 vezes, cada vez passando um número:
function(str, info) { return simplesEval(str, info); }
Note que, desta vez, definimos uma função anônima, function (x) { console.log('Dou-lhe ' + x); }.
JavaScript já define algumas funções de alta ordem importantes, como a função map:
function(str, info) { return simplesEval(str, info); }
Ou a função forEach, para iterar sobre os elementos:
function(str, info) { return simplesEval(str, info); }
Funções de alta ordem em JavaScript para arrays: forEach, filter, map, reduce, some, every, find, findIndex.
Funções anônimas (sintaxe nova)
Há uma notação compacta para definir funções anônimas: x => x * 2 (função que recebe um argumento, x e retorna o seu dobro). Outros exemplos:
(x, y) => x + y: função soma (para funções com dois ou mais argumentos, eles precisam ser envoltos em parênteses)() => 42: função que não recebe argumentos e sempre retorna 42.x => { console.log(x); console.log(x + 1); }: função sucessor com impressão no console (para rodar duas ou mais instruções, envolva-as com chaves; ao usar chaves, nenhum valor é retornado)(n, a) => ({nome: n, altura: a}): função que retorna um objeto construído a partir de dois parâmetros (para retornar um objeto/hash, envolva as chaves em parênteses, para evitar ambiguidade com o caso anterior)
(Experimente converter essa notação para JavaScript antigo)
Exemplo:
function(str, info) { return simplesEval(str, info); }
Exercícios
map
Implemente a função meuMap em JavaScript:
(Duas implementações possíveis: criando um array e modificando-o com operações destrutivas, ou usando apenas operações não-destrutivas; )
function(str, info) { return simplesEval(str, info); }
Filmes
(Baseado em http://reactivex.io/learnrx/.)
Nos exemplos a seguir, considere os seguintes dados (execute o código para carregar a variável filmes na memória):
function(str, info) { return simplesEval(str, info); }
map
Use a função map para mapear o array em um novo array contendo somente os títulos dos filmes
function(str, info) { return simplesEval(str, info); }
filter
Use a função filter para selecionar apenas os filmes com notas superior a 4.
function(str, info) { return simplesEval(str, info); }
map e filter
Agora combine map e filter para retornar apenas os títulos dos filmes com nota superior a 4.
function(str, info) { return simplesEval(str, info); }
reduce
A função reduce aplica uma função dada a um acumulador e cada elemento do array para reduzir o array a um único valor. A sintaxe é:
arr.reduce(f, valorInicial), onde
valorInicialé o valor inicial do acumulador- se
valorInicialnão for passado, a função assume que o valor do inicial do acumulador é igual ao valor do primeiro elemento dearr.
- se
fé uma função que recebe dois parâmetros:acum: o valor atual do acumuladorx: o elemento sendo processado atualmente no array- (na verdade a função
frecebe 4 argumentos, mas vamos ignorar os outros dois)
- o retorno da função
fé atribuído ao acumulador para ser usado na próxima invocação def reduceretorna o valor final do acumulador
Por exemplo, considere a função somatório, que retorna o somatório dos elementos de um array, escrito da forma tradicional, com for:
function(str, info) { return simplesEval(str, info); }
A função pode ser escrita de forma mais compacta usando o reduce:
function(str, info) { return simplesEval(str, info); }
Exemplo para somar as notas dos filmes:
function(str, info) { return simplesEval(str, info); }
reduce (máximo)
Agora use reduce para retornar a maior nota do conjunto.
Dica: use o operador ternário ?:. A expressão condicao ? x : y retorna x se a condicao for verdadeira, e y caso contrário.
function(str, info) { return simplesEval(str, info); }
(Você também pode tentar calcular a maior nota sem usar funções de alta ordem. O código será muito mais longo.)