Skip to content
This page has been auto-translated and may contain errors.View in English

Objetos

docs.scrimba.com

Digamos que você está descrevendo um único usuário: um nome, um email, uma idade, se está conectado. Você poderia espalhá-los em quatro variáveis separadas, mas eles pertencem juntos, e mantê-los separados significa passar quatro coisas toda vez que você quer dizer "o usuário". Um objeto deixa você reunir tudo isso em um único valor, onde cada parte tem um nome que você pode procurar. A maioria dos dados que você vai lidar, um usuário, um produto, um carrinho, chega exatamente assim.

Criando um objeto

Um objeto é uma coleção de valores nomeados, escrita com chaves. Dentro, você lista pares chave: valor, separados por vírgulas. A chave é o nome; o valor é o que ela mantém:

js
const user = {
  name: "Marina",
  age: 27,
};

console.log(user.name); // Marina

Você lê um valor de volta com um ponto e a chave: user.name. Isso é chamado notação de ponto, e é o que você vai usar na maioria das vezes. A chave à esquerda, o valor armazenado à direita.

Há uma segunda forma de ler um valor, usando colchetes com a chave como uma string:

js
console.log(user["name"]); // Marina

Na maioria das vezes a notação de ponto fica mais limpa, então use-a. Os colchetes ganham seu espaço quando a chave não é uma palavra simples (digamos que tem um espaço) ou quando você não sabe a chave antecipadamente e ela está em outra variável.

Um objeto agrupa valores relacionados sob chaves string, escritas como pares chave: valor dentro de chaves. Onde um array mantém uma lista ordenada que você acessa por posição, um objeto mantém valores que você acessa por nome:

js
const user = {
  name: "Marina",
  age: 27,
  isActive: true,
};

console.log(user.name); // Marina
console.log(user["age"]); // 27

Há duas formas de entrar. Notação de ponto (user.name) é o padrão: mais curta e clara. Notação de colchetes (user["age"]) faz o mesmo trabalho mas toma a chave como uma string, o que desbloqueia duas coisas que notação de ponto não pode fazer.

A forma de colchetes é necessária quando a chave não é um identificador válido, por exemplo quando contém um espaço ou começa com um dígito, e quando a chave é dinâmica, sentada em uma variável em vez de digitada literalmente:

js
const field = "name";
console.log(user[field]); // Marina, lê a chave mantida em `field`
console.log(user.field); // undefined, procura por uma chave literal chamada "field"

Essa diferença confunde as pessoas: user[field] usa o valor de field, enquanto user.field procura por uma chave soletrada field. Ler uma chave que não existe retorna undefined em vez de um erro.

Um objeto é a estrutura central do JavaScript para agrupar valores sob chaves. Chaves são strings (ou Symbols, que você pode deixar de lado por enquanto); um valor pode ser qualquer coisa, incluindo outro objeto ou uma função. Você escreve como pares chave: valor em chaves:

js
const user = {
  name: "Marina",
  age: 27,
  isActive: true,
};

console.log(user.name); // Marina
console.log(user["age"]); // 27

Notação de ponto é o padrão idiomático. Notação de colchetes é a mesma procura com a chave calculada como uma expressão, por isso é necessária em exatamente dois casos: quando a chave não é um identificador válido (user["first name"], user["2fa"]), e quando a chave é dinâmica:

js
const field = "name";
console.log(user[field]); // Marina, avalia `field` para "name" primeiro

Dois comportamentos valem a pena manter em mente. Ler uma chave ausente retorna undefined, nunca lança, então um erro de digitação falha silenciosamente em vez de explicitamente. E as chaves são sempre strings nos bastidores: user[27] e user["27"] chegam ao mesmo slot, porque o número é coagido para uma chave string. Essa coerção é uma fonte comum de confusão quando chaves de objeto e índices de array começam a parecer iguais.

js
const product = {
  title: "Luminária de mesa",
  price: 40,
  inStock: true,
};

console.log(`${product.title} costs ${product.price}`); // Luminária de mesa costs 40

Um objeto é um valor como qualquer outro. Você pode armazená-lo em uma const, colocá-lo em um array, passá-lo para uma função e retorná-lo. As chaves dão nomes às suas partes, que é o ponto inteiro: product.price diz o que é, onde um 40 nu sentado em uma variável não.

