Funções puras
Uma função que atenda aos dois requisitos a seguir é chamada de pura:
- Sempre, quando chamado com os mesmos argumentos, retorna o mesmo resultado.
- Nenhum efeito colateral ocorre quando a função é executada.
Vamos considerar um exemplo:
function circleArea(radius){
return radius * radius * 3.14
}
Se esta função receber o mesmo valor
radius, sempre retornará o mesmo resultado. Ao mesmo tempo, durante a execução da função, nada fora dela muda, ou seja, não tem efeitos colaterais. Tudo isso significa que esta é uma função pura.
Aqui está outro exemplo:
let counter = (function(){
let initValue = 0
return function(){
initValue++;
return initValue
}
})()
Vamos tentar essa função no console do navegador.

Testando a função no console do navegador
Como você pode ver, a função
counterque implementa o contador retorna resultados diferentes cada vez que é chamada. Portanto, não pode ser chamado de puro.
E aqui está outro exemplo:
let femaleCounter = 0;
let maleCounter = 0;
function isMale(user){
if(user.sex = 'man'){
maleCounter++;
return true
}
return false
}
Aqui é mostrada uma função
isMaleque, quando passada no mesmo argumento, sempre retorna o mesmo resultado. Mas tem efeitos colaterais. Ou seja, estamos falando sobre como alterar uma variável global maleCounter. Como resultado, essa função não pode ser chamada de pura.
▍ Por que funções puras são necessárias?
Por que traçamos a linha entre funções regulares e puras? A questão é que funções puras têm muitos pontos fortes. Seu uso pode melhorar a qualidade do código. Vamos falar sobre o que o uso de funções puras nos oferece.
1. O código de funções puras é mais claro do que o código de funções comuns, é mais fácil de ler
Cada função pura visa uma tarefa específica. Ele, chamado com a mesma entrada, sempre retorna o mesmo resultado. Isso melhora muito a legibilidade do código e facilita sua documentação.
2. Funções puras se prestam melhor à otimização ao compilar seu código
Suponha que você tenha um trecho de código como este:
for (int i = 0; i < 1000; i++){
console.log(fun(10));
}
Se
fun- esta é uma função que não é pura, então durante a execução deste código, esta função deverá ser chamada fun(10)1000 vezes.
E se
funfor uma função pura, o compilador pode otimizar o código. Pode ser algo assim:
let result = fun(10)
for (int i = 0; i < 1000; i++){
console.log(result);
}
3. Funções puras são mais fáceis de testar
Os testes de função pura não devem ser sensíveis ao contexto. Ao escrever testes de unidade para funções puras, eles simplesmente passam alguns valores de entrada para tais funções e verificam o que eles retornam em relação a certos requisitos.
Aqui está um exemplo simples. A função pura pega um array de números como argumento e adiciona 1 a cada elemento desse array, retornando um novo array. Aqui está uma representação abreviada:
const incrementNumbers = function(numbers){
// ...
}
Para testar tal função, basta escrever um teste de unidade semelhante ao seguinte:
let list = [1, 2, 3, 4, 5];
assert.equals(incrementNumbers(list), [2, 3, 4, 5, 6])
Se uma função não estiver limpa, para testá-la, é necessário levar em consideração muitos fatores externos que podem afetar seu comportamento. Como resultado, testar essa função será mais difícil do que testar uma função pura.
Funções de ordem superior.
Uma função de ordem superior é aquela que tem pelo menos um dos seguintes recursos:
- É capaz de aceitar outras funções como argumentos.
- Ele pode retornar uma função como resultado de seu trabalho.
O uso de funções de ordem superior permite aumentar a flexibilidade de seu código, ajudando a escrever programas mais compactos e eficientes.
Digamos que haja uma matriz de inteiros. É necessário criar em sua base um novo array do mesmo comprimento, mas tal, cada elemento do qual representará o resultado da multiplicação do elemento correspondente do array original por dois.
Se você não usar os recursos das funções de ordem superior, a solução para esse problema pode ser assim:
const arr1 = [1, 2, 3];
const arr2 = [];
for (let i = 0; i < arr1.length; i++) {
arr2.push(arr1[i] * 2);
}
Se você pensar sobre o problema, descobre que os objetos de tipo
Arrayem JavaScript têm um método map(). Este método é denominado como map(callback). Ele cria um novo array preenchido com os elementos do array para o qual é chamado, processado com a função passada a ele callback.
É assim que a solução para esse problema usando o método se parece
map():
const arr1 = [1, 2, 3];
const arr2 = arr1.map(function(item) {
return item * 2;
});
console.log(arr2);
Método
map()é um exemplo de função de ordem superior.
O uso correto de funções de ordem superior ajuda a melhorar a qualidade do seu código. Nas próximas seções deste material, retornaremos a essas funções mais de uma vez.
Resultados da função de cache
Digamos que você tenha uma função pura semelhante a esta:
function computed(str) {
// ,
console.log('2000s have passed')
// ,
return 'a result'
}
Para melhorar o desempenho do código, não nos fará mal recorrer ao cache dos resultados dos cálculos realizados na função. Ao chamar essa função com os mesmos parâmetros com os quais já foi chamada, não será necessário realizar os mesmos cálculos novamente. Em vez disso, seus resultados, previamente armazenados no cache, serão retornados imediatamente.
Como equipar uma função com cache? Para fazer isso, você pode escrever uma função especial que pode ser usada como um wrapper para a função de destino. Daremos um nome a essa função especial
cached. Esta função recebe uma função objetiva como argumento e retorna uma nova função. Em uma função, cachedvocê pode organizar o armazenamento em cache dos resultados de uma chamada para a função agrupada em torno dela usando um objeto regular ( Object) ou usando um objeto que é uma estrutura de dadosMap...
Esta é a aparência do código da função
cached:
function cached(fn){
// , fn.
const cache = Object.create(null);
// fn, .
return function cachedFn (str) {
// - fn
if ( !cache[str] ) {
let result = fn(str);
// , fn,
cache[str] = result;
}
return cache[str]
}
}
Aqui estão os resultados de experiências com esse recurso no console do navegador.

