034.3 Lição 2
Certificação: |
Web Development Essentials |
---|---|
Versão: |
1.0 |
Tópico: |
034 Programação em JavaScript |
Objetivo: |
034.3 Estruturas de controle e funções do JavaScript |
Lição: |
2 de 2 |
Introdução
Além do conjunto padrão de funções integradas fornecido pela linguagem JavaScript, os desenvolvedores podem escrever suas próprias funções personalizadas, de forma a conduzir uma entrada de dados até uma saída adequada às necessidades do aplicativo. As funções personalizadas são, basicamente, um conjunto de declarações encapsuladas e empregadas como parte de uma expressão.
O uso de funções é uma boa maneira de evitar a duplicação do código, já que elas podem ser chamadas de locais diferentes ao longo de todo o programa. Além disso, o agrupamento de declarações em funções facilita a vinculação de ações personalizadas a eventos, o que é um aspecto essencial da programação em JavaScript.
Definição de função
Conforme um programa cresce, vai ficando mais difícil organizar o que ele faz sem usar funções. Cada função tem seu próprio escopo privado de variáveis, de forma que as variáveis definidas dentro de uma função estarão disponíveis apenas dentro dessa mesma função. Assim, elas não se confundem com variáveis de outras funções. As variáveis globais continuam acessíveis de dentro das funções, mas a maneira preferível de enviar valores de entrada para uma função é por meio de parâmetros de função. Como exemplo, vamos incrementar o validador de número primo da lição anterior:
// A naive prime number tester
// The number we want to evaluate
let candidate = 231;
// Auxiliary variable
let is_prime = true;
// Start with the lowest prime number after 1
let factor = 2;
// Keeps evaluating while factor is less than the candidate
while ( factor < candidate )
{
if ( candidate % factor == 0 )
{
// The remainder is zero, so the candidate is not prime
is_prime = false;
break;
}
// The next number that will divide the candidate
factor++;
}
// Display the result in the console window
if ( is_prime )
{
console.log(candidate, "is prime");
}
else
{
console.log(candidate, "is not prime");
}
Se em um ponto mais adiante no programa for preciso verificar se um número é primo, será necessário repetir o código que já foi escrito. Essa prática não é recomendada, já que quaisquer correções ou melhorias no código original precisariam ser replicadas manualmente em todos os lugares nos quais o código foi copiado. Além disso, a repetição do código sobrecarrega o navegador e a rede, tornando mais lenta a exibição da página web. Em vez disso, mova as declarações apropriadas para uma função:
// A naive prime number tester function
function test_prime(candidate)
{
// Auxiliary variable
let is_prime = true;
// Start with the lowest prime number after 1
let factor = 2;
// Keeps evaluating while factor is less than the candidate
while ( factor < candidate )
{
if ( candidate % factor == 0 )
{
// The remainder is zero, so the candidate is not prime
is_prime = false;
break;
}
// The next number that will divide the candidate
factor++;
}
// Send the answer back
return is_prime;
}
A declaração da função começa com uma declaração function
, seguida pelo nome da função e seus parâmetros. O nome da função deve seguir as mesmas regras que os nomes das variáveis. Os parâmetros da função, também conhecidos como argumentos da função, são separados por vírgulas e postos entre parênteses.
Tip
|
Não é obrigatório listar os argumentos na declaração da função. Os argumentos passados para uma função podem ser obtidos de um objeto |
No exemplo, a função test_prime
tem apenas um argumento: o argumento candidate
, que é o candidato a número primo a ser testado. Os argumentos da função funcionam como variáveis, mas seus valores são atribuídos pela declaração que chama a função. Por exemplo, a declaração test_prime(231)
chama a função test_prime
e atribui o valor 231 ao argumento candidate
, que então fica disponível dentro do corpo da função como uma variável comum.
Se a declaração de chamada usar variáveis simples para os parâmetros da função, seus valores serão copiados para os argumentos da função. Este procedimento — copiar os valores dos parâmetros usados na declaração de chamada para os parâmetros usados dentro da função — é chamado de passagem de argumentos por valor. Quaisquer modificações feitas no argumento pela função não afetam a variável original usada na declaração de chamada. No entanto, se a declaração inicial usar objetos complexos como argumentos (ou seja, objetos com propriedades e métodos vinculados) para os parâmetros da função, eles serão passados como referência e a função será capaz de modificar o objeto original usado na declaração de chamada.
Os argumentos que são passados por valor, assim como as variáveis declaradas dentro da função, não são visíveis fora dela. Ou seja, seu escopo é restrito ao corpo da função em que foram declarados. Ainda assim, as funções geralmente são empregadas para criar alguma saída visível fora da função. Para compartilhar um valor com sua função de chamada, uma função define uma declaração return
.
Por exemplo, a função test_prime
do exemplo anterior retorna o valor da variável is_prime
. Portanto, a função pode substituir a variável em qualquer lugar em que ela teria sido usada no exemplo original:
// The number we want to evaluate
let candidate = 231;
// Display the result in the console window
if ( test_prime(candidate) )
{
console.log(candidate, "is prime");
}
else
{
console.log(candidate, "is not prime");
}
A declaração return
, como seu nome indica, devolve o controle para a função de chamada. Portanto, onde quer que a declaração return
seja colocada na função, nada que venha depois dela é executado. Uma função pode conter múltiplas instruções return
. Esta prática pode ser útil se algumas delas estiverem dentro de blocos condicionais de declarações, de modo que a função possa ou não executar uma instrução return
determinada a cada execução.
Nem sempre as funções retornam um valor, por isso a instrução return
não é obrigatória. As declarações internas da função são executadas independentemente de sua presença, de modo que as funções também podem ser utilizadas, por exemplo, para alterar os valores das variáveis globais ou o conteúdo dos objetos passados por referência. Não obstante, se a função não tiver uma declaração return
, seu valor de retorno por padrão será undefined
: uma variável reservada que não tem um valor e não pode ser escrita.
Expressões das funções
Em JavaScript, as funções são apenas mais um tipo de objeto. Assim, elas podem ser empregadas no script como variáveis. Essa característica se torna explícita quando a função é declarada usando uma sintaxe alternativa, chamada expressões de função:
let test_prime = function(candidate)
{
// Auxiliary variable
let is_prime = true;
// Start with the lowest prime number after 1
let factor = 2;
// Keeps evaluating while factor is less than the candidate
while ( factor < candidate )
{
if ( candidate % factor == 0 )
{
// The remainder is zero, so the candidate is not prime
is_prime = false;
break;
}
// The next number that will divide the candidate
factor++;
}
// Send the answer back
return is_prime;
}
A única diferença entre este exemplo e a declaração da função no exemplo anterior está na primeira linha: let test_prime = function(candidate)
em vez de function test_prime(candidate)
. Em uma expressão de função, o nome test_prime
é usado para o objeto que contém a função e não para nomear a função em si. As funções definidas em expressões de função são chamadas da mesma maneira que as funções definidas usando a sintaxe de declaração. No entanto, enquanto as funções declaradas podem ser chamadas antes ou depois de sua declaração, as expressões de função só podem ser chamadas após sua inicialização. Como ocorre com as variáveis, chamar uma função definida em uma expressão antes de sua inicialização causará um erro de referência.
Recursão de função
Além de executar declarações e chamar funções integradas, as funções personalizadas também podem chamar outras funções personalizadas, incluindo elas mesmas. Nesse caso, falamos em recursão de função (ou recursividade de função). Dependendo do tipo de problema que se está tentando resolver, pode ser mais simples usar funções recursivas do que loops aninhados para executar tarefas repetitivas.
Até agora, aprendemos como usar uma função para testar se um determinado número é primo. Agora, vamos supor que queiramos encontrar o próximo primo após um determinado número. Poderíamos empregar um loop while
para incrementar o número do candidato e escrever um loop aninhado que buscaria fatores inteiros para aquele candidato:
// This function returns the next prime number
// after the number given as its only argument
function next_prime(from)
{
// We are only interested in the positive primes,
// so we will consider the number 2 as the next
// prime after any number less than two.
if ( from < 2 )
{
return 2;
}
// The number 2 is the only even positive prime,
// so it will be easier to treat it separately.
if ( from == 2 )
{
return 3;
}
// Decrement "from" if it is an even number
if ( from % 2 == 0 )
{
from--;
}
// Start searching for primes greater then 3.
// The prime candidate is the next odd number
let candidate = from + 2;
// "true" keeps the loop going until a prime is found
while ( true )
{
// Auxiliary control variable
let is_prime = true;
// "candidate" is an odd number, so the loop will
// try only the odd factors, starting with 3
for ( let factor = 3; factor < candidate; factor = factor + 2 )
{
if ( candidate % factor == 0 )
{
// The remainder is zero, so the candidate is not prime.
// Test the next candidate
is_prime = false;
break;
}
}
// End loop and return candidate if it is prime
if ( is_prime )
{
return candidate;
}
// If prime not found yet, try the next odd number
candidate = candidate + 2;
}
}
let from = 1024;
console.log("The next prime after", from, "is", next_prime(from));
Note que precisamos usar uma condição constante para o loop while
(a expressão true
dentro do parêntese) e a variável auxiliar is_prime
para saber quando parar o loop. Embora essa solução esteja correta, usar loops aninhados não é tão elegante quanto usar a recursão para executar a mesma tarefa:
// This function returns the next prime number
// after the number given as its only argument
function next_prime(from)
{
// We are only interested in the positive primes,
// so we will consider the number 2 as the next
// prime after any number less than two.
if ( from < 2 )
{
return 2;
}
// The number 2 is the only even positive prime,
// so it will be easier to treat it separately.
if ( from == 2 )
{
return 3;
}
// Decrement "from" if it is an even number
if ( from % 2 == 0 )
{
from--;
}
// Start searching for primes greater then 3.
// The prime candidate is the next odd number
let candidate = from + 2;
// "candidate" is an odd number, so the loop will
// try only the odd factors, starting with 3
for ( let factor = 3; factor < candidate; factor = factor + 2 )
{
if ( candidate % factor == 0 )
{
// The remainder is zero, so the candidate is not prime.
// Call the next_prime function recursively, this time
// using the failed candidate as the argument.
return next_prime(candidate);
}
}
// "candidate" is not divisible by any integer factor other
// than 1 and itself, therefore it is a prime number.
return candidate;
}
let from = 1024;
console.log("The next prime after", from, "is", next_prime(from));
Ambas as versões de next_prime
retornam o próximo número primo após o número dado como seu único argumento (from
). A versão recursiva, como a versão anterior, começa verificando os casos especiais (ou seja, números menores ou iguais a dois). Em seguida, incrementa o candidato e começa a procurar por quaisquer fatores inteiros com o loop for
(observe que o loop while
não está mais presente). Nesse ponto, o único número primo par já foi testado, então o candidato e seus possíveis fatores são incrementados de dois em dois (um número ímpar mais dois é o número ímpar seguinte).
Existem apenas duas maneiras de sair do loop for
no exemplo. Se todos os fatores possíveis forem testados e nenhum deles tiver um resto igual a zero ao dividir o candidato, o loop for
se completa e a função retorna o candidato como o número primo seguinte depois de from
. Caso contrário, se factor
for um fator inteiro de candidate
(candidate % factor == 0
), o valor retornado virá da função next_prime
chamada recursivamente, desta vez com o candidate
incrementado como parâmetro from
. As chamadas por next_prime
serão empilhadas umas sobre as outras, até que um candidato finalmente não retorne fatores inteiros. Nesse momento, a última instância de next_prime
contendo o número primo o retornará para a instância next_prime
anterior, e assim sucessivamente até chegar à primeira instância de next_prime
. Embora cada invocação da função use os mesmos nomes como variáveis, as invocações são isoladas entre si, de modo que suas variáveis são mantidas separadas na memória.
Exercícios Guiados
-
Que tipo de sobrecarga os desenvolvedores podem mitigar usando funções?
-
Qual a diferença entre argumentos de função passados por valor e argumentos de função passados por referência?
-
Qual valor será usado como saída de uma função personalizada se não houver uma declaração de retorno?
Exercícios Exploratórios
-
Qual é a causa provável de um Uncaught Reference Error (Erro de referência não detectado) emitido ao chamar uma função declarada com a sintaxe expression?
-
Escreva uma função chamada
multiples_of
que recebe três argumentos:factor
,from
eto
. Dentro da função, use a instruçãoconsole.log()
para imprimir todos os múltiplos defactor
que estejam entrefrom
eto
.
Resumo
Esta lição ensina como escrever funções personalizadas em código JavaScript. As funções personalizadas permitem que o desenvolvedor divida a aplicação em “pedaços” de código reutilizáveis, facilitando a criação e manutenção de programas mais extensos. A lição aborda os seguintes conceitos e procedimentos:
-
Como definir uma função personalizada: declarações de função e expressões de função.
-
Uso de parâmetros como entrada da função.
-
Uso da declaração
return
para definir a saída da função. -
Recursão de função.
Respostas aos Exercícios Guiados
-
Que tipo de sobrecarga os desenvolvedores podem mitigar usando funções?
As funções nos permitem reutilizar trechos de código, o que facilita a manutenção do programa. Um arquivo de script menor também economiza memória e tempo de download.
-
Qual a diferença entre argumentos de função passados por valor e argumentos de função passados por referência?
Quando passado por valor, o argumento é copiado para a função e a função não é capaz de modificar a variável original na declaração de chamada. Quando passado por referência, a função é capaz de manipular a variável original usada na declaração de chamada.
-
Qual valor será usado como saída de uma função personalizada se não houver uma declaração de retorno?
O valor retornado será definido como
undefined
.
Respostas aos Exercícios Exploratórios
-
Qual é a causa provável de um Uncaught Reference Error (Erro de referência não detectado) emitido ao chamar uma função declarada com a sintaxe expression?
A função foi chamada antes de sua declaração no arquivo de script.
-
Escreva uma função chamada
multiples_of
que recebe três argumentos:factor
,from
eto
. Dentro da função, use a instruçãoconsole.log()
para imprimir todos os múltiplos defactor
que estejam entrefrom
eto
.function multiples_of(factor, from, to) { for ( let number = from; number <= to; number++ ) { if ( number % factor == 0 ) { console.log(factor, "*", number / factor, "=", number); } } }