JunoCriando um objeto Um objeto reúne valores relacionados sob nomes, escritos como pares chave: valor em chaves. Leia um valor com um ponto e a chave, como user.name. Use a forma de colchete quadrado apenas quando a chave tem um caractere estranho ou vive em uma variável.
JunoCriando um objeto Um objeto acessa valores por nome onde um array os acessa por posição. Notação de ponto é seu padrão; notação de colchetes toma a chave como uma string, então use-a para chaves que não são palavras simples ou que vivem em uma variável. Lembre que user[field] lê a chave armazenada em field, enquanto user.field procura por uma chave literal field, e uma chave ausente volta como undefined em vez de errar.
JunoCriando um objeto Chaves são strings nos bastidores, então user[27] e user["27"] acertam o mesmo slot, e uma chave ausente retorna undefined em vez de lançar. Notação de ponto é idiomática; notação de colchetes é a mesma procura com uma chave calculada, necessária para chaves não-identificador e dinâmicas. Esse undefined silencioso em um erro de digitação é conveniente até que esconda um bug.

Alterando um objeto

Objetos não são fixados uma vez que você os faz. Você pode adicionar uma nova chave, alterar uma existente ou remover uma inteiramente. Para adicionar ou atualizar, atribua à chave:

js
const user = {
  name: "Marina",
};

user.age = 30; // adiciona uma nova chave
user.name = "Marina Silva"; // atualiza a existente

console.log(user); // { name: "Marina Silva", age: 30 }

Se a chave já existe, atribuir a ela substitui o valor. Se não, é criada. Para remover uma chave, use delete:

js
delete user.age;
console.log(user); // { name: "Marina Silva" }

Aqui está a parte que surpreende as pessoas. Um objeto const ainda pode ter suas propriedades alteradas. const significa que o nome user não pode apontar para um objeto diferente, mas o objeto para o qual aponta é permitido:

js
const cart = { total: 0 };
cart.total = 25; // ok, alterando o que está dentro
// cart = { total: 25 }; // isso daria erro, reatribuindo o nome

Objetos são mutáveis: você adiciona, atualiza e remove chaves após a criação. Atribuir a uma chave a cria se for nova e a sobrescreve se existir. Remover uma chave é o trabalho de delete:

js
const user = { name: "Marina" };

user.age = 30; // adicionar
user.age = 31; // atualizar
delete user.age; // remover

console.log(user); // { name: "Marina" }

O que pega as pessoas é const. const bloqueia o nome, não o conteúdo. A ligação não pode ser reatribuída a um novo objeto, mas o objeto que ela mantém fica totalmente editável:

js
const cart = { total: 0, items: 0 };
cart.total = 25; // permitido, mutando o objeto
cart.items += 1; // permitido
// cart = {}; // TypeError, reatribuindo uma ligação const

É por isso que const é o padrão sensato mesmo para objetos que você planeja alterar. Você quase nunca quer reapontar a variável, e const o impede de fazer por acidente enquanto deixa o objeto em si aberto.

Objetos são mutáveis. Atribuição a uma chave insere ou substitui (cria ou sobrescreve), e delete remove:

js
const user = { name: "Marina", age: 30 };

user.role = "admin"; // adicionar
delete user.age; // remover

console.log(user); // { name: "Marina", role: "admin" }

A distinção const é a que você deve internalizar, porque é realmente sobre o que const protege. const congela a ligação, nunca o valor. O nome não pode ser reatribuído; o objeto que ele referencia fica aberto:

js
const cart = { total: 0 };
cart.total = 25; // ok, mutando o objeto referenciado
// cart = { total: 25 }; // TypeError: Assignment to constant variable

Se você realmente quer o próprio objeto bloqueado, Object.freeze(obj) impede adicionar, alterar ou remover chaves, embora seja um congelamento superficial (objetos aninhados permanecem mutáveis) e falha silenciosamente em código não-strict em vez de errar. Na prática você raramente precisa disso. O hábito que vale a pena manter é const por padrão: sinaliza que a ligação é estável e bloqueia reatribuição acidental, que é o erro que realmente acontece, enquanto deixa mutação normal disponível.

JunoAlterando um objeto Atribua a uma chave para adicionar ou atualizá-la, e use delete para remover uma. A surpresa é const: impede você apontar user para um objeto diferente, mas você ainda pode alterar o que está dentro do que ele mantém. Demorou um tempo para eu parar de esperar que const bloqueasse tudo.
JunoAlterando um objeto Atribuição adiciona ou sobrescreve uma chave, delete remove uma. const bloqueia o nome, não o conteúdo, então um objeto const fica totalmente editável enquanto a variável em si não pode ser repontada. Esse é exatamente o motivo pelo qual const é um padrão fino mesmo para objetos que você quer alterar.
JunoAlterando um objeto Atribuição insere ou substitui uma chave, delete remove uma, e const protege a ligação em vez do valor, então o objeto referenciado fica mutável. Se você deve bloquear o objeto, Object.freeze o faz superficialmente e falha silenciosamente fora do modo strict. Use-o raramente; const por padrão já impede o erro de reatribuição que realmente aparece.

