Como sabemos no capítulo Tipos de dados, existem oito tipos de dados em JavaScript. Sete deles são chamados de “primitivos”, porque seus valores contêm apenas uma única coisa (seja uma string ou um número ou qualquer outra coisa).
Em contraste, os objetos são usados para armazenar coleções codificadas de vários dados e entidades mais complexas. Em JavaScript, os objetos penetram em quase todos os aspectos da linguagem. Portanto, devemos entendê-los primeiro, antes de nos aprofundarmos em qualquer outro lugar.
Um objeto pode ser criado com colchetes {…}
com uma lista opcional de propriedades . Uma propriedade é um par “chave: valor”, onde key
é uma string (também chamada de “nome da propriedade”) e value
pode ser qualquer coisa.
Podemos imaginar um objeto como um armário com arquivos assinados. Cada dado é armazenado em seu arquivo pela chave. É fácil encontrar um arquivo pelo nome ou adicionar/remover um arquivo.
Um objeto vazio (“gabinete vazio”) pode ser criado usando uma das duas sintaxes:
deixe usuário = new Object(); // sintaxe do "construtor de objeto" deixe usuário = {}; // sintaxe "objeto literal"
Normalmente, são usados os colchetes {...}
. Essa declaração é chamada de objeto literal .
Podemos colocar imediatamente algumas propriedades em {...}
como pares “chave: valor”:
deixe usuário = { // um objeto name: "John", // pela chave "name" armazena valor "John" idade: 30 // pela chave "idade" armazena valor 30 };
Uma propriedade tem uma chave (também conhecida como “nome” ou “identificador”) antes dos dois pontos ":"
e um valor à direita dela.
No objeto user
, existem duas propriedades:
A primeira propriedade tem o nome "name"
e o valor "John"
.
O segundo tem o nome "age"
e o valor 30
.
O objeto user
resultante pode ser imaginado como um gabinete com dois arquivos assinados denominados “nome” e “idade”.
Podemos adicionar, remover e ler arquivos dele a qualquer momento.
Os valores das propriedades são acessíveis usando a notação de ponto:
//obtém os valores das propriedades do objeto: alerta(usuário.nome); // John alerta(usuário.idade); //30
O valor pode ser de qualquer tipo. Vamos adicionar um booleano:
usuário.isAdmin = verdadeiro;
Para remover uma propriedade, podemos usar o operador delete
:
excluir usuário.idade;
Também podemos usar nomes de propriedades com várias palavras, mas eles devem ser colocados entre aspas:
deixe usuário = { nome: "João", idade: 30, "gosta de pássaros": true // o nome da propriedade multipalavras deve ser colocado entre aspas };
A última propriedade da lista pode terminar com uma vírgula:
deixe usuário = { nome: "João", idade: 30, }
Isso é chamado de vírgula “à direita” ou “suspensa”. Torna mais fácil adicionar/remover/mover propriedades, porque todas as linhas se tornam iguais.
Para propriedades multipalavras, o acesso por ponto não funciona:
// isso daria um erro de sintaxe user.likes pássaros = verdadeiro
JavaScript não entende isso. Ele pensa que abordamos user.likes
e, em seguida, apresenta um erro de sintaxe quando se depara com birds
inesperados.
O ponto exige que a chave seja um identificador de variável válido. Isso implica: não contém espaços, não começa com dígito e não inclui caracteres especiais ( $
e _
são permitidos).
Existe uma “notação de colchetes” alternativa que funciona com qualquer string:
deixe usuário = {}; // definir user["gosta de pássaros"] = true; // pegar alert(usuário["gosta de pássaros"]); // verdadeiro //excluir deletar usuário["gosta de pássaros"];
Agora está tudo bem. Observe que a string entre colchetes está entre aspas corretamente (qualquer tipo de aspas serve).
Os colchetes também fornecem uma maneira de obter o nome da propriedade como resultado de qualquer expressão – em oposição a uma string literal – como a partir de uma variável, como segue:
let key = "gosta de pássaros"; // igual a user["gosta de pássaros"] = true; usuário[chave] = verdadeiro;
Aqui, a key
variável pode ser calculada em tempo de execução ou depender da entrada do usuário. E então usamos para acessar a propriedade. Isso nos dá muita flexibilidade.
Por exemplo:
deixe usuário = { nome: "João", idade: 30 }; let key = prompt("O que você quer saber sobre o usuário?", "nome"); //acesso por variável alerta(usuário[chave]); // John (se inserir "nome")
A notação de ponto não pode ser usada de maneira semelhante:
deixe usuário = { nome: "João", idade: 30 }; deixe chave = "nome"; alerta(user.key) // indefinido
Podemos usar colchetes em um literal de objeto, ao criar um objeto. Isso é chamado de propriedades computadas .
Por exemplo:
let fruta = prompt("Qual fruta comprar?", "maçã"); deixe saco = { [fruta]: 5, // o nome da propriedade é retirado da variável fruta }; alerta(bag.apple); // 5 se fruta="maçã"
O significado de uma propriedade computada é simples: [fruit]
significa que o nome da propriedade deve ser retirado de fruit
.
Portanto, se um visitante inserir "apple"
, bag
se tornará {apple: 5}
.
Essencialmente, isso funciona da mesma forma que:
let fruta = prompt("Qual fruta comprar?", "maçã"); deixe saco = {}; // pega o nome da propriedade da variável fruta saco[fruta] = 5;
…Mas parece melhor.
Podemos usar expressões mais complexas entre colchetes:
deixe fruta = 'maçã'; deixe saco = { [fruta + 'Computadores']: 5 // bag.appleComputers = 5 };
Os colchetes são muito mais poderosos do que a notação de ponto. Eles permitem quaisquer nomes de propriedades e variáveis. Mas também são mais complicados de escrever.
Portanto, na maioria das vezes, quando os nomes das propriedades são conhecidos e simples, o ponto é usado. E se precisarmos de algo mais complexo, mudamos para colchetes.
Em código real, geralmente usamos variáveis existentes como valores para nomes de propriedades.
Por exemplo:
function makeUser(nome, idade) { retornar { nome: nome, idade: idade, // ...outras propriedades }; } deixe usuário = makeUser("John", 30); alerta(usuário.nome); // John
No exemplo acima, as propriedades têm os mesmos nomes das variáveis. O caso de uso de criar uma propriedade a partir de uma variável é tão comum que existe uma abreviação especial do valor da propriedade para torná-lo mais curto.
Em vez de name:name
podemos apenas escrever name
, assim:
function makeUser(nome, idade) { retornar { nome, // igual a nome: nome idade, // igual a idade: idade // ... }; }
Podemos usar propriedades normais e abreviações no mesmo objeto:
deixe usuário = { nome, // igual a nome:nome idade: 30 };
Como já sabemos, uma variável não pode ter um nome igual a uma das palavras reservadas ao idioma como “for”, “let”, “return” etc.
Mas para uma propriedade de objeto, não existe tal restrição:
// essas propriedades estão corretas deixe obj = { para: 1, deixe: 2, retorno: 3 }; alerta( obj.for + obj.let + obj.return ); //6
Resumindo, não há limitações nos nomes das propriedades. Eles podem ser quaisquer strings ou símbolos (um tipo especial para identificadores, que será abordado posteriormente).
Outros tipos são automaticamente convertidos em strings.
Por exemplo, um número 0
torna-se uma string "0"
quando usado como chave de propriedade:
deixe obj = { 0: "teste" // igual a "0": "teste" }; // ambos os alertas acessam a mesma propriedade (o número 0 é convertido na string "0") alerta(obj["0"]); // teste alerta(obj[0]); //teste (mesma propriedade)
Há uma pequena pegadinha com uma propriedade especial chamada __proto__
. Não podemos configurá-lo para um valor que não seja de objeto:
deixe obj = {}; obj.__proto__ = 5; // atribui um número alerta(obj.__proto__); // [object Object] - o valor é um objeto, não funcionou como esperado
Como podemos ver no código, a atribuição a um primitivo 5
é ignorada.
Abordaremos a natureza especial de __proto__
nos capítulos subsequentes e sugeriremos maneiras de corrigir tal comportamento.
Uma característica notável dos objetos em JavaScript, em comparação com muitas outras linguagens, é que é possível acessar qualquer propriedade. Não haverá erro se a propriedade não existir!
Ler uma propriedade inexistente apenas retorna undefined
. Portanto, podemos testar facilmente se a propriedade existe:
deixe usuário = {}; alerta(user.noSuchProperty === indefinido); // true significa "não existe tal propriedade"
Há também um operador especial "in"
para isso.
A sintaxe é:
"chave" no objeto
Por exemplo:
deixe usuário = {nome: "John", idade: 30}; alert("idade" no usuário); // verdadeiro, user.age existe alert("blabla" em usuário); // falso, user.blabla não existe
Observe que no lado in
deve haver um nome de propriedade . Geralmente é uma string entre aspas.
Se omitirmos as aspas, isso significa que uma variável deve conter o nome real a ser testado. Por exemplo:
deixe usuário = {idade: 30}; deixe chave = "idade"; alert(digite usuário); // verdadeiro, a propriedade "idade" existe
Por que existe o operador in
? Não é suficiente comparar com undefined
?
Bem, na maioria das vezes a comparação com undefined
funciona bem. Mas há um caso especial em que falha, mas "in"
funciona corretamente.
É quando existe uma propriedade do objeto, mas armazena undefined
:
deixe obj = { teste: indefinido }; alerta(obj.teste); // é indefinido, então - não existe tal propriedade? alerta("teste" em obj); // verdadeiro, a propriedade existe!
No código acima, a propriedade obj.test
existe tecnicamente. Portanto, o operador in
funciona corretamente.
Situações como esta acontecem muito raramente, porque undefined
não deve ser atribuído explicitamente. Usamos principalmente null
para valores “desconhecidos” ou “vazios”. Portanto, o operador in
é um convidado exótico no código.
Para percorrer todas as chaves de um objeto, existe uma forma especial de loop: for..in
. Isso é algo completamente diferente da construção for(;;)
que estudamos antes.
A sintaxe:
for (digite o objeto) { //executa o corpo de cada chave entre as propriedades do objeto }
Por exemplo, vamos gerar todas as propriedades de user
:
deixe usuário = { nome: "João", idade: 30, isAdmin: verdadeiro }; for (deixe digitar o usuário) { //chaves alerta(chave); //nome, idade, isAdmin //valores para as chaves alerta(usuário[chave]); // João, 30 anos, verdadeiro }
Observe que todas as construções “for” nos permitem declarar a variável de loop dentro do loop, como let key
aqui.
Além disso, poderíamos usar outro nome de variável aqui em vez de key
. Por exemplo, "for (let prop in obj)"
também é amplamente utilizado.
Os objetos estão ordenados? Em outras palavras, se fizermos um loop sobre um objeto, obteremos todas as propriedades na mesma ordem em que foram adicionadas? Podemos confiar nisso?
A resposta curta é: “ordenada de maneira especial”: propriedades inteiras são classificadas, outras aparecem em ordem de criação. Os detalhes seguem.
Como exemplo, vamos considerar um objeto com os códigos telefônicos:
deixe códigos = { "49": "Alemanha", "41": "Suíça", "44": "Grã-Bretanha", // .., "1": "EUA" }; for (deixe o código nos códigos) { alerta(código); //1, 41, 44, 49 }
O objeto pode ser usado para sugerir uma lista de opções ao usuário. Se estamos criando um site principalmente para o público alemão, provavelmente queremos que 49
seja o primeiro.
Mas se executarmos o código, veremos uma imagem totalmente diferente:
EUA (1) vai primeiro
depois a Suíça (41) e assim por diante.
Os códigos telefônicos vão em ordem crescente, porque são números inteiros. Então vemos 1, 41, 44, 49
.
Propriedades inteiras? O que é isso?
O termo “propriedade inteira” aqui significa uma string que pode ser convertida de e para um número inteiro sem alteração.
Portanto, "49"
é um nome de propriedade inteiro, porque quando é transformado em um número inteiro e vice-versa, ainda é o mesmo. Mas "+49"
e "1.2"
não são:
//Number(...) converte explicitamente em um número // Math.trunc é uma função integrada que remove a parte decimal alerta(String(Math.trunc(Number("49"))) ); // "49", mesmo, propriedade inteira alerta(String(Math.trunc(Number("+49"))) ); // "49", diferente de "+49" ⇒ não é propriedade inteira alerta(String(Math.trunc(Number("1.2"))) ); // "1", diferente de "1.2" ⇒ não é propriedade inteira
…Por outro lado, se as chaves não forem inteiras, elas serão listadas na ordem de criação, por exemplo:
deixe usuário = { nome: "João", sobrenome: "Smith" }; usuário.idade = 25; //adiciona mais um // propriedades não inteiras são listadas na ordem de criação for (deixe prop no usuário) { alerta(prop); //nome, sobrenome, idade }
Portanto, para corrigir o problema dos códigos telefônicos, podemos “trapacear” tornando os códigos não inteiros. Adicionar um sinal de mais "+"
antes de cada código é suficiente.
Assim:
deixe códigos = { "+49": "Alemanha", "+41": "Suíça", "+44": "Grã-Bretanha", // .., "+1": "EUA" }; for (deixe o código nos códigos) { alerta( +código); // 49, 41, 44, 1 }
Agora funciona como pretendido.
Objetos são arrays associativos com vários recursos especiais.
Eles armazenam propriedades (pares de valores-chave), onde:
As chaves de propriedade devem ser strings ou símbolos (geralmente strings).
Os valores podem ser de qualquer tipo.
Para acessar uma propriedade, podemos usar:
A notação de ponto: obj.property
.
Notação de colchetes obj["property"]
. Colchetes permitem tirar a chave de uma variável, como obj[varWithKey]
.
Operadores adicionais:
Para excluir uma propriedade: delete obj.prop
.
Para verificar se existe uma propriedade com a chave fornecida: "key" in obj
.
Para iterar sobre um objeto: loop for (let key in obj)
.
O que estudamos neste capítulo é chamado de “objeto simples”, ou apenas Object
.
Existem muitos outros tipos de objetos em JavaScript:
Array
para armazenar coleções de dados ordenadas,
Date
para armazenar as informações sobre a data e hora,
Error
para armazenar as informações sobre um erro.
…E assim por diante.
Eles têm características especiais que estudaremos mais tarde. Às vezes as pessoas dizem algo como “Tipo Array” ou “Tipo Data”, mas formalmente eles não são tipos próprios, mas pertencem a um único tipo de dados “objeto”. E eles o estendem de várias maneiras.
Objetos em JavaScript são muito poderosos. Aqui acabamos de arranhar a superfície de um tópico que é realmente enorme. Trabalharemos de perto com objetos e aprenderemos mais sobre eles nas próximas partes do tutorial.
importância: 5
Escreva o código, uma linha para cada ação:
Crie um objeto vazio user
.
Adicione o name
da propriedade com o valor John
.
Adicione o surname
da propriedade com o valor Smith
.
Altere o valor do name
para Pete
.
Remova o name
da propriedade do objeto.
deixe usuário = {}; usuário.nome = "João"; usuário.sobrenome = "Smith"; usuário.nome = "Pete"; excluir nome do usuário;
importância: 5
Escreva a função isEmpty(obj)
que retorna true
se o objeto não tiver propriedades, false
caso contrário.
Deveria funcionar assim:
deixe agendar = {}; alert(isEmpty(agenda)); // verdadeiro agendar["8:30"] = "levanta"; alert(isEmpty(agenda)); // falso
Abra uma sandbox com testes.
Basta fazer um loop sobre o objeto e return false
imediatamente se houver pelo menos uma propriedade.
função estáVazia(obj) { for (deixe digitar obj) { // se o loop foi iniciado, existe uma propriedade retornar falso; } retornar verdadeiro; }
Abra a solução com testes em uma sandbox.
importância: 5
Temos um objeto armazenando os salários da nossa equipe:
deixe salários = { João: 100, Ana: 160, Pete: 130 }
Escreva o código para somar todos os salários e armazene na variável sum
. Deve ser 390
no exemplo acima.
Se salaries
estiverem vazios, o resultado deverá ser 0
.
deixe salários = { João: 100, Ana: 160, Pete: 130 }; seja soma = 0; for (deixe digitar os salários) { soma += salários[chave]; } alerta(soma); //390
importância: 3
Crie uma função multiplyNumeric(obj)
que multiplique todos os valores de propriedades numéricas de obj
por 2
.
Por exemplo:
// antes da chamada deixe menu = { largura: 200, altura: 300, título: "Meu menu" }; multiplicarNumeric(menu); //após a chamada cardápio = { largura: 400, altura: 600, título: "Meu menu" };
Observe multiplyNumeric
não precisa retornar nada. Deve modificar o objeto no local.
PS Use typeof
para verificar um número aqui.
Abra uma sandbox com testes.
função multiplicarNumeric(obj) { for (deixe digitar obj) { if (typeof obj[chave] == 'número') { obj[chave] *= 2; } } }
Abra a solução com testes em uma sandbox.