JS Básico
JS Básico
Ver no GitHub
Este material aborda os principais conteúdos para que você entenda e consiga aplicar os fundamentos básicos da programação com a
linguagem JavaScript. No final deste material existem listas de exercícios para a prática dos fundamentos aprendidos em cada uma das
seções. Se você está começando agora, a recomendação é: resolva todos os exercícios, se possível, até mais de uma vez.
Não tenha pressa. Leia com atenção e seja persistente. A programação de computadores pode ser intimidadora, mas, a cada passo que
der, você se sentirá cada vez mais motivado a entrar nesse mundo.
Bons estudos!
Sumário
Introdução à Programação com JavaScript
Sumário
1. Algoritmo
2. Variável
2.1. Nomenclatura de variáveis
2.2. Atribuição de valores
2.3. Tipos de variáveis
2.4. Exercícios com tipos de variáveis
3. Operadores
3.1. Operadores Aritméticos
3.2. Operadores Relacionais
3.3. Operadores Lógicos
4. Tabela verdade
4.1. Operadores “e”, “ou” e “não”
4.2. Exercícios com tabela verdade
5. Precedência de operadores
5.1. Exercícios com expressões
6. Primeiros passos em programação
6.1. Olá, mundo!
6.2. Declaração de variáveis
Constantes
6.3. Comandos de entrada
6.4. Comandos de saída
6.5. Um básico exemplo de entrada, processamento e saída
6.6. Tipos de variáveis em JavaScript
Conversão de tipos
6.7. Comentários: como dar mais sentido ao seu código
6.8. Escrevendo mensagens melhores
Template strings
7. Estruturas de decisão: escolha um caminho
7.1. If
7.2. If / Else
7.3. If / Else / If
7.4. Switch/Case
8. Estruturas de repetição: como evitar a fadiga
8.1. While
8.2. Do/While
8.3. For
8.4. Expressões de incremento e decremento
8.5. O comando break
8.6. O comando continue
9. Vetores e matrizes: expandindo as possibilidades
9.1. Vetores
Tipos em vetores JavaScript
Dimensionamento de vetores
A propriedade length
9.2. Matrizes
Diagonais de uma matriz
9.3. Métodos do objeto Array e outras utilidades
push()
unshift()
shift()
pop()
splice()
slice()
sort()
reverse()
indexOf()
includes()
for…of
10. Funções
10.1. Funções sem parâmetro ou sem retorno
10.2. Parâmetros rest
10.3. Funções recursivas
10.4. Diferentes sintaxes para declaração de funções
10.5. Funções callback
Uso de funções callback na linguagem JavaScript
11. Orientação a objetos
11.1. Classes
O operador this
O construtor
Atributos e métodos privados
Atributos e métodos estáticos
11.2 Herança
12. Depuração: solucionando erros comuns em JavaScript
ReferenceError
TypeError
SyntaxError
Lista 1 - Expressões, entrada e saída de dados
Lista 2 - Estruturas de decisão: Se/Senão
Lista 3 - Estruturas de decisão: Escolha/Caso
Lista 4 - Laços de Repetição: Enquanto e Faça/Enquanto
Lista 5 - Laços de Repetição: Para
Lista 6 - Desafios com Laços de Repetição
Lista 7 - Vetores
Lista 8 - Matrizes
Lista 9 - Funções
Lista 10 - Objetos
Referências
1. Algoritmo
Após entender o que é a programação de computadores, o primeiro conceito com o qual costumamos nos deparar no aprendizado de
programação é o conceito de algoritmo.
Podemos definir algoritmo como uma sequência finita e ordenada de passos para a resolução de um problema específico.
Para exemplificar, vamos começar com um algoritmo descritivo para um problema clássico do cotidiano: como fritar ovos?
Percebe que o algoritmo acima é basicamente uma receita? Algoritmos são exatamente como receitas.
Uma boa recomendação para a construção de algoritmos descritivos é sempre utilizar apenas um verbo por linha, já que cada verbo
geralmente indicará uma ação diferente a ser tomada.
O nosso principal objetivo ao estudar Lógica de Programação é compreender como criar programas de computador. Um
Programa de Computador nada mais é que um algoritmo escrito com o uso de uma linguagem de computador, ou linguagem
de programação, como C, Pascal, Java, JavaScript, PHP etc. Há uma infinidade de linguagens de programação disponíveis
mundo afora. Futuramente veremos como elas funcionam.
2. Variável
Na Matemática, variável é o nome que se dá ao termo que substitui um outro desconhecido, como numa equação: y = 2x + 1 . Em
Lógica de Programação, o valor de uma variável pode ser conhecido ou não.
De forma genérica, uma variável é uma referência a um local na memória do computador, onde um valor pode estar armazenado, por
exemplo: em um programa de computador, podemos ter uma variável chamada idade , que armazena o valor 18 .
a = 4;
nome = "José";
Observe que o símbolo de igualdade, = , aqui é utilizado como operador de atribuição. Para igualdade, utilize == ou === .
Os valores entre parênteses correspondem aos tipos de variáveis como são usados em linguagens de programação como C++
ou Java. Geralmente, estes tipos aparecem de forma abreviada: int, char, bool.
3. Operadores
Um operador é um símbolo que, como seu próprio nome diz, é utilizado para representar operações lógico-matemáticas. Os operadores
que iremos utilizar são os operadores aritméticos, os operadores relacionais e os operadores lógicos. Observe que alguns deles, já
utilizados na Matemática, terão seu símbolo substituído por outro na Programação. Já que utilizaremos JavaScript como linguagem de
programação neste material, todos os operadores nos exemplos serão operadores válidos na linguagem JavaScript.
4. Tabela verdade
Uma tabela verdade é uma tabela utilizada em Lógica para verificar se uma expressão é verdadeira ou falsa.
Para compreender como se dá a construção desta tabela, podemos nos fazer as seguintes perguntas:
X e Y são verdadeiros?
X ou Y são verdadeiros?
Ao questionar se X e Y são verdadeiros, questionamos se ambos são verdadeiros. Ou seja: a expressão X && Y só é verdadeira quando
X == true e Y == true . Repare que aqui já começamos a utilizar os termos true e false para representar valores verdadeiros ou
falsos.
O operador ou ( || ) já funciona de maneira diferente. Ao questionarmos se X ou Y são verdadeiros, questionamos se pelo menos um
deles é verdadeiro. Ou seja: a expressão X || Y é verdadeira quando X == true e Y == false , ou X == false e Y == true , ou X ==
true e Y == true .
O operador não ( ! ) é utilizado para negar uma determinada expressão. Ex.: se X == true , então !X == false ; se X == false , então
!X == true .
5. Precedência de operadores
A precedência, ou ordem, de operadores é a convenção que indica a ordem em que as operações devem ser realizadas em uma
expressão. A lista abaixo descreve a ordem em que os operadores devem ser considerados:
1. Parênteses e funções
2. Potência e resto
3. Multiplicação e divisão
4. Adição e subtração
5. Operadores relacionais
6. Operadores lógicos
3 + 4 * 9;
3 + 36;
39;
Neste exemplo, a parte da expressão entre parênteses é resolvida antes da multiplicação.
(3 + 4) * 9;
7 * 9;
63;
Quando operadores de mesma prioridade se chocam, a operação mais à esquerda prevalece sobre as demais. Ex.:
(3 * 4) / 3;
12 / 3;
4;
Neste exemplo, o operador de soma tem prioridade sobre os operadores relacionais, e os operadores relacionais têm prioridade sobre o
operador lógico.
10 < 9 e 6 + 3 > 10
10 < 9 e 9 > 10
F e F
F
Para facilitar, isole as expressões que estão antes e após o operador lógico, resolva-as e só então compare seus resultados através do
operador lógico.
a) 2 + 3 - 5 * 8 / 4
b) 7 * 4 / 2 + 9 - 6
c) (4 + 2) * 3 / 4
d) 7 > 2 && 3 -5 < 8
e) 8 > 12 && 5 < 3 + 4
e) 9 > 15 || 3 < 3 + 4
Em Lógica de Programação, a sintaxe está ligada à estrutura da linguagem de programação. Ela é o conjunto de regras que definem a
forma correta de composição das estruturas básicas de uma linguagem. É o que deve responder à pergunta: como construir tal estrutura
corretamente?
Outro conceito que precisamos compreender é o de semântica. Em Lógica de Programação, a semântica está ligada ao significado de
um determinado código. A semântica deve responder à pergunta: tal código faz realmente o que se espera que ele faça?
Recomendamos que você faça a leitura dos capítulos a seguir testando cada um dos exemplos.
No Google Chrome, pressione Ctrl + Shift + i para abrir as Ferramentas do desenvolvedor. Estas ferramentas foram inicialmente
pensadas para que desenvolvedores pudessem testar seus projetos. Nós as utilizaremos para executar os nossos programas.
Com a guia de ferramentas do desenvolvedor aberta, vá até a aba console, digite o código do exemplo abaixo e pressione Enter :
alert("Olá, mundo!");
let idade;
Em JavaScript, o termo let é utilizado para declarar uma variável com escopo de bloco, ou seja, uma variável que só é acessível dentro
do bloco em que foi declarada. Um exemplo de bloco são as estruturas de decisão e de repetição que conheceremos em breve.
Você também pode utilizar o termo var para declarar uma variável com escopo global. Variáveis assim, mesmo se declaradas dentro de
um bloco, serão acessíveis do lado de fora dele.
var idade;
Em JavaScript, até que uma variável seja inicializada, ou seja, até que ela receba algum valor, seu conteúdo será undefined (indefinido).
Você pode inicializar uma variável juntamente à sua declaração:
Mas podemos também declarar uma variável e inicializá-la com um valor lido:
Antes de prosseguirmos, lembre-se que você pode declarar quantas variáveis quiser, inclusive em uma única linha em seu programa, por
exemplo:
Constantes
Em certos casos precisamos declarar variáveis que não terão seu valor ou referência modificados. Este tipo de variável é chamado de
constante. Para declarar uma constante em JavaScript, utilize o termo const :
É importante saber que constantes devem sempre ser inicializadas junto à sua declaração, como fizemos no exemplo acima. Tente
declarar uma constante sem inicializá-la ou tente modificar o valor de uma constante já inicializada e verifique o que acontece.
let nome = prompt("Digite seu nome. Ele será armazenado na variável nome.");
alert("O nome digitado foi " + nome);
Quando se trata de linguagens de programação, os comandos costumam ter um par de parênteses logo após seu nome, onde serão
informadas opções para sua execução. Estes “comandos” são chamados de funções. Os valores que vão entre os parênteses são
chamados de parâmetros ou argumentos. Se você já viu uma função matemática, deve ter notado a semelhança. Neste exemplo, o
valor lido pela função prompt() é armazenado na variável nome .
Em JavaScript, há três funções que podem ser utilizadas para exibir a saída de um programa:
alert("Olá, mundo!");
console.log("Olá, mundo!");
document.write("Olá, mundo!");
let a, b, c;
a = Number(prompt("Digite um número inteiro: "));
b = Number(prompt("Digite outro número inteiro: "));
c = a + b;
console.log(c);
let x;
x = 50;
alert("O valor de x é " + x);
x = "Alan Turing";
alert("O valor de x é " + x);
Devido a esta característica, costumamos dizer que a linguagem JavaScript é de tipagem dinâmica.
Outra peculiaridade desta linguagem está relacionada ao fato de que é possível realizar operações com variáveis de tipos diferentes.
Devido a isto, a tipagem da linguagem JavaScript é também considerada fraca. Teste o exemplo abaixo:
let a = 2,
b = "2";
alert(a + b);
Se surpreendeu com o resultado? Isto acontece porque a variável b está armazenando um valor do tipo string (cadeia). Em operações
envolvendo valores do tipo string , o sinal de soma ( + ) realiza a operação de concatenação, ou simplesmente junção.
Embora não se possa fixar o tipo de uma variável em JavaScript, os tipos de valores existentes nesta linguagem são:
números ( number )
cadeias de caracteres ( string )
lógicos ( boolean )
objetos ( object )
nulo ( null )
indefinido ( undefined )
Conversão de tipos
Por padrão, qualquer valor lido pela função prompt() é considerado uma string (cadeia de caracteres):
Logo, se precisarmos que o valor lido seja considerado como numérico, é necessário realizar a conversão de tipo.
Em JavaScript não existe bem uma distinção entre números inteiros e números reais. Um número real é simplesmente um valor numérico
acompanhado de uma parte decimal.
Para converter uma string em um número inteiro, utilizamos a função parseInt() . Se a string informada possuir uma parte
decimal, essa parte será ignorada:
Se a string a ser convertida representar um número com parte decimal poderemos utilizar a função parseFloat() .
Há ainda uma alternativa mais simples a essas duas funções que realiza por conta própria o trabalho de decidir se o número em questão
é um inteiro ou real: a função Number() .
Não se assuste com o uso de funções encadeadas, como no exemplo anterior. Lembre-se que, de acordo com a precedência de
operadores que vimos no início deste material, as funções são a primeira coisa a ser resolvida em uma expressão. Se você possui uma
função sendo executada dentro de outra, a função mais interna será executada primeiro, depois a mais externa. O operador de
atribuição só recebe o resultado final.
Caso se sinta mais confortável, você também pode escrever o exemplo anterior da seguinte maneira:
Ou ainda:
Podemos acreditar que, muito provavelmente, seu colega terá dificuldades em compreender seu código para, então, conseguir
prosseguir com a solução do problema.
Mas, e se você pudesse escrever algo mais dentro do seu código para orientar seu colega sem que isso atrapalhasse o funcionamento do
seu programa? Isso é possível com o uso de comentários.
Através de comentários, você pode escrever qualquer coisa dentro do seu código sem que isso afete o funcionamento do seu programa.
Para escrever um comentário, apenas insira // e escreva seu comentário. Exemplo:
Às vezes utilizamos comentários também para suprimir uma ou mais linhas de código de um programa para que elas não sejam
executadas:
let x = 10,
y = 20;
console.log(x);
// escreva(y)
// Veja que só a variável x será escrita, pois a linha que deveria escrever y foi marcada como um comentário
Alguns comentários podem ser mais longos e ocupar várias linhas de código. Neste caso, a primeira linha comentada deverá ser iniciada
com /* e a última finalizada com */ . Exemplo:
let z = 30;
/* Este comentário
foi um pouco
mais longe */
console.log(z);
A função console.log permite que vários valores sejam informados separados por vírgula, inclusive tornando possível misturar strings e
variáveis. Observe o exemplo a seguir:
A função alert não permite vários valores, mas você pode passar uma grande string concatenada com sinais de +:
Template strings
Há um recurso que pode simplificar mais ainda o nosso trabalho com mensagens, chamado template strings . Com elas é possível
mesclar strings, variáveis e outras expressões, em uma única expressão, sem a necessidade de abrir e fechar aspas várias vezes ou usar o
operador de concatenação:
Uma template string deve ser envolvida por crases (``) ao invés de aspas, e toda variável ou expressão dentro dela deve aparecer dentro
de chaves acompanhadas por um cifrão ${} . Além disso, template strings podem ser quebradas em várias linhas para facilitar a
organização do código.
7. Estruturas de decisão: escolha um caminho
Na programação, há momentos em que precisamos fazer decisões, definir caminhos diferentes para situações diferentes, e é por isso
que existem as estruturas de decisão.
As duas estruturas de decisão presentes na maioria das linguagens de programação são o se/senão ( if / else ) e o escolha/caso
( switch / case ).
As estruturas de decisão são peça fundamental na estruturação dos algoritmos utilizados nos sistemas que utilizamos diariamente. Por
exemplo, pense em quando você entra com seu e-mail e senha para realizar login em uma rede social. Observe que no momento em que
você clica no botão Entrar, duas situações são possíveis: seu login pode ser aceito e você entrará na rede social, ou a página indicará que
seu e-mail e/ou senha estão incorretos. Para decidir qual das duas ações realizar, o sistema certamente fez uso de uma estrutura de
decisão.
7.1. If
A estrutura if (se) é utilizada na maioria dos casos em que é necessário decidir que ação será tomada.
Para que você compreenda como funciona a estrutura if , imagine a seguinte situação: após lermos um número inteiro qualquer, se
este número for par, deveremos exibir uma mensagem informando que ele é par. Ex.:
let numero;
numero = prompt("Entre com um número: ");
if (numero % 2 == 0) {
alert("O número lido é par.");
}
A partir da terceira linha do código acima, começamos a utilizar a estrutura de decisão if . Observe que ela é constituída da palavra if
seguida de um par de parênteses. Dentro dos parênteses deve haver uma expressão lógica, ou seja, uma expressão que possa ser
avaliada como verdadeiro ou falso , true ou false . Quando a expressão entre parênteses é igual a true , o código entre as chaves
da estrutura é executado. Se o resultado da expressão for false , o código entre as chaves é simplesmente ignorado.
Neste exemplo, a expressão avaliada é numero % 2 == 0 . Considerando que a variável numero contenha mesmo um número par, como
4, sabemos que o resultado da expressão numero % 2 será igual a 0, pois ao se dividir 4 por 2, o resto (ou módulo) é 0, logo a expressão
retornará o valor true . Observe:
4 % 2 == 0;
0 == 0;
true;
Se o valor da variável numero for ímpar, o resto da sua divisão por 2 será 1, o mesmo acontecerá com qualquer outro número ímpar,
logo a expressão retornará o valor false . Observe:
3 % 2 == 0;
1 == 0;
false;
7.2. If / Else
Nem sempre é suficiente apenas verificar se uma condição é atendida e executar determinada ação. Muitas vezes precisamos de uma
segunda alternativa, um plano B.
Ainda conforme o exemplo abordado anteriormente, imagine que caso o número lido não seja par tenhamos que mostrar uma
mensagem também informando que o número é ímpar. Acompanhe o exemplo:
7.3. If / Else / If
Mas o que fazer quando temos mais de duas alternativas?
Para responder a essa pergunta, vamos pensar no seguinte problema: após ler um número inteiro, precisamos informar ao usuário se
este número é negativo, nulo (0) ou positivo. Podemos fazer isso da seguinte forma:
let numero;
numero = parseInt(prompt("Entre com um número inteiro: "));
if (numero < 0) {
console.log("O número lido é negativo.");
} else if (numero > 0) {
console.log("O número lido é positivo.");
} else {
console.log("O número lido é nulo.");
}
Observe que neste caso foram testadas duas condições: numero < 1 e numero > 1 . A segunda condição segue acompanhando o
primeiro else , indicando que esta condição só deverá ser verificada caso a primeira não seja verdadeira. Por fim, considerando que
nenhuma das duas condições seja verdadeira, o código após o segundo else poderá ser executado.
Se numero < 1 for verdadeiro , o código console.log("O número lido é negativo.") será executado.
Se numero < 1 for falso e numero > 1 for verdadeiro , o código console.log("O número lido é positivo.") será executado.
Se numero < 1 for falso e numero > 1 for falso , o código console.log("O número lido é nulo.") será executado.
Tenha em mente que a quantidade de testes que podem ser realizados em sequência é basicamente ilimitada, mas não é comum vermos
estruturas de decisão exageradamente longas.
Para adquirir um maior domínio, teste os exemplos vistos, mas não pare por aí, crie seus próprios exemplos, teste condições compostas,
como idade > 18 && peso < 70 , estruturas com e sem um else , com dois ou mais else if até perceber que já está dominando o
assunto.
7.4. Switch/Case
Seguindo a mesma linha das estruturas ìf / else , o switch / case (escolha/caso) oferece uma maneira mais prática de resolver
problemas onde há muitos valores possíveis para uma variável.
Para que você possa entender melhor, imagine que precisemos construir uma calculadora que realize as quatro operações básicas: soma,
subtração, divisão e multiplicação. Poderíamos solicitar que o usuário informasse a operação desejada e, logo após, os números a serem
operados. Para soma, o usuário digitaria + , para subtração - e assim por diante.
if (opcao == "+") {
// realiza a soma
} else if (opcao == "-") {
// realiza a subtração
} else if (opcao == "/") {
// realiza a divisão
} else if (opcao == "*") {
// realiza a multiplicação
}
Você deve ter percebido que não declaramos nem lemos a variável opcao , tampouco descrevemos as operações, apenas comentamos.
Não se preocupe com isto. O importante neste exemplo é entendermos como se dá a seleção de uma das opções.
Com a estrutura switch / case , o exemplo anterior poderia ser construído da seguinte forma:
switch (opcao) {
case "+":
// realiza a soma
break;
case "-":
// realiza a subtração
break;
case "/":
// realiza divisão
break;
case "*":
// realiza multiplicação
}
Com o comando switch , selecionamos a variável a ser verificada, neste caso, opcao . Em seguida, definimos cada um dos possíveis
valores para a variável escolhida com a palavra case . A cada caso verificado, se este for verdadeiro, realizamos a operação selecionada.
Em seguida, o comando break é utilizado para impedir que as opções seguintes sejam verificadas mesmo após um caso verdadeiro.
Falaremos mais sobre este comando no capítulo sobre estruturas de repetição.
Perceba que não são utilizados operadores relacionais para verificar, por exemplo, se o valor da variável opcao é igual a + ou - , apenas
informamos a variável a ser comparada e quais são seus possíveis valores, neste caso: + , - , / e * .
Observe que não é possível o uso de operadores relacionais para verificar se, por exemplo, a variável escolhida é maior ou menor que um
valor qualquer.
Ainda sobre o exemplo anterior, o que fazer caso o usuário não digite uma opção válida? E se o usuário simplesmente digitasse um ponto
( . ) ou qualquer outra coisa que não estamos esperando? Para situações assim, utilizamos o default , que é o caso padrão. O default
nos dá a possibilidade de definir o que fazer quando nenhum dos casos previstos é verdadeiro. De forma resumida, poderíamos aplicá-lo
em nosso exemplo da seguinte maneira:
switch (opcao) {
case "+":
// realiza a soma
break;
case "-":
// realiza a subtração
break;
default:
// informa ao usuário que a opção é inválida
}
A boa notícia é que alguém já pensou nisso antes. As linguagens de programação possuem estruturas que chamamos de estruturas de
repetição ou de laços de repetição. Estas estruturas são utilizadas sempre que nos deparamos com tarefas que exijam que um
procedimento se repita por várias vezes.
As três estruturas de repetição que abordaremos são: while (enquanto), do / while (faça/enquanto) e for (para). Cada uma dessas
estruturas é mais adequada em uma situação específica, mas o propósito de automatizar tarefas repetitivas permanece em todas elas.
Assim como nas estruturas de decisão, o funcionamento de uma estrutura de repetição é baseado em uma condição, que mais uma vez
deverá ser representada através de uma expressão lógica.
A seguir, conheceremos cada uma das estruturas de repetição mais a fundo. Antes de começarmos, tenha em mente que sempre é
possível utilizar qualquer uma das três estruturas de repetição em um programa onde elas se façam necessárias.
8.1. While
Imagine uma situação em que você precise repetir determinada tarefa enquanto uma certa condição for verdadeira, por exemplo:
Essa mesma situação pode ser descrita em código, utilizando JavaScript, da seguinte forma:
while (1 == 1) {
console.log("um é igual a um.");
}
Neste exemplo, enquanto a condição 1 == 1 for avaliada como verdadeira, a expressão console.log("um é igual a um.") será
executada. Pense nisso como um ciclo: avaliamos a condição, se ela for verdadeira, o código é executado, depois tudo se repete
novamente.
Se você testou o código acima, deve ter percebido que ele gerou um loop infinito, que é como chamamos os laços de repetição
intermináveis. O motivo de esse laço de repetição ser interminável é bastante óbvio, pois sabemos que 1 será sempre igual a 1, logo a
condição sempre será verdadeira.
Quando a condição é avaliada como falsa, o código entre chaves não é executado e o programa segue para após a estrutura de repetição.
Apenas para exemplificar, sem o uso de uma linguagem de programação específica, poderíamos fazer o seguinte:
Em muitos dos casos em que utilizamos laços de repetição, nós trabalhos com números inteiros. Considere, por exemplo, uma situação
em que seja necessário imprimir todos os números inteiros de 1 a 10. Em casos como este, utilizamos variáveis do tipo inteiro que
possam ter seu valor alterado progressivamente. A estas variáveis damos o nome de contadoras:
let x = 1;
while (x <= 10) {
console.log(x);
x = x + 1;
}
No exemplo acima, a variável x assume o papel de contadora, pois é utilizada como condição do laço de repetição e seu valor é
incrementado a cada repetição. A variável é inicializada com o valor 1 e definimos que enquanto o valor nela contido for menor ou igual
a 10 , a seguinte tarefa deve ser executada: escreve-se o valor de x e adiciona-se 1 ao valor atual de x .
Ao término desse programa, o valor de x será igual a 10 ou igual a 11 ? Perceba que em algum momento nessa estrutura de repetição,
o valor de x atingirá 10 , a condição entre parênteses será avaliada como verdadeira, o valor de x será mostrado e incrementado, ou
seja, x passará a ser igual a 11 . A partir deste ponto, a condição x <= 10 voltará a ser avaliada e, desta vez, seu resultado será falso ,
pois x é igual a 11 , logo não é nem menor nem igual a 10 . Com isto, o laço de repetição será interrompido e o valor de x , que agora é
igual a 11 , não será mais mostrado.
Se precisarmos realizar o caminho contrário, ou seja, imprimir todos os números inteiros de 10 a 1 (em ordem decrescente), o seguinte
código seria suficiente:
let x = 10;
while (x >= 1) {
console.log(x);
x = x - 1;
}
Perceba que, neste exemplo, o valor de x é decrementado, ou seja, subtraímos 1 do valor de x a cada repetição. A condição verificada
também é diferente: desta vez, verificamos a cada repetição se o valor de x é maior ou igual a 1 .
1. A condição é testada
2. Se a condição for avaliada como verdadeira, o código entre chaves é executado, retorna-se ao passo 1
3. Se a condição for avaliada como falsa, segue-se ao passo 4
4. A estrutura é finalizada
8.2. Do/While
A segunda estrutura de repetição que iremos abordar funciona de forma muito semelhante à primeira, com uma pequena diferença: a
condição só é verificada após a primeira execução do código. Observe o exemplo:
Escreva: um é igual a um. Enquanto um for igual a um, repita.
Essa mesma situação pode ser descrita em código, utilizando JavaScript, da seguinte forma:
do {
console.log("um é igual a um.");
} while (1 == 1);
Se você ainda não conseguiu compreender a diferença entre while e do / while , reflita nas seguintes situações:
While (Enquanto):
Mariana pede a Felipe que vá ao mercado e lhe diz: Felipe, se os tomates estiverem bons, pegue um. Enquanto houver tomates
bons, continue pegando tomates. Se não houver mais tomates bons, pare de pegar os tomates.
Do / While (Faça/Enquanto):
Mariana pede a Felipe que vá ao mercado e lhe diz: Felipe, pegue um tomate. Enquanto houver tomates bons, continue
pegando tomates. Se não houver mais tomates bons, pare de pegar os tomates.
8.3. For
A última estrutura de repetição que iremos abordar tem um comportamento bastante semelhante ao das anteriores, porém, tem uma
finalidade muito específica: a estrutura de decisão for (para) é a mais adequada em situações em que o número de repetições a serem
executadas já é conhecido.
Observe como faríamos para imprimir os números inteiros de 1 a 1000 utilizando for :
Você pode ter a impressão de que a condição entre parênteses ficou maior, mas não é exatamente isso o que acontece. Ao utilizarmos a
estrutura for , temos acesso a três regras, separadas por ponto e vírgula, que definirão como a estrutura será executada. A estas regras
damos o nome de parâmetros ou argumentos.
1. O primeiro parâmetro é um espaço disponibilizado para a declaração e inicialização de variáveis que serão utilizadas apenas dentro
desta estrutura for . Observe que no exemplo acima, nós declaramos e inicializamos a variável x com o valor 1 . A variável x será
nossa contadora e, por vezes, também poderá ser chamada de variável de controle.
2. O segundo parâmetro é referente à condição que será verificada a cada repetição. Enquanto a expressão informada for avaliada
como verdadeira, o código continuará a ser repetido. No exemplo acima, definimos que o código será repetido enquanto o valor de
x for menor ou igual a 1000 .
3. O terceiro e último parâmetro é destinado à definição da alteração que a variável de controle irá sofrer a cada repetição.
Geralmente, definimos nesta posição uma expressão de incremento ou de decremento da variável de controle. Em nosso exemplo,
a variável x é incrementada em 1 a cada repetição.
De maneira resumida, podemos dizer que: considerando a(s) variável(eis) de controle definida(s), enquanto a condição informada for
verdadeira, o valor da(s) variável(eis) de controle será alterado e o código entre chaves será executado.
Observe que isto pode ser utilizado em qualquer momento no código, tanto na estrutura for quanto em qualquer outra estrutura de
repetição ou mesmo fora de uma estrutura de repetição.
x = x + 1;
x += 1;
x++;
Qualquer um dos métodos acima fará com que o valor contido em x seja incrementado em 1 , ou seja: se o valor de x for 5 , por
exemplo, x++ fará com que x passe a valer 6 .
x = x - 1;
x -= 1;
x--;
Caso seja necessário incrementar ou decrementar o valor de uma variável em mais que 1 , os operadores ++ ou -- não poderão ajudar.
Em casos como estes, apenas os dois primeiros métodos mostrados irão funcionar. Exemplo:
// Incremento
x = x + 3;
x += 3;
// Decremento
y = y - 5;
y -= 5;
Um caso bastante típico acontece em programas que “perguntam ao usuário” quando parar. Imagine um programa que, infinitamente,
leia um nome e responda com uma saudação. A cada repetição, este programa questiona ao usuário se ele deseja continuar.
while (true) {
nome = prompt("Entre com seu nome: ");
alert("Olá, " + nome + "! Tenha um bom dia.");
if (resposta == "n") {
break;
}
}
Perceba que, a estrutura de repetição while deverá ser executada infinitamente, já que a expressão entre parênteses é sempre true .
Após visualizar a saudação, o usuário deverá digitar n caso deseje parar o programa. Desta forma, enquanto a resposta não for n , o
programa continuará a ser executado.
Saiba que o comando break pode ser utilizado com qualquer uma das três estruturas de repetição.
Em algumas situações, encontraremos estruturas de repetição dentro de outras estruturas de repetição. Em casos assim, saiba que o
comando break irá parar apenas a estrutura de repetição mais próxima. Exemplo:
for (let x = 1; x <= 10; x++) {
for (let y = 1; y <= 5; y++) {
console.log("X: " + x + " - Y: " + y);
if ((x + y) % 3 == 0) {
console.log("É MÚLTIPLO DE 3. PAROU!");
break;
}
}
}
Neste caso, apenas o segundo for , o que utiliza a variável y como variável de controle, será parado. A estrutura anterior deverá seguir
para o próximo valor e dar continuidade ao código. Teste este exemplo e veja como ele se comporta.
Observe que no exemplo acima, a mensagem que mostra o próximo valor de i nunca será exibida porque toda iteração é interrompida
logo após a execução do primeiro console.log .
Crie um algoritmo que leia o nome e as 4 notas de 50 alunos, calcule a média de cada aluno e informe quais foram aprovados e
quais foram reprovados.
Como você faria se precisasse armazenar as 4 notas de cada um desses 50 alunos para mostrá-las no fim do seu programa?
Provavelmente, teria que declarar, no mínimo, 200 variáveis, certo?!
Com vetores ou matrizes, é possível armazenar inúmeros valores em uma única variável. Assim, você poderia ter algo semelhante a:
Contamos as posições de um vetor sempre a partir de 0 . A última posição de um vetor sempre poderá ser representada por n-1 , onde
n é o tamanho do vetor. Observe: em um vetor vet de tamanho n == 8 , a última posição de vet será representada pelo índice 7 ,
que corresponde a n-1 .
let numeros = [54, 21, 100, 89, 90, 32, 23, 74];
Neste caso, utilizamos um par de colchetes ( [ ] ) para criar uma lista de valores que seriam atribuídos à variável numeros . O termo
utilizado pela linguagem JavaScript para representar um vetor é array , que significa lista.
Apesar disto, nem sempre desejamos atribuir valores a um vetor logo em sua inicialização. Muitas vezes, queremos um vetor vazio que
possa ter seus valores informados pelo usuário ou através de atribuições sucessivas ao longo do programa. Poderíamos ter declarado o
vetor numeros sem nenhum valor e o preenchido ao longo do código:
Veja que cada valor lido ou atribuído foi armazenado em uma posição diferente do vetor, sempre representada por um índice numérico,
neste caso, de 0 a 7 , contemplando as 8 posições existentes.
Para simplificação do trabalho com vetores grandes, recomenda-se o uso de estruturas de repetição. Geralmente, utiliza-se a estrutura
de repetição para , que oferece um mecanismo mais simples para se trabalhar com intervalos. Observe o exemplo a seguir:
O algoritmo acima deverá ler 100 nomes, armazenar cada um deles em uma posição de um vetor e depois imprimi-los. Em linguagens
como C e Java, ao declarar a variável listaDeNomes seria necessário definir seu tamanho, que é igual a 100 .
Com uma estrutura de repetição for , percorremos o vetor desde a posição 0 até a 99, armazenando cada nome lido em uma das
posições.
Novamente, com uma estrutura de repetição, percorremos cada uma das posições, imprimindo o nome armazenado em cada uma delas.
Este mesmo algoritmo poderia ser construído com a estrutura de repetição while :
Ou do/while :
do {
listaDeNomes[x] = prompt();
x++;
} while (x <= 99);
Observe que nestes dois últimos exemplos nós não fizemos a impressão dos nomes lidos. Para que a impressão ocorra, apenas duplique
a estrutura de repetição, substituindo a linha de leitura por uma de escrita, utilizando console.log() ou document.write() .
Assim como variáveis comuns, em JavaScript, os vetores não possuem um tipo definido de dados que podem armazenar. Isto faz com que
possamos ter vetores como o seguinte:
Em linguagens como C, C++ e Java, isto não seria possível, já que, nestas linguagens, vetores são estruturas homogêneas (que armazenam
um único tipo de dado).
Dimensionamento de vetores
Na linguagem JavaScript, vetores são dinamicamente dimensionados, o que significa que não é necessário estipular o tamanho do vetor
no momento da sua criação (como é feito em linguagens como C, C++ e Java), o vetor terá, inicialmente, o tamanho 0 (zero) e irá
aumentar conforme utilizarmos seu espaço:
No exemplo acima, começamos a utilizar o vetor a partir da posição 0, a primeira posição disponível em um vetor. Mas, e se tivéssemos
começado da posição 5, por exemplo? Se apenas ocuparmos a posição 5, o tamanho do vetor será 1 ou 5?
Nem 1 nem 5. O tamanho do vetor, neste caso, será 6. Mas, por quê? Porque estamos ocupando a posição 5, que, na sequência, é a 6ª
posição do vetor. Para que possamos utilizar a 6ª posição, a linguagem gera posições vazias entre o índice 0 e o índice 4, como se
tivéssemos feito o seguinte:
// 0,1,2,3,4,5
let listaDeCompras = [, , , , , "Tomate"];
A propriedade length
Certo, já sabemos como um vetor é dimensionado, mas e se precisarmos descobrir o tamanho de um vetor qualquer? Imagine que você
tenha criado um programa que leia diversos valores em uma estrutura de repetição, mas você não saiba quantas vezes essa estrutura de
repetição foi executada. Em casos como esse (e em muitos outros), deve-se utilizar a propriedade length .
A propriedade length retorna o tamanho de um vetor. Basta utilizá-la junto ao nome do vetor com um ponto:
9.2. Matrizes
Semelhante ao conceito encontrado na Matemática, em Lógica de Programação, matriz é o nome que damos a estruturas de dados
bidimensionais. Enquanto que podemos representar visualmente um vetor como uma única linha ou coluna com diversos valores, uma
matriz se parece mais com uma tabela em que há uma ou mais linhas e colunas.
A matriz abaixo, por exemplo, é uma matriz de tamanho 3 X 3 , ou seja: 3 linhas e 3 colunas.
let tabela = [
[54, 21, 3],
[29, 99, 306],
[76, 5, 11],
];
let tabela = [
[54, 21, 3],
[29, 99, 306],
[76, 5, 11],
];
Anteriormente, dissemos que vetores são como “coleções de variáveis”. Se observarmos com atenção, cada par de colchetes nesta matriz
é um conjunto, uma coleção de valores, um vetor. Temos três conjuntos com três valores cada. Os três conjuntos estão dentro de um
conjunto maior, por isso dizemos que matrizes são como “coleções de vetores”.
Todos os conceitos vistos anteriormente sobre vetores são completamente válidos ao se tratar de matrizes, adicionando-se apenas mais
uma dimensão. Observe que, agora, para cada uma das n linhas temos m colunas. Desta forma, para nos referirmos a qualquer posição
desta matriz, precisaremos especificar uma linha e uma coluna:
Para percorrer matrizes, o método mais comumente utilizado é bastante semelhante ao utilizado para vetores. No entanto, como agora
precisamos percorrer tanto linhas quanto colunas, muitas vezes torna-se necessário o uso de estruturas de repetição encadeadas:
let tabela = [
[54, 21, 3],
[29, 99, 306],
[76, 5, 11],
];
No exemplo acima temos duas estruturas de repetição: a mais externa para percorrer as linhas da matriz, e a mais interna para percorrer
as colunas da matriz. A ideia é que, para cada linha, possamos percorrer todas as três colunas.
Para deixar este exemplo mais didático, teste-o da seguinte maneira e observe o que acontece:
let tabela = [
[54, 21, 3],
[29, 99, 306],
[76, 5, 11],
];
Para que uma matriz possua uma diagonal, esta matriz deve obrigatoriamente ser uma matriz quadrada, ou seja, uma matriz que possua
o mesmo número de linhas e colunas.
No exemplo acima, pode-se ver as diagonais primária e secundária de uma matriz de tamanho 3 X 3.
Se observarmos com calma, há padrões bastante simples pelos quais podemos identificar os elementos das diagonais de uma matriz
quadrada apenas com lógica de programação.
Os elementos da diagonal primária sempre estarão em posições em que o número da linha é igual ao número da coluna. Se desejarmos
percorrer os elementos da diagonal principal de uma determinada matriz de tamanho n , podemos contar de 0 a n-1 utilizando um
único contador para linhas e colunas:
let matriz = [
[-1, 2, -5],
[3, 0, -3],
[5, 7, -6],
];
let n = 3;
for (let x = 0; x < n; x++) {
console.log(matriz[x][x]);
}
Já os elementos da diagonal secundária de uma matriz de tamanho n podem ser identificados se começarmos contando de 0 nas linhas
e de n-1 nas colunas. O elemento seguinte da diagonal secundária sempre estará uma linha “após” e uma coluna “antes” da atual:
let matriz = [
[-1, 2, -5],
[3, 0, -3],
[5, 7, -6],
];
let n = 3;
for (let x = 0; x < n; x++) {
console.log(matriz[x][n - 1 - x]);
}
9.3. Métodos do objeto Array e outras utilidades
Neste capítulo conheceremos alguns dos métodos do objeto Array e outras utilidades, que facilitarão a forma como trabalhamos com
listas.
push()
unshift()
O método unshift() adiciona um ou mais elementos ao início de um array e retorna o novo tamanho ( length ) do array :
shift()
pop()
splice()
O métood splice() altera o conteúdo de um array , adicionando novos elementos e removendo antigos. O método retorna um array
contendo os elementos removidos e altera o array original permanentemente.
Este método aceita até três parâmetros: (1) índice inicial, (2) número de elementos a serem removidos a partir do índice inicial e (3)
elementos a serem inseridos.
slice()
O método slice() retorna um intervalo de um array , sem modificar o array original.
Este método aceita dois parâmetros: (1) posição inicial do intervalo e (2) posição final do intervalo.
Observe no exemplo acima que, embora tenhamos indicado o intervalo dos índices 2 a 4, apenas os elementos dos índices 2 e 3 foram
retornados. O slice não retorna o elemento da posição final informada.
É possível também utilizar um valor negativo no segundo parâmetro, indicando quantas posições contar a partir do final do array .
sort()
O método sort() ordena os elementos de um array conforme a tabela de caracteres Unicode e retorna o próprio array .
Observe no segundo exemplo acima que os números não estão necessariamente ordenados em ordem crescente, já que “10” vem antes
de “2” e “80” vem antes de “9” em código Unicode.
Você pode construir sua própria função de ordenação e aplicá-la ao método sort() . Passe pela documentação do método para ver
como.
reverse()
O método reverse() inverte os elementos de um array . O último passa a ser o primeiro e vice-versa.
indexOf()
O método indexOf() retorna o primeiro índice em que determinado elemento é encontrado em um array . O método retorna -1 se o
elemento não puder ser encontrado.
Este método aceita até dois parâmetros: (1) o valor a ser procurado e (2) a posição de onde começar a procurar.
No segundo exemplo, o número 6 pode ser encontrado também na posição 3 , mas estamos buscando a partir da posição 4 .
includes()
O método includes() verifica se um array possui determinado elemento e retorna true caso positivo ou false caso contrário.
Este método aceita até dois parâmetros: (1) o valor a ser procurado e (2) a posição de onde começar a procurar.
let cores = ["verde", "amarelo", "azul", "branco"];
let encontrou = cores.includes("amarelo");
console.log(encontrou); // true
for…of
O for..of não é um método do objeto Array , mas pode ser bastante útil como uma alternativa ao loop for que já conhecemos. Este
loop percorre um array e atribui a uma variável temporária o valor referente a cada posição.
10. Funções
Uma função é um procedimento armazenado, um bloco de código que pode ser executado a partir de um ponto específico de um
programa. Funções geralmente possuem nomes, mas, em JavaScript, é comum vermos funções anônimas.
function soma(a, b) {
return a + b;
}
Você deve ter notado que nada aconteceu. Isso porque a função foi apenas declarada. Agora, precisamos realizar a chamada desta
função. Para isto, se já tiver executado o exemplo acima, execute o seguinte código:
Se executou o código acima, você deve ter obtido o valor 30 impresso no console. A variável resultado foi criada apenas para
armazenar o retorno da função soma(a, b) . Como o nosso propósito era apenas escrever o retorno da função, poderíamos ter
simplificado para:
console.log(soma(10, 20)); // 30
Como falamos há pouco, embora didática, a função soma(a, b) não é um dos exemplos mais úteis de funções. E se pudéssemos
construir uma função que converta uma temperatura de Celsius para Fahrenheit, para que não precisemos nos lembrar da fórmula a
cada vez que for necessário realizar uma conversão?
function celsiusParaFahrenheit(temperaturaCelsius) {
return (9 * temperaturaCelsius + 160) / 5;
}
console.log(celsiusParaFahrenheit(32)); // 89.6
10.1. Funções sem parâmetro ou sem retorno
Como já dissemos, nem toda função possui parâmetro. Saiba também que nem toda função possui retorno. A seguir, construímos a
função mostrarHoras() , que mostra em uma caixa de diálogo o horário atual:
function mostrarHoras() {
let data = new Date();
alert(data.getHours() + ":" + data.getMinutes() + ":" + data.getSeconds());
}
A função mostrarHoras() não possui parâmetros nem um retorno explícito. No entanto, em JavaScript, se uma função não possuir
retorno explícito, esta retornará undefined . Tente imprimir o retorno desta função:
console.log(mostrarHoras()); // undefined
Você verá que, embora um alerta seja exibido com o horário atual, no console o valor impresso será undefined .
Parâmetros rest te possibilitam estabelecer uma variável que represente um número indeterminado de parâmetros que são tratados
como um array dentro de função. No exemplo abaixo, a função soma(...termos) retornará o resultado da soma de quantos termos
forem passados por parâmetro:
function soma(...termos) {
let resultado = 0;
for (i = 0; i < termos.length; i++) {
resultado += termos[i];
}
return resultado;
}
console.log(soma(2, 3, 5, 7, 1, 4, 10)); // 32
function fatorial(num) {
if (num > 1) {
return num * fatorial(num - 1);
}
return 1;
}
console.log(fatorial(4)); // 24
Diferentemente da solução convencional, com estruturas de repetição, a solução recursiva não requer o uso de uma variável para
armazenamento dos resultados temporários das multiplicações. A sequência abaixo expressa a resolução do problema de maneira
recursiva. Observe que em vez de estimarmos o resultado do fatorial de 3, na segunda linha, precisamos considerar o retorno da
execução de fat(3) , depois fat(2) e fat(1) para, só então, realizarmos as multiplicações devidas, voltando passo a passo, até
obtermos o resultado de 4 * 6 (ou 4 * fat(3) ), que é 24 .
fat(4);
4 * fat(3);
3 * fat(2);
2 * fat(1);
2 * 1;
3 * 2;
4 * 6;
24;
No exemplo a seguir, a função fibonacci(n) , recebe como parâmetro um inteiro n e retorna o valor do n-ésimo termo da sequência de
Fibonacci. Faça o exercício de tentar interpretá-la como fizemos no exemplo anterior. Lembre-se que cada chamada da função, desde que
n seja maior que 1 , resultará em duas novas chamadas.
function fibonacci(n) {
if (n <= 1) return 1;
A função acima é considerada anônima, pois não possui um nome. Mas como invocar uma função que não possui um nome? Neste caso,
por meio da variável soma , que carrega uma referência para a função anônima declarada:
console.log(soma(4, 4)); // 8
Há uma terceira forma de se declarar uma função, chamada arrow function ou função seta. O exemplo anterior poderia ser construído
com uma arrow function da seguinte forma:
console.log(soma(6, 3)); // 9
Assim como na forma que utilizamos até aqui, os parênteses servem para definir os argumentos (ou parâmetros) da função. Após a seta -
=> - é declarado o corpo da função.
Em uma arrow function que possui somente um argumento, os parênteses tornam-se opcionais. Da mesma forma, caso a função seja
composta por uma única expressão, o uso de chaves e da palavra return são opcionais, como no seguinte exemplo:
console.log(metade(8)); // 4
A função metade possui um único parâmetro numero e retorna o resultado da expressão numero / 2 .
Para a construção deste exemplo, utilizaremos a mesma função soma(x, y) , porém, agora com um terceiro argumento chamado
callback . Dentro do corpo da função soma(x, y, callback) , o argumento callback será invocado como uma função que receberá o
resultado da expressão x + y .
function soma(x, y, callback) {
callback(x + y);
}
No exemplo acima, o argumento callback pode ser qualquer função que receba um único argumento, e essa função pode fazer
absolutamente qualquer coisa com esse argumento.
É importante entendermos que, ao invocar a função soma , devemos informar três argumentos: dois números e uma função. Essa função
pode ser (1) declarada no ato da invocação ou (2) declarada previamente e apenas passada na lista de argumentos. Construiremos a
seguir um exemplo em que a função callback é declarada no ato da invocação da função soma . A função que usaremos como callback
deverá exibir no console o resultado da soma de x e y .
Observe que a função exibe possui um único argumento chamado valor , e imprime no console um texto contendo esse valor .
Sabemos que o resultado da impressão será O resultado é 5 , pois na declaração da função soma especificamos que a função que
fosse passada como callback seria invocada recebendo como argumento o resultado de x + y .
Como mencionamos anteriormente, uma função que será usada como callback também pode ser declarada previamente e apenas
informada na lista de argumentos, no ato da invocação da função que a receberá. A seguir, a função exibe é primeiro declarada, e
depois passada como argumento na invocação da função soma :
function exibe(valor) {
console.log(`O resultado é ${valor}`);
}
Funções callback também podem ser funções anônimas ou arrow functions. Inclusive, é muito mais comum vê-las sendo usadas nestas
duas formas:
A linguagem JavaScript faz uso massivo de funções callback. Em uma das seções anteriores conhecemos alguns dos métodos do objeto
Array, que são funções nativas da linguagem JavaScript que podem ser aplicadas a vetores. Além das que vimos, há outras funções
bastante úteis, que dependem de funções callback para que sejam utilizadas, como é o caso de map , filter , reduce e forEach .
A função (ou método) map nos permite percorrer um vetor e gerar um novo vetor a partir do vetor percorrido, com a possibilidade de
modificar os valores de cada posição. A cada posição percorrida no vetor original, uma função callback é invocada. O vetor que está
sendo gerado receberá para cada posição o valor retornado pela função callback.
A seguir, construiremos uma função chamada dobra(valor) , que retorna um valor multiplicado por 2. Depois utilizaremos a função
map da linguagem JavaScript para percorrer um vetor com valores numéricos e gerar um novo vetor com o dobro de cada valor do vetor
original.
function dobra(valor) {
return 2 * valor;
}
Além do caso da função map , e das outras citadas nesta seção, as funções callback são necessárias para vários outros processos na
linguagem JavaScript, inclusive para lidar com funções assíncronas. Mas esse é um assunto que deverá interessar apenas àqueles que
pretendem se aprofundar nesta linguagem. Para conhecer mais sobre funções assíncronas, acesse: https://developer.mozilla.org/pt-
BR/docs/Learn/JavaScript/Asynchronous/Concepts
Até agora tivemos acesso a dois recursos que nos possibilitaram (1) armazenar valores e, consequentemente, representar características,
e (2) definir e realizar ações. O primeiro deles são as variáveis e o segundo as funções. Objetos nos permitem concentrar estes dois
recursos. Imagine que precisemos representar um retângulo, concentrando suas características, como largura e altura :
Quando falamos em orientação a objetos, largura e altura são consideradas propriedades ou atributos do objeto retangulo . Após
definido o objeto retangulo , para imprimir sua largura , por exemplo, basta o seguinte:
console.log(retangulo.largura);
Se precisássemos definir uma função para cálculo da área, bastaria adicionar mais uma propriedade e vincular a ela uma função:
let retangulo = {
largura: 5,
altura: 4,
calcularArea: function () {
return this.largura * this.altura;
},
};
Não se preocupe com o this no exemplo anterior. Ele será explicado na seção seguinte.
Por ser vinculada a um objeto, a função calcularArea() também pode ser chamada de método. Agora, o cálculo da área pode ser
obtido da seguinte maneira:
console.log(retangulo.calcularArea());
Saiba que você já estava lidando com objetos desde os primeiros exemplos que realizamos neste material. Na verdade, o que se diz é que
praticamente todas as coisas são objetos em JavaScript. O próprio console é um objeto e log() é uma função (ou método) que
pertence a ele. A função alert() pertence ao objeto window , embora não seja obrigatório mencioná-lo:
window.alert("Teste isso.");
Para aprender mais sobre objetos em JavaScript, acesse Trabalhando com objetos.
11.1. Classes
A definição mais simples e objetiva para classes seria que classes são modelos para a criação de objetos. Classes são um recurso
existente em outras linguagens já há um bom tempo. Na linguagem JavaScript, porém, as classes só foram introduzidas por volta de 2015.
Em uma classe se pode definir, por exemplo, quais atributos e métodos um objeto, que ainda será criado, deverá possuir. A grande
vantagem é que esse modelo pode ser reaproveitado para a criação de quantos objetos forem necessários, diferentemente do que
fizemos na seção anterior.
O código a seguir mostra a declaração de uma classe Pessoa , com as propriedades nome e idade , e o método apresentar() .
class Pessoa {
nome;
idade;
apresentar() {
console.log(
"Olá! Meu nome é " + this.nome + " e tenho " + this.idade + " anos."
);
}
}
Para criar objetos a partir da classe Pessoa e utilizá-los, podemos executar o seguinte código:
pessoa1.nome = "Alice";
pessoa1.idade = 19;
pessoa2.nome = "Hugo";
pessoa2.idade = 21;
pessoa1.apresentar();
pessoa2.apresentar();
O operador this
Observe que para fazer referência às propriedades nome e idade dentro do método apresentar() na classe Pessoa , foi necessário
utilizar o operador this . Sempre que precisar referenciar uma propriedade dentro de um método ou referenciar um método dentro de
outro, utilize o operador this . O termo this pode ser traduzido do inglês como este, esta ou isto. Ao usar this.nome o que queremos
dizer é que nos referimos à propriedade nome desta classe.
A importância de se utilizar o operador this fica mais clara quando enxergamos além do escopo da classe. Considere o seguinte
exemplo:
class Mensagem {
texto = "Não este.";
exibir() {
console.log(texto);
}
}
Como não utilizamos o this , o JavaScript irá considerar a variável texto como foi declarada fora da classe. Modifique o método
exibir() adicionando o operador this à variável texto e observe a diferença.
O construtor
Em uma expressão como new Pessoa() , o operador new é o responsável pela criação de objetos. Quando utilizado, o operador new
executa o método construtor da classe. O propósito deste método é definir como se dará a criação do objeto.
Por padrão, toda classe possui um construtor, mesmo que não explícito. Um construtor padrão não possui nenhum comportamento
específico, no entanto, podemos modificá-lo conforme a necessidade. O construtor abaixo exibirá uma mensagem sempre que um novo
objeto do tipo Triangulo for criado:
class Triangulo {
base;
altura;
constructor() {
console.log("Um novo triângulo foi criado.");
}
}
A principal utilidade de um construtor é definir valores para as propriedades do objeto em sua criação. Anteriormente, criamos a classe
Pessoa e, sempre que construíamos um novo objeto a partir dessa classe, era necessário acessar cada uma das propriedades e
modificá-la: pessoa1.nome = "Tal nome" . No exemplo abaixo, reconstruímos a classe Pessoa com um construtor que nos permite criar
objetos já com nome e idade inicializados:
class Pessoa {
constructor(nome, idade) {
this.nome = nome;
this.idade = idade;
}
apresentar() {
console.log(
"Olá! Meu nome é " + this.nome + " e tenho " + this.idade + " anos."
);
}
}
Observe que não foi necessário declarar nome e idade antes do construtor, como fizemos no primeiro exemplo da classe Pessoa .
Como estas duas propriedades já têm seus valores inicializados no construtor, seria redundante tê-los declarado antes. Para utilizar este
construtor, bastaria o seguinte:
pessoa1.apresentar();
Eventualmente, necessitamos que métodos ou atributos de um objeto não sejam acessados diretamente por quem o está manipulando.
Geralmente isso ocorre com atributos que possuem valores sensíveis e que não devem ser modificados sem um critério específico.
Por padrão, todos os atributos e métodos de uma classe são públicos, o que significa que eles podem ser acessados e modificados a
qualquer momento após a criação de um objeto. Um atributo ou método que recusa esse tipo de comportamento é considerado
privado.
Imagine um sistema que manuseie dados de vários clientes. Vez ou outra um cliente erra o número do seu documento de idade (RG)
durante o cadastro e precisa modificar esse valor depois. Porém, por uma política do sistema, um número de documento nunca deve ser
alterado sem que antes se registre que essa alteração ocorreu.
class Cliente {
nome;
rg;
constructor(nome, rg) {
this.nome = nome;
this.rg = rg;
}
}
Na classe acima, nada garante que iremos registrar a alteração do número do RG. Podemos simplesmente criar um novo objeto Cliente
e modificar seu rg a qualquer momento:
Para evitar a alteração indiscriminada do valor de rg , podemos tornar este atributo privado utilizando o sinal # (cerquilha) antes de seu
nome:
class Cliente {
nome;
#rg;
constructor(nome, rg) {
this.nome = nome;
this.#rg = rg;
}
}
A partir de agora, se criarmos um novo objeto Cliente e tentarmos acessar ou modificar o atributo #rg veremos uma mensagem de
erro que indica que este campo privado só pode ser acessado de dentro da classe:
E se tentarmos utilizar rg sem o sinal de # acabaremos criando um novo atributo; assim teremos um rg público e um #rg privado.
Você provavelmente não vai querer fazer isso:
console.log(cliente);
Então, como modificar um atributo privado? Por meio de um método público. Como atributos privados só podem ser acessados e
modificados a partir de dentro da classe, um método que seja público poderá fazer esse acesso ou alteração por nós:
class Cliente {
nome;
#rg;
constructor(nome, rg) {
this.nome = nome;
this.#rg = rg;
}
alterarRg(novoRg) {
console.log(
`O RG de ${this.nome} foi alterado de ${this.#rg} para ${novoRg}.`
);
this.#rg = novoRg;
}
exibirDados() {
console.log(`Nome do Cliente: ${this.nome}`);
console.log(`RG do Cliente: ${this.#rg}`);
}
}
Agora podemos criar um objeto Cliente , modificar seu #rg por meio do método público alterarRg() , que registra no console a
alteração que está ocorrendo, e ainda podemos visualizar os novos dados por meio do método exibirDados() :
cliente.alterarRg("750021");
cliente.exibirDados();
Até o momento, usamos classes para definir atributos e métodos, mas foram os objetos que os utilizaram. Em algumas situações pode
ser necessário definir atributos e métodos que serão utilizados pela classe, e não pelos objetos. Atributos e métodos assim são chamados
de estáticos.
Quando um atributo ou método é definido como estático, somente a classe pode acessá-lo e modificá-lo. Objetos não possuem acesso a
atributos e métodos estáticos.
A seguir, vamos utilizar um atributo estático para contar quantos objetos já foram criados a partir da classe Veiculo :
class Veiculo {
fabricante;
modelo;
static contador = 0;
constructor(fabricante, modelo) {
this.fabricante = fabricante;
this.modelo = modelo;
Veiculo.contador++;
}
}
Ao declarar o atributo contador com a palavra-chave static estamos definindo que este atributo deverá permanecer e ser acessado
somente pela classe Veiculo e não por seus objetos. Veja o que acontece no código abaixo:
console.log(uno.contador); // undefined
console.log(Veiculo.contador); // 1
Quando tentamos acessar o atributo contador por meio do objeto uno , obtemos o resultado undefined , porém, ao acessar essa
propriedade por meio do nome da classe, podemos ver que seu valor é 1 .
Também pode fazer sentido que determinados métodos sejam estáticos, principalmente quando se deseja manipular um atributo
estático ou executar alguma ação independente de objetos:
class Veiculo {
fabricante;
modelo;
static contador = 0;
constructor(fabricante, modelo) {
this.fabricante = fabricante;
this.modelo = modelo;
Veiculo.contador++;
}
static reiniciarContador() {
this.contador = 0;
}
}
No exemplo acima, a palavra-chave this dentro do método estático reiniciarContador() faz referência à classe Veiculo , e não a um
objeto. Teste com o código abaixo:
console.log(Veiculo.contador); // 3
Veiculo.reiniciarContador();
console.log(Veiculo.contador); // 0
11.2 Herança
A programação orientada a objetos carrega influências bastante notáveis da biologia. A ideia de se utilizar classes na programação,
inclusive, parece ter sido motivada pela Taxonomia, na qual os organismos são organizados em agrupamentos (classes) conforme
compartilham de características em comum.
Por vezes, iremos nos deparar com classes diferentes que compartilham das mesmas propriedades (atributos e métodos). Em alguns
desses casos, faremos uso de um dos principais recursos da programação orientada a objetos: a herança.
A herança é o relacionamento em que uma classe mãe compartilha seus atributos e métodos de forma unilateral com uma ou mais
classes filhas. Dizemos unilateral porque somente a(s) classe(s) filha(s) recebe(m) propriedades da mãe, mas não o inverso.
Dadas como exemplo as classes A e B , essas duas são candidatas a um relacionamento de herança quando podemos afirmar que A é
um(a) B ou que B é um(a) A .
Um exemplo bastante prático disso seriam as classes Pessoa e Professor . Podemos afirmar que Professor é uma Pessoa . Neste
caso, faz muito sentido que a classe Professor seja uma classe filha (ou subclasse) da classe Pessoa , a classe mãe (ou superclasse). Mas
por que não o contrário? Veja: toda Pessoa pode possuir um nome e uma idade ; todo Professor também. Já um Professor pode
possuir algo como um curso em que leciona; nem toda Pessoa leciona em um curso. Percebemos que as propriedades da classe
Pessoa são comuns à classe Professor , mas esta última pode possuir propriedades que são somente suas.
class Pessoa {
nome;
idade;
constructor(nome, idade) {
this.nome = nome;
this.idade = idade;
}
apresentar() {
console.log(`Olá! Meu nome é ${this.nome} e tenho ${this.idade} anos.`);
}
}
Para criar uma nova classe Professor , que possua as mesmas propriedades da classe Pessoa , podemos fazer apenas:
Junte as duas classes anteriores ao código a seguir e verifique como um objeto do tipo Professor pode ser criado exatamente como
criaríamos um objeto do tipo Pessoa :
Ao declarar a classe Professor com a palavra-chave extends temos a possibilidade de informar de que classe a classe Professor é
derivada. Neste caso, escolhemos a classe Pessoa . Isto significa que a classe Professor terá acesso aos mesmo atributos e métodos já
declarados na classe Pessoa , inclusive seu construtor.
Porém, faria pouco sentido criar novas classes utilizando extends se essas novas classes forem sempre iguais às suas classes mães, ou
superclasses. Como faríamos para adicionar novas propriedades à classe Professor mantendo as que já foram definidas na classe
Pessoa ?
No exemplo acima, acrescentamos à classe Professor o atributo curso . Mas para que esse atributo pudesse ser inicializado já na
criação do objeto, seria necessário modificar também o construtor da classe:
Se você tentou criar um objeto a partir do construtor acima, deve ter se deparado com uma mensagem de erro que indicava que é
necessário utilizar super() antes do this em uma classe derivada:
Uncaught ReferenceError: Must call super constructor in derived class before accessing 'this' or returning
from derived constructor
Neste caso, o operador super() se refere ao construtor da superclasse Pessoa . Como Professor está estendendo a classe Pessoa , a
criação de objetos do tipo Professor é feita por meio do construtor da classe Pessoa .
Sendo assim, no construtor de nossa subclasse Professor , deveremos invocar primeiro o construtor da classe Pessoa por meio do
operador super() , e poderemos passar os mesmos parâmetros que são aceitos no construtor original: nome e idade . Somente após
isso é que poderemos inicializar os atributos específicos da classe Professor .
Assim temos um novo construtor que inicializa não só nome e idade , que são comuns à qualquer Pessoa , mas também inicializamos o
atributo curso , específico de Professor .
Por fim, subclasses podem tanto definir novos comportamentos para métodos já existentes em suas classes mães, como também podem
definir novos métodos:
apresentar() {
console.log(
`Olá! Eu sou o(a) Prof(a). ${this.nome} e leciono no curso de ${this.curso}.`
);
}
}
Neste último exemplo modificamos o método apresentar() para que ele exiba uma mensagem diferente da exibida por objetos do tipo
Pessoa .
ReferenceError
console.log(x);
No código acima, a variável x nunca foi declarada. A seguinte mensagem de erro será exibida:
console.Log("Olá, mundo!");
No código acima, o nome da função console.log() está escrito incorretamente, com uma letra maiúscula. A seguinte mensagem de
erro será exibida:
SyntaxError
console..log("Olá, mundo!")
No código acima, há dois pontos separando o objeto console da função log . A seguinte mensagem de erro será exibida:
Lista 7 - Vetores
1. Com um vetor, leia 5 números inteiros e imprima-os.
2. Com um vetor, leia 5 nomes e imprima-os.
3. Com um vetor, leia 5 números inteiros e imprima-os em ordem inversa. Ex.: 5,1,4,8,0 – 0,8,4,1,5.
4. Com um vetor, leia 10 números reais. Imprima os números lidos, multiplicando os números de posições ímpares por 1.05, e os de
posições pares por 1.02.
5. Com um vetor, leia 5 números reais, imprima-os na ordem lida e na ordem inversa.
6. Declare dois vetores, preencha o primeiro com 10 números inteiros lidos, copie o conteúdo do primeiro para o segundo, depois
imprima os dois vetores lado a lado.
7. Em um vetor que contém as médias de uma turma de 10 alunos, calcule a média da turma e conte quantos alunos obtiveram nota
acima da média da turma. Escreva a média da turma e o total de alunos acima da média.
8. Declare um vetor com 5 nomes de pessoas diferentes. Em seguida, permita que o usuário digite um nome. Se este nome estiver no
vetor, informe que tal pessoa foi encontrada. Senão, informe que tal pessoa não foi encontrada.
9. Declare três vetores. Preencha o primeiro e depois o segundo, cada um com 5 números inteiros lidos. Por fim, percorra os três
vetores simultaneamente, preenchendo o terceiro com as somas dos outros dois. Imprima os três lado a lado.
Lista 8 - Matrizes
Na presente lista de exercícios, quando o enunciado disser “Gere uma matriz”, você deverá declarar a matriz vazia e utilizar estruturas de
repetição para percorrê-la e atribuir valores automaticamente. Nesse caso, os valores não serão informados pelo usuário; exceto o
tamanho da matriz, quando especificado. Por exemplo, poderíamos atribuir um número inteiro aleatório, entre 1 e 100, à variável x da
seguinte maneira:
Quando se disser “Leia uma matriz”, entenda que todos os elementos da matriz deverão ser lidos; ou seja, informados pelo usuário.
1. Leia e armazene nome , idade e salário de 5 pessoas em uma única matriz. Ao fim, exiba a matriz em formato tabular. Ex.:
José 30 3000.00
Maria 28 3200.00
Carlos 41 9500.00
Joaquim 56 12000.00
Silvia 32 10000.00
2. Leia uma matriz 3 x 3 de inteiros. Ao fim, exiba a matriz em formato tabular e também o elemento do centro. Ex.:
1 2 3
4 5 6
7 8 9
Elemento do centro: 5
3. Leia uma matriz quadrada N x N de inteiros, onde N é um inteiro informado pelo usuário. Exiba a matriz em formato tabular.
4. Leia uma matriz N x M de inteiros, onde N e M são inteiros informados pelo usuário. Exiba a matriz em formato tabular.
5. Gere uma matriz 4 x 4 de inteiros. Ao fim, exiba a matriz em formato tabular e também os elementos dos cantos. Ex.:
1 7 5 3
5 2 0 9
3 4 8 1
2 5 3 9
6. Gere uma matriz 3 x 3 de inteiros aleatórios. Exiba a matriz em formato tabular e também os resultados das somas dos elementos
da diagonal principal e da diagonal secundária. Ex.:
5 3 2
8 5 4
7 2 3
7. Gere uma matriz 10 x 10 de inteiros, onde o valor de cada elemento é dado pela soma de seus índices. Exiba a matriz em formato
tabular. Ex.:
0 1 2 3 4 5 6 7 8 9
1 2 3 4 5 6 7 8 9 10
2 3 4 5 6 7 8 9 10 11
...
9 10 11 12 13 14 15 16 17 18
8. Gere uma matriz identidade N x N , onde N é um inteiro informado pelo usuário. Uma matriz identidade é uma matriz quadrada em
que os elementos de sua diagonal principal são todos iguais a 1 e os demais elementos iguais a 0 . Exiba a matriz identidade em formato
tabular. Ex.:
1 0 0 0
0 1 0 0
0 0 1 0
0 0 0 1
9. Gere uma matriz 3 x 3 inteiros aleatórios, exiba a matriz em formato tabular, depois escreva os elementos pares e os ímpares dessa
matriz, separados e na ordem em que aparecem. Ex.:
8 1 3
5 6 7
3 4 2
Pares: 8, 6, 4, 2
Ímpares: 1, 3, 5, 7, 3
10. Leia uma matriz 2 x 3 de inteiros e exiba essa matriz em formato tabular. Logo após, leia um inteiro D e escreva os elementos
dessa matriz que sejam divisíveis por D .
11. Gere duas matrizes 4 x 2 de inteiros aleatórios, A e B . Em seguida, gere uma matriz resultante de A + B e exiba as três em
formato tabular.
12. Considerando que N é um inteiro >= 3 informado pelo usuário, gere uma matriz quadrada N x N , em que os elementos às
margens da matriz sejam iguais a 1 e os internos sejam iguais a 0 . Ao fim, exiba a matriz em formato tabular. Ex.:
1 1 1 1 1 1 1 1 1 1 1 1
1 0 1 1 0 0 1 1 0 0 0 1
1 1 1 1 0 0 1 1 0 0 0 1
1 1 1 1 1 0 0 0 1
1 1 1 1 1
13. Considerando que N e M são inteiros informados pelo usuário, leia uma matriz N x M de inteiros, e gere uma matriz transposta a
partir dela. Exiba a matriz original e a matriz transposta em formato tabular. Ex.:
Matriz original:
1 2 3
4 5 6
Matriz transposta:
1 4
2 5
3 6
14. Considerando que N e M são inteiros informados pelo usuário, leia uma matriz N x M de inteiros, e gere uma matriz rotacionada em
90 graus em sentido horário a partir dela. Exiba a matriz original e a matriz rotacionada em formato tabular. Ex.:
Matriz original:
1 2
3 4
5 6
Matriz rotacionada:
5 3 1
6 4 2
15. Utilizando uma matriz de 3 x 3 , construa um Jogo da Velha que possa ser jogado por duas pessoas. Ao fim do jogo, informe quem
venceu ou se houve empate.
Lista 9 - Funções
1. Crie uma função escreva(texto) que receba um texto como parâmetro e exiba esse texto no console.
2. Crie uma função soma(x, y) que receba dois números como parâmetros e retorne sua soma.
3. Crie uma função hora() que retorne o horário atual do sistema no formato horas:minutos:segundos .
4. Crie uma função mostrarHora() que escreva no console o horário atual do sistema no formato horas:minutos:segundos .
5. Crie uma função quadrado(num) que receba um número como parâmetro e retorne o seu quadrado.
6. Crie uma função cubo(num) que receba um número como parâmetro e retorne o seu cubo.
7. Crie uma função potencia(num, expoente) que receba como parâmetros um número ( num ) e um expoente, e retorne a
potenciação/exponenciação de num elevado a expoente .
8. Crie uma função recursiva potencia(num, expoente) que receba como parâmetros um número ( num ) e um expoente, e retorne a
potenciação/exponenciação de num elevado a expoente . Nesta função não são permitidas estruturas de repetição.
9. Crie uma função imc(peso, altura) que receba os parâmetros peso e altura , e retorne o Índice de Massa Corporal resultante.
10. Crie uma função calculaAreaQuadrado(lado) que calcule e retorne a área de um quadrado com base na medida de seu lado.
11. Crie uma função calculaAreaRetangulo(largura, altura) que calcule e retorne a área de um retângulo com base em sua largura
e altura.
12. Crie uma função calculaAreaCirculo(raio) que calcule e retorne a área de um círculo com base em seu raio.
13. Crie uma função mostrarLista(lista) que receba um array como parâmetro, percorra cada uma de suas posições e as escreva
separadamente no console.
14. Crie uma função matrizNula(matriz) que receba uma matriz como parâmetro, retorne true caso a matriz seja nula e false
caso contrário.
15. Crie uma função matrizQuadrada(matriz) que receba uma matriz como parâmetro, retorne true caso a matriz seja quadrada e
false caso contrário.
16. Crie uma função matrizDiagonal(matriz) que receba uma matriz como parâmetro, retorne true caso a matriz seja diagonal e
false caso contrário. Utilize a função matrizQuadrada(matriz) para verificar se a matriz é quadrada antes de verificar se é uma
matriz diagonal.
17. Crie uma função soma(...termos) que retorne o resultado da soma de um número indefinido de termos .
18. Crie uma função escreva(...valores) que funcione exatamente como a console.log() : escreva um número indefinido de
valores no console.
19. Crie uma função mediaAritmetica(...termos) que receba como parâmetro um número indefinido de termos e retorne sua
média aritmética.
20. Crie uma função ehPrimo(numero) que receba um número natural como parâmetro e retorne true caso o número seja primo ou
false caso contrário.
21. Crie uma função ehPerfeito(numero) que receba um número natural como parâmetro e retorne true caso o número seja
perfeito ou false caso contrário. Um número é perfeito quando é igual à soma dos seus divisores sem contar com ele mesmo. Ex.:
6 é perfeito porque 1 + 2 + 3 = 6 .
Lista 10 - Objetos
1. Construa uma classe Pessoa , contendo algumas propriedades, como nome , idade e sexo e um método apresenta() , que exiba
uma mensagem de apresentação com os dados desta pessoa.
2. Construa uma classe Retangulo , contendo as propriedades altura e largura , e um método area() ou calculaArea() . A área
de um retângulo é dada pela seguinte fórmula: A = b * h , onde A representa área, b representa base e h representa altura.
3. Construa uma classe Cubo , contendo a propriedade aresta e um método volume() ou calculaVolume() . Um cubo possui
medidas iguais em todas as suas arestas, o que resulta em seus lados todos iguais. O volume de um cubo é dado pela fórmula V =
a ** 3 , onde V representa o volume e a a medida da aresta.
4. Construa uma classe Carro , contendo as propriedades modelo , marca , cor , portas , ano , anoModelo , ligado , farolLigado
velocidade , e os métodos ligaDesliga() , acelera() , freia() e ligaDesligaFarol() . Os métodos devem interagir com as
propriedades do objeto. Seja criativo.
Referências
LOPES, Anita. GARCIA, Guto. Introdução à programação – 500 algoritmos resolvidos. Rio de Janeiro: Elsevier, 2002 - 15ª Tiragem.
MOZILLA. Guia JavaScript. Mozilla Developer Network. MDN Web Docs. Disponível em: https://developer.mozilla.org/pt-
BR/docs/Web/JavaScript/Guide.
MOZILLA. Uma reintrodução ao JavaScript (Tutorial de JS). Mozilla Developer Network. MDN Web Docs. Disponível em:
https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/A_re-introduction_to_JavaScript.