Experimentar uma função cujos resultados são armazenados em cache
Funções preguiçosas
Em corpos de função, geralmente existem algumas instruções para verificar algumas condições. Às vezes, as condições que correspondem a eles precisam ser verificadas apenas uma vez. Não faz sentido verificá-los sempre que a função é chamada.
Nessas circunstâncias, você pode melhorar o desempenho da função "excluindo" essas instruções após sua primeira execução. Como resultado, verifica-se que a função, durante suas chamadas subsequentes, não terá que realizar verificações que não serão mais necessárias. Esta será a função "preguiçosa".
Suponha que desejamos escrever uma função
fooque sempre retorna o objeto Datecriado na primeira vez que a função é chamada. Observe que precisamos de um objeto que foi criado exatamente quando a função foi chamada pela primeira vez.
Seu código pode ser assim:
let fooFirstExecutedDate = null;
function foo() {
if ( fooFirstExecutedDate != null) {
return fooFirstExecutedDate;
} else {
fooFirstExecutedDate = new Date()
return fooFirstExecutedDate;
}
}
Cada vez que esta função é chamada, a condição deve ser verificada. Se essa condição for muito difícil, as chamadas para essa função levarão a uma queda no desempenho do programa. É aqui que podemos usar a técnica de criação de funções "preguiçosas" para otimizar o código.
Ou seja, podemos reescrever a função da seguinte maneira:
var foo = function() {
var t = new Date();
foo = function() {
return t;
};
return foo();
}
Após a primeira chamada para a função, substituímos a função original pela nova. Esta nova função retorna o valor
trepresentado pelo objeto Datecriado na primeira vez que a função foi chamada. Como resultado, nenhuma condição precisa ser verificada ao chamar tal função. Essa abordagem pode melhorar o desempenho do seu código.
Este foi um exemplo condicional muito simples. Vamos agora olhar para algo mais próximo da realidade.
Ao anexar manipuladores de eventos a elementos DOM, você precisa realizar verificações para garantir que a solução seja compatível com navegadores modernos e com o IE:
function addEvent (type, el, fn) {
if (window.addEventListener) {
el.addEventListener(type, fn, false);
}
else if(window.attachEvent){
el.attachEvent('on' + type, fn);
}
}
Acontece que toda vez que chamamos uma função
addEvent, uma condição é verificada nela, o que é suficiente para verificar apenas uma vez, quando ela é chamada pela primeira vez. Vamos tornar esta função "preguiçosa":
function addEvent (type, el, fn) {
if (window.addEventListener) {
addEvent = function (type, el, fn) {
el.addEventListener(type, fn, false);
}
} else if(window.attachEvent){
addEvent = function (type, el, fn) {
el.attachEvent('on' + type, fn);
}
}
addEvent(type, el, fn)
}
Como resultado, podemos dizer que se uma determinada condição for verificada em uma função, que deve ser realizada apenas uma vez, então, aplicando a técnica de criação de funções "preguiçosas", você pode otimizar o código. Ou seja, a otimização consiste no fato de que após a primeira verificação da condição, a função original é substituída por uma nova, na qual não há mais verificação das condições.
Funções de currying
Currying é a transformação de uma função, após a aplicação da qual uma função que tinha que ser chamada antes de passar vários argumentos para ela de uma vez se transforma em uma função que pode ser chamada passando os argumentos necessários um de cada vez.
Em outras palavras, estamos falando sobre o fato de que uma função curried, que requer vários argumentos para funcionar corretamente, é capaz de aceitar o primeiro deles e retornar uma função que é capaz de receber um segundo argumento. Essa segunda função, por sua vez, retorna uma nova função que recebe um terceiro argumento e retorna uma nova função. Isso continuará até que o número necessário de argumentos seja passado para a função.
Qual a utilidade disso?
- Currying ajuda a evitar situações em que uma função precisa ser chamada, passando o mesmo argumento repetidamente.
- Essa técnica ajuda a criar funções de ordem superior. É extremamente útil para lidar com eventos.
- Graças ao currying, você pode organizar a preparação preliminar de funções para executar certas ações e, em seguida, reutilizar convenientemente essas funções em seu código.
Considere uma função simples que adiciona os números passados a ela. Vamos encerrar
add. Leva três operandos como argumentos e retorna sua soma:
function add(a,b,c){
return a + b + c;
}
Essa função pode ser chamada passando-lhe menos argumentos do que o necessário (embora isso leve ao fato de que ela retorna algo completamente diferente do que se espera dela). Ele também pode ser chamado com mais argumentos do que os fornecidos quando foi criado. Em tal situação, argumentos "desnecessários" serão simplesmente ignorados. A experiência com uma função semelhante pode ter a seguinte aparência:
add(1,2,3) --> 6
add(1,2) --> NaN
add(1,2,3,4) --> 6 // .
Como curry tal função?
Aqui está o código da função
curryque se destina a curry outras funções:
function curry(fn) {
if (fn.length <= 1) return fn;
const generator = (...args) => {
if (fn.length === args.length) {
return fn(...args)
} else {
return (...args2) => {
return generator(...args, ...args2)
}
}
}
return generator
}
Aqui estão os resultados de experiências com esse recurso no console do navegador.