Métodos e this

Um valor em um objeto pode ser uma função. Quando é, é chamado de método: algo que o objeto pode fazer, em vez de um fato que armazena. Você o chama com o mesmo ponto, mais parênteses:

js
const user = {
  name: "Marina",
  greet() {
    return `Oi, sou ${this.name}`;
  },
};

console.log(user.greet()); // Oi, sou Marina

A nova palavra dentro é this. Quando você chama user.greet(), this significa "o objeto no qual este método foi chamado", que aqui é user. Então this.name lê a chave name do mesmo objeto. É assim que um método acessa os valores do seu próprio objeto sem que você os passe.

Quando uma função é armazenada como um valor em um objeto, é um método. Métodos deixam um objeto carregar comportamento junto com seus dados, e eles acessam aqueles dados através de this:

js
const cart = {
  items: ["luminária", "mesa"],
  total: 65,
  summary() {
    return `${this.items.length} itens, ${this.total} total`;
  },
};

console.log(cart.summary()); // 2 itens, 65 total

A regra para this que cobre quase tudo que você vai encontrar: this é o objeto à esquerda do ponto no momento da chamada. Chame cart.summary() e this é cart, então this.items e this.total leem do mesmo objeto.

A frase "no momento da chamada" importa. this não é fixado quando você escreve o método, é decidido por como o método é chamado. Chame-o como cart.summary() e this é cart; puxe a função para fora e chame-a por conta própria e this não aponta mais para cart. Você vai encontrar essa borda depois, mas para métodos chamados com um ponto, this funciona exatamente como você esperaria.

Uma função armazenada em um objeto é um método, e ela acessa o próprio estado do objeto através de this. A regra de ligação que importa na prática: this é definido por como uma função é chamada, não onde é definida. Para uma chamada de método normal, this é o objeto à esquerda do ponto:

js
const cart = {
  total: 65,
  discount: 0.1,
  finalPrice() {
    return this.total * (1 - this.discount);
  },
};

console.log(cart.finalPrice()); // 58.5

Como a ligação é dinâmica, desanexar o método quebra: const fn = cart.finalPrice; fn(); perde o contexto cart e this não aponta mais onde você quer.

A pegadinha prática é arrow functions como métodos. Uma arrow não recebe seu próprio this; ela captura o this do escopo ao redor onde foi definida. Então uma arrow usada como método lê o this exterior (no nível superior, não o objeto) em vez do objeto no qual ela vive:

js
const user = {
  name: "Marina",
  greet: () => `Oi, sou ${this.name}`, // errado: `this` não é `user`
  greetOk() {
    return `Oi, sou ${this.name}`; // certo: atalho de método
  },
};

A regra para levar: use atalho de método (greet() {}) quando a função precisa de this para significar o objeto, e reserve arrows para callbacks onde você realmente quer o this ao redor, que é a maioria dos outros lugares.

JunoMétodos e this Uma função armazenada em um objeto é um método, chamado com um ponto e parênteses como user.greet(). Dentro dele, this significa o objeto no qual o método foi chamado, então this.name lê o próprio name daquele objeto. É assim que um método chega aos valores do seu próprio objeto.
JunoMétodos e this Um método é uma função vivendo em um objeto, e this é o objeto à esquerda do ponto quando você o chama. Então cart.summary() faz this significar cart. A pegadinha é que this é decidido no momento da chamada, não quando você escreve o método, o que importa assim que você puxa um método de seu objeto.
JunoMétodos e thisthis é ligado por como uma função é chamada, não onde vive, então desanexar um método perde seu objeto. Use atalho de método quando você precisa de this para significar o objeto, e mantenha arrow functions para callbacks onde você quer o this ao redor, já que uma arrow captura o exterior em vez disso. Essa mistura de arrow-como-método é clássica, e falha silenciosamente.

Objetos e arrays juntos

A maioria dos dados reais é uma lista de objetos: muitos usuários, muitos produtos, cada objeto um registro com as mesmas chaves. Você coloca objetos dentro de um array:

js
const products = [
  { title: "Luminária de mesa", price: 40 },
  { title: "Caderno", price: 6 },
  { title: "Caneta", price: 3 },
];

console.log(products[0].title); // Luminária de mesa

Você acessa um objeto por sua posição (products[0]), então um de seus campos com um ponto (.title). Encadeados juntos, products[0].title lê o título do primeiro produto.

Para ler um campo de cada objeto, faça um loop sobre o array:

js
for (const product of products) {
  console.log(product.title);
}
// Luminária de mesa
// Caderno
// Caneta

Cada volta do loop, product é um objeto da lista, e product.title lê seu título. Essa forma, um array de objetos que você faz loop sobre, é a maioria do trabalho de dados que você fará.

Um array de objetos é a forma da maioria dos dados reais: cada objeto um registro, cada um compartilhando as mesmas chaves. Combine acesso de array com acesso de ponto para alcançar qualquer campo único:

js
const products = [
  { title: "Luminária de mesa", price: 40 },
  { title: "Caderno", price: 6 },
  { title: "Caneta", price: 3 },
];

console.log(products[1].price); // 6

Como é um array, métodos de array funcionam nele, e é aqui que eles ganham seu valor. Puxe um campo de cada objeto com map, mantenha registros correspondentes com filter, role-os em um único valor com reduce:

js
const titles = products.map((product) => product.title);
console.log(titles); // ["Luminária de mesa", "Caderno", "Caneta"]

const total = products.reduce((sum, product) => sum + product.price, 0);
console.log(total); // 49

O callback recebe um objeto por iteração, e você lê seus campos por chave. Uma vez que isso clica, "para cada registro, pegue este campo" e "some este campo em todos os registros" se tornam uma linha.

Um array de objetos é a forma canônica de dados estruturados, e métodos de array é como você o trabalha. Cada callback recebe um registro; você lê seus campos por chave e transforma, filtra ou agrega:

js
const orders = [
  { id: 1, total: 40, paid: true },
  { id: 2, total: 6, paid: false },
  { id: 3, total: 3, paid: true },
];

const paidTotal = orders
  .filter((order) => order.paid)
  .reduce((sum, order) => sum + order.total, 0);

console.log(paidTotal); // 43

Encadear lê de cima para baixo como um pipeline: mantenha os pedidos pagos, então some seus totais. Isso se compõe claramente porque cada passo toma um array de objetos e retorna outro array (ou, para reduce, um único valor).

Uma coisa a manter em mente enquanto os dados crescem: esses objetos são mantidos por referência, o que a seção avançada abaixo torna preciso. Mutar um objeto dentro de um callback map ou forEach altera o registro original no array, não uma cópia, o que é ocasionalmente o que você quer e frequentemente uma surpresa. Quando você quer transformar sem tocar na fonte, construa objetos novos em vez de editar aqueles que foram entregues a você.

JunoObjetos e arrays juntos A maioria dos dados reais é um array de objetos: uma lista onde cada item é um registro com as mesmas chaves. Acesse um com products[0], então um campo com products[0].title. Faça um loop sobre o array para tocar cada registro, e você tem o padrão por trás da maioria do trabalho de dados que fará.
JunoObjetos e arrays juntos Um array de objetos é a forma que a maioria dos dados reais chega, e métodos de array são construídos para ele. map puxa um campo de cada registro, filter mantém os correspondentes, reduce soma através deles, com o callback entregue um objeto por volta. "Para cada registro, pegue este campo" para de ser um loop e se torna uma linha.
JunoObjetos e arrays juntos Métodos de array se encadeiam em um pipeline sobre registros: filtre para baixo, então reduza para um valor, cada passo lendo campos por chave. Observe que esses objetos são mantidos por referência, então mutar um dentro de um callback map ou forEach edita o original em seu lugar. Quando você quer transformar sem tocar na fonte, construa objetos novos em vez de editar aqueles que foi dado.

Aonde objetos vão em seguida

Objetos e arrays são as duas estruturas que você vai usar constantemente, e a maioria dos dados que você lida é algum aninhamento dos dois: objetos com campos de array, arrays de objetos, e combinações de ambos. Combine-os com métodos de array e você pode moldar quase qualquer dado na forma que você precisa.

O próximo passo é colocar objetos para funcionar em uma página. Quando você chega ao DOM, os elementos que você lê e altera são objetos também, com chaves que você define e métodos que você chama, então tudo aqui continua direto.