DEV Community

Bruno Moutinho
Bruno Moutinho

Posted on

Introdução à programação em JS - elementos básicos parte 2

Computadores são extremamente bons em tarefas repetitivas. Eles conseguem executar os mesmos cálculos e procedimentos milhares de vezes sem erros que seres humanos geralmente cometem por causa do cansaço ou falta de atenção. Entretanto, não parece uma boa ideia escrever o código milhares de vezes para que o procedimento aconteça milhares de vezes.

Funções são blocos de código que podem ser invocados quando conveniente, evitando a necessidade de se repetir o código várias vezes em uma aplicação. Hoje estudaremos as funções e operadores.


Índice da série:

Introdução à programação em JS - elementos básicos parte 1

Introdução à programação em JS - elementos básicos parte 2 (você está aqui)

Funções

Em JavaScript, funções podem ser declaradas de duas formas: usando a palavra function ou usando a sintaxe de "flecha". Existem diferenças entre essas duas formas de se declarar uma função, mas, por enquanto, essas diferenças são irrelevantes e podemos escolher a sintaxe que acharmos melhor. Abaixo, um exemplo de como usar cada uma das formas:

// Usando a palavra "function"
function minhaFunção() {}

// Usando a sintaxe de flecha
const minhaFunçãoFlecha = () => {}

// Neste caso é importante frisar que estamos criando
// uma variável e inicializando ela com uma função.
// Como estamos criando uma variável, poderíamos ter
// utilizado "let" (dado) ou "var" (variável) ao invés
// de "const" (constante).
// Entretanto, exceto quando você precisar, é uma boa
// prática utilizar "const", pois evita que se
// modifique o valor da variável de maneiras inesperadas.
Enter fullscreen mode Exit fullscreen mode

As partes principais da declaração são o nome da função (minhaFuncao e minhaFuncaoFlecha), os parâmetros (delimitados por ( e ), embora não tenhamos adicionado nenhum parâmetro nesses exemplos) e o corpo da função (delimitado por { e }, embora não tenhamos adicionado nenhuma expressão no corpo).

O nome da função pode receber qualquer texto, desde que obedeça às seguintes restrições (que são as mesmas restrições aplicadas a nomes de variáveis):

É importante lembrar que os interpretadores de JavaScript diferenciam os nomes entre letras maiúsculas e minúsculas, ou seja, os nomes minhaFunção e minhafunção são diferentes. Da mesma forma, caracteres com acentuação são diferentes dos caracteres sem acentuação, ou seja, os nomes minhaFuncao e minhaFunção são diferentes.

Como as duas formas de declarar funções são equivalentes para os exemplos que usaremos aqui, vou utilizar, por preferência pessoal, a sintaxe de flecha, principalmente por precisar escrever menos.

Toda função retorna um valor quando executada. No caso das funções minhaFunção e minhaFunçãoFlecha que definimos anteriormente, como elas não têm um retorno explícito, elas retornam, implicitamente, o valor indefinido (undefined). Para definirmos um retorno explícito em uma função, nós usamos a palavra return, cuja tradução é retorna:

// A função abaixo retorna o valor verdadeiro
// quando executada
const retornaVeradeiro = () => {
  return true;
}

retornaVerdadeiro()
// true

// A função abaixo retorna o valor falso
// quando executada.
const retornaFalso = () => false;

// Perceba que eu omiti as chaves ({}) e a
// palavra de retorno (return). Quando
// escrevemos uma função com sintaxe de flecha
// que tem apenas a expressão de retorno, podemos
// omitir esses elementos, diminuindo ainda mais
// a quantidade de código que precisamos escrever.
// Compare com a função equivalente abaixo:
function tambémRetornaFalso() {
  return false;
}

retornaFalso()
// false

tambémRetornaFalso()
// false
Enter fullscreen mode Exit fullscreen mode

Quando definimos uma função, podemos indicar valores que serão utilizados para calcular o valor de retorno da função. Esses valores são chamados de parâmetros. Abaixo temos um exemplo simples de uma função com parâmetros definidos:

// A função abaixo recebe como parâmetro dois
// valores, que demos o nome de x e y, e retorna
// a soma desses valores.
const adição = (x, y) => x + y;

// A expressão abaixo executa a função "adição"
// atribuindo para o parâmetro "x" o valor 40 e
// para o parâmetro "y" o valor 2.
adição(40, 2);
// 42
Enter fullscreen mode Exit fullscreen mode

Operadores

No exemplo acima, utilizamos o operador +. Não havíamos comentado anteriormente nessa série sobre os operadores ainda, mas podemos entender eles como funções que são "chamadas" com uma sintaxe diferente: enquanto a nossa função adição recebe o valor dos parâmetros dentro dos parênteses (adição(40, 2)), o operador + recebe os parâmetros um de cada lado (40 + 2). Outras operações matemáticas também existem em JavaScript, como - (subtração), * (multiplicação), / (divisão) e ^ (potenciação).

Também existem operadores lógicos: temos o "e" lógico (&&), o "ou" lógico (||) e o "não" lógico (!). Esses operadores seguem as regras da lógica Booleana:

  • Só são aceitos dois valores: verdadeiro (true) e falso (false);
  • Uma expressão lógica pode ser:
    • Um valor Booleano;
    • O operador lógico "não" (!) e uma expressão lógica;
    • Duas expressões lógicas conectadas por um operador lógico "e" (&&) ou "ou" (||).
  • Toda expressão lógica resolve para um valor Booleano.

Observe abaixo cada um dos casos:

// Expressões lógicas com somente um valor Booleano:
true
// true
false
// false

// Expressões lógicas com o operador lógico "não":
! true
// false
! false
// true
// O espaço entre o operador lógico e o valor Booleano é opcional.

// Expressões lógicas conectadas por um operador lógico:
true && true
// true
true && false
// false
false && true
// false
false && false
// false

true || true
// true
true || false
// true
false || true
// true
false || false
// false

// E podemos tornar tudo isso mais complicado:
( ! true ) && true
// false

// Assim como na matemática, é interessante o uso
// de parênteses para identificar a ordem das
// operações. Nesse caso, queremos aplicar o
// operador "não" ao valor "verdadeiro" antes de
// avaliarmos o resultado do operador "e".
// Como '( ! true )' é avaliado para o valor
// "falso", e o resultado da expressão
// 'false && true' é falso, o resultado da
// expressão '( ! true ) && true' é "falso".
Enter fullscreen mode Exit fullscreen mode

Uma breve explicação sobre cada um dos operadores lógicos:

! - Operador de negação. Retorna o valor negado da expressão que vem à sua direita.

&& - Operador de intersecção; "e" lógico. Retorna o valor "verdadeiro" apenas quando todos os operandos são verdadeiros. É chamado de intersecção quando pensamos em conjuntos matemáticos, pois quando queremos a intersecção de um conjunto A com um conjunto B, queremos os elementos que fazem parte do conjunto A e também fazem parte do conjunto B. A imagem abaixo ilustra essa relação:

https://s3-us-west-2.amazonaws.com/secure.notion-static.com/4421df08-9ad8-4669-b598-637468573353/AB.svg

PraCegoVer - A imagem acima mostra dois círculos parcialmente sobrepostos, um com a letra "A" ao centro, o outro com a letra B ao centro. A área em que um círculo se sobrepõe ao outro está rabiscada em vermelho, indicando que é a área de intersecção dos círculos. Imagem criada em https://excalidraw.com/

|| - Operador de união; "ou" lógico. Retorna o valor "verdadeiro" quando pelo menos um dos operandos é verdadeiro. É chamado de união quando pensamos em conjuntos matemáticos, pois quando queremos a união de um conjunto A com um conjunto B, queremos os elementos que fazem parte do conjunto A, do conjunto B ou dos dois conjuntos. A imagem abaixo ilustra essa relação:

