JavaScript: Introdução e funções de alta ordem

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:

Apenas as strings com acento grave (`) permitem interpolação de strings:

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).

Tópico opcional: diferença entre let e var

JavaScript originalmente não permitia declarar variáveis com escopo de bloco; assim, todas as declarações de variáveis feitas com var são movidas para o início da função, o que é conhecido como hoisting.

Alguns exemplos (experimente trocar var por let e vice-versa e veja como o comportamento muda):

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:

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:

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:

Ou a função forEach, para iterar sobre os elementos:

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:

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; )

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):

map

Use a função map para mapear o array em um novo array contendo somente os títulos dos filmes

filter

Use a função filter para selecionar apenas os filmes com notas superior a 4.

map e filter

Agora combine map e filter para retornar apenas os títulos dos filmes com nota superior a 4.

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 valorInicial não for passado, a função assume que o valor do inicial do acumulador é igual ao valor do primeiro elemento de arr.
  • f é uma função que recebe dois parâmetros:
    • acum: o valor atual do acumulador
    • x: o elemento sendo processado atualmente no array
    • (na verdade a função f recebe 4 argumentos, mas vamos ignorar os outros dois. Se quiser mais detalhes, clique aqui)
  • o retorno da função f é atribuído ao acumulador para ser usado na próxima invocação de f
  • reduce retorna 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:

A função pode ser escrita de forma mais compacta usando o reduce:

Exemplo para somar as notas dos filmes:

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.

(Você também pode tentar calcular a maior nota sem usar funções de alta ordem. O código será muito mais longo.)

Referências