Experimentando curry no console do navegador
Composição de funções
Suponha que você precise escrever uma função que, tomando uma string como entrada
bitfish, retorna uma string HELLO, BITFISH.
Como você pode ver, essa função tem dois propósitos:
- Concatenação de strings.
- Converter os caracteres da string resultante em maiúsculas.
Esta é a aparência do código para tal função:
let toUpperCase = function(x) { return x.toUpperCase(); };
let hello = function(x) { return 'HELLO, ' + x; };
let greet = function(x){
return hello(toUpperCase(x));
};
Vamos experimentar.

Testando uma função no console do navegador
Esta tarefa inclui duas subtarefas organizadas como funções separadas. Como resultado, o código da função
greeté bastante simples. Se fosse necessário realizar mais operações em strings, a funçãogreetconteria uma construção comofn3(fn2(fn1(fn0(x)))).
Vamos simplificar a solução do problema e escrever uma função que componha outras funções. Vamos encerrar
compose. Aqui está seu código:
let compose = function(f,g) {
return function(x) {
return f(g(x));
};
};
Agora a função
greetpode ser criada usando a função compose:
let greet = compose(hello, toUpperCase);
greet('kevin');
Usar uma função
composepara criar uma nova função baseada em duas existentes implica em criar uma função que chama essas funções da esquerda para a direita. Como resultado, obtemos um código compacto e fácil de ler.
Agora nossa função
composeleva apenas dois parâmetros. E gostaríamos que ele pudesse aceitar qualquer número de parâmetros.
Uma função semelhante, capaz de aceitar qualquer número de parâmetros, está disponível na conhecida biblioteca de sublinhado de código aberto .
function compose() {
var args = arguments;
var start = args.length - 1;
return function() {
var i = start;
var result = args[start].apply(this, arguments);
while (i--) result = args[i].call(this, result);
return result;
};
};
Usando a composição de funções, você pode tornar as relações lógicas entre as funções mais compreensíveis, melhorar a legibilidade do seu código e estabelecer a base para futuras extensões e refatorações.
Você usa alguma forma especial de trabalhar com funções em seus projetos JavaScript?