https://s3-us-west-2.amazonaws.com/secure.notion-static.com/323497ae-f779-4b60-8771-b9c86c20d0ed/A_OR_B.svg

PraCegoVer - A imagem acima mostra dois círculos parcialmente sobrepostos, um com a letra "A" ao centro, o outro com a letra "B" ao centro. A área dos dois círculos, sobreposta ou não, está rabiscada em vermelho, indicando que é a área de união dos círculos. Imagem criada em https://excalidraw.com/

Para cada um dos operadores, os valores apresentados nos exemplos acima são o conjunto de valores possíveis para qualquer expressão lógica. Se organizamos esses conjuntos em uma tabela, essa tabela é chamada de "tabela verdade".

Funções puras

Um conceito muito importante quando estamos lidando com funções é o de funções puras. Funções puras adicionam certas restrições que devem ser seguidas:

  • São determinísticas, ou seja, sempre que a função for executada com os mesmos parâmetros, ela terá o mesmo resultado;
  • Não existe efeito colateral para a execução do código da função.

Alguns dos benefícios que temos com esse tipo de função são:

  • Por conta do determinismo, é fácil criar testes automatizados para funções puras, pois sabemos que o resultado para aquele conjunto de parâmetros é sempre o mesmo;
  • Por conta do determinismo, podemos aplicar otimizações de execução, salvando o resultado da execução com determinado conjunto de parâmetros, de forma a não precisar executar o código uma segunda vez para obter o resultado correto;
  • Por conta da ausência de efeitos colaterais, podemos executar o mesmo código paralelamente em diferentes partes da aplicação sem o receio de que uma execução interfira com a outra.

Funções puras são extremamente simples em conceito, mas é muito fácil não seguir suas restrições.

// A função pintar recebe 2 parâmetros: "casa" e "cor",
// modifica a cor da casa atribuindo a ela o valor
// recebido no parâmetro "cor" e retorna a casa.
const pintar = ( casa, cor ) => {
  casa.cor = cor;
  return casa;
}

const casa = {
  material: "tijolo",
  andares: 1,
  cor: "branca",
};

pintar( casa, "azul" );
// > { material: "tijolo", andares: 1, cor: "azul" }
casa;
// > { material: "tijolo", andares: 1, cor: "azul" }
Enter fullscreen mode Exit fullscreen mode

No exemplo acima, estamos modificando a propriedade cor da variável casa. Como estamos modificando o valor da variável, estamos criando um efeito colateral da execução do nosso código. Se outro ponto da aplicação estiver executando um código utilizando, também, a mesma variável casa, podemos ter algum comportamento estranho e muito difícil de reproduzir.

// A função pintar recebe 2 parâmetros: "casa" e "cor",
// cria uma nova casa com todas as propriedades do parâmetro
// "casa", sobrescrevendo o valor da propriedade "cor" com
// o valor do parâmetro "cor" e retorna a nova casa.
const pintar = ( casa, cor ) => {
  const novaCasa = { ...casa, cor };
  return novaCasa;
}

const casa = {
  material: "tijolo",
  andares: 1,
  cor: "branca",
};

pintar( casa, "azul" );
// > { material: "tijolo", andares: 1, cor: "azul" }
casa;
// > { material: "tijolo", andares: 1, cor: "branca" }
Enter fullscreen mode Exit fullscreen mode

Dessa vez, os valores do parâmetro casa não foram alterados. Ao invés disso, nós copiamos as propriedades da variável e criamos uma nova variável sobrescrevendo o valor da propriedade cor.

Operador de expansão

Antes de finalizarmos, deixe eu explicar o que está acontecendo na linha const novaCasa = { ...casa, cor };. O símbolo ... é um operador chamado de spread, cuja tradução é "expansão". No contexto em que ele está sendo usado, ele "expande" o objeto casa, expondo todas as suas propriedades. Outra coisa que deve parecer estranha é que eu coloquei tanto ...casa quanto cor sem determinar seus valores. Isso acontece por que, quando uma propriedade tem o mesmo valor de uma variável, os interpretadores de JavaScript conseguem inferir que o valor da propriedade será o valor da variável. Observe os exemplos abaixo:

// Os dois exemplos abaixo criam um objeto
// com as mesmas propriedades e com o mesmo
// valor em cada uma das propriedades.

const casa = {
  material: "tijolo",
  andares: 1,
  cor: "branca",
};

casa;
// > { material: "tijolo", andares: 1, cor: "branca" }

const material = "tijolo";
const andares = 1;
const cor = "branca";
const outraCasa = {
  material,
  andares,
  cor,
};

outraCasa;
// > { material: "tijolo", andares: 1, cor: "branca" }

// Os dois exemplos abaixo criam cópias
// do objeto "casa" definido anteriormente.

const cópiaDaCasa = {
  material: casa.material,
  andares: casa.andares,
  cor: casa.cor,
};

cópiaDaCasa;
// > { material: "tijolo", andares: 1, cor: "branca" }

const outraCópiaDaCasa = { ...casa };

outraCópiaDaCasa;
// > { material: "tijolo", andares: 1, cor: "branca" }

// Os dois exemplos abaixo criam cópias
// do objeto casa, mas sobrescrevem a propriedade
// andares.

const casaMaisAlta = {
  material: casa.material,
  andares: 2,
  cor: casa.cor,
};

casaMaisAlta;
// > { material: "tijolo", andares: 2, cor: "branca" }

const outraCasaMaisAlta = {
  ...casa,
  andares: 2,
};

outraCasaMaisAlta;
// > { material: "tijolo", andares: 2, cor: "branca" }
Enter fullscreen mode Exit fullscreen mode

Quando criamos um objeto, podemos definir a mesma propriedade duas vezes na declaração, porém o objeto não pode ter duas propriedades diferentes com o mesmo nome. Toda propriedade deve ter um nome único. O que os interpretadores fazem quando definimos duas propriedades com o mesmo nome é sobrescrever a primeira propriedade declarada pela segunda. Na prática, a última propriedade declarada com cada nome terá o seu valor no objeto criado. Por isso, quando expandimos um objeto (expondo as suas propriedades na criação de um outro objeto) e declaramos uma propriedade que já existia no objeto expandido com um valor diferente, estamos sobrescrevendo o valor daquela propriedade.

Conclusão

Parabéns! Você deu mais um importante passo na compreensão dos elementos básicos para escrever aplicações em JavaScript!

Neste texto, exploramos as diferentes formas de declarar uma função. Vimos, também, o que são operadores. Aprendemos o que são funções puras e quais são seus benefícios. Por fim, aprendemos sobre o operador de expansão e como podemos criar novos objetos a partir de outros objetos.

Funções

Podem ser declaradas com a palavra function ou com a sintaxe de flecha. Utilizando a sintaxe de flecha, podemos omitir as chaves ({ e }) e a palavra return (retorna) caso a única expressão do corpo da função seja a expressão de retorno.

Operadores

São como funções que não usam os parênteses (( e )) para delimitar onde estão seus parâmetros, mas utilizam ou o que vem imediatamente após como parâmetro (como no caso do operador lógico de negação !) ou o que vem imediatamente antes e imediatamente após (como no caso do operador de adição +).

No caso dos operadores lógicos, nós testamos todos os valores possíveis para cada operador.

Funções puras

São funções determinísticas, ou seja, dado um conjunto de parâmetros, sempre retorna o mesmo valor, e que não têm efeitos colaterais. Isso as torna mais fáceis de serem testadas e evita problemas de execução paralela.

Operador de expansão

Nos permite expandir as propriedades de um objeto para que seja mais simples de criar uma cópia dele.

Da mesma forma que no último texto, é importante que todos os conceitos tratados nesse texto fiquem claros. A releitura ajuda a fixar esses conceitos. Caso algum conceito não esteja bem explicado, por favor entre em contato por qualquer forma listada na página Contato no menu.

No próximo texto iremos aprender sobre listas e controle de fluxo!

Top comments (0)