Podemos facilmente criar esses objetos usando a sintaxe literal do objeto. Se parece com isso:
const product = { name: 'apple', category: 'fruits', price: 1.99} console.log(product);
Objetos em JavaScript são coleções dinâmicas de pares de valores-chave. A chave é sempre uma string e deve ser exclusiva na coleção. O valor pode ser um primitivo, um objeto ou até uma função.
Podemos acessar uma propriedade usando o ponto ou a notação quadrada.
console.log(product.name);//"apple"console.log(product["name"]);//"apple"
Aqui está um exemplo em que o valor é outro objeto.
const product = { name: 'apple', category: 'fruits', price: 1.99, nutrients : { carbs: 0.95, fats: 0.3, protein: 0.2 }}
O valor do carbs
A propriedade é um novo objeto. Aqui está como podemos acessar o carbs
propriedade.
console.log(product.nutrients.carbs);//0.95
Nomes de propriedade abreviados
Considere o caso em que temos os valores de nossas propriedades armazenados em variáveis.
const name="apple";const category = 'fruits';const price = 1.99;const product = { name: name, category: category, price: price}
O JavaScript suporta o que é chamado de nomes abreviados de propriedades. Isso nos permite criar um objeto usando apenas o nome da variável. Ele criará uma propriedade com o mesmo nome. O próximo literal de objeto é equivalente ao anterior.
const name="apple";const category = 'fruits';const price = 1.99;const product = { name, category, price}
Object.create
A seguir, vejamos como implementar objetos com comportamento, objetos orientados a objetos.
O JavaScript possui o que é chamado de sistema de protótipo que permite compartilhar o comportamento entre objetos. A idéia principal é criar um objeto chamado protótipo com um comportamento comum e usá-lo ao criar novos objetos.
O sistema de protótipo nos permite criar objetos que herdam o comportamento de outros objetos.
Vamos criar um objeto protótipo que nos permita adicionar produtos e obter o preço total de um carrinho de compras.
const cartPrototype = { addProduct: function(product){ if(!this.products){ this.products = [product] } else { this.products.push(product); } }, getTotalPrice: function(){ return this.products.reduce((total, p) => total + p.price, 0); }}
Observe que desta vez o valor da propriedade addProduct
é uma função. Também podemos escrever o objeto anterior usando um formulário mais curto chamado sintaxe do método abreviado.
const cartPrototype = { addProduct(product){/*code*/}, getTotalPrice(){/*code*/}}
o cartPrototype
é o objeto de protótipo que mantém o comportamento comum representado por dois métodos, addProduct
e getTotalPrice
. Ele pode ser usado para criar outros objetos que herdam esse comportamento.
const cart = Object.create(cartPrototype);cart.addProduct({name: 'orange', price: 1.25});cart.addProduct({name: 'lemon', price: 1.75});console.log(cart.getTotalPrice());//3
o cart
objeto tem cartPrototype
como seu protótipo. Ele herda o comportamento dele. cart
possui uma propriedade oculta que aponta para o objeto de protótipo.
Quando usamos um método em um objeto, esse método é pesquisado primeiro no próprio objeto, e não no seu protótipo.
isto
Observe que estamos usando uma palavra-chave especial chamada this
para acessar e modificar os dados no objeto.
Lembre-se de que funções são unidades independentes de comportamento em JavaScript. Eles não são necessariamente parte de um objeto. Quando estão, precisamos ter uma referência que permita que a função acesse outros membros no mesmo objeto. this
é o contexto da função. Dá acesso a outras propriedades.
Dados
Você pode se perguntar por que não definimos e inicializamos o products
propriedade no próprio objeto protótipo.
Não devemos fazer isso. Protótipos devem ser usados para compartilhar comportamento, não dados. O compartilhamento de dados levará a ter os mesmos produtos em vários objetos do carrinho. Considere o código abaixo:
const cartPrototype = { products:[], addProduct: function(product){ this.products.push(product); }, getTotalPrice: function(){}}const cart1 = Object.create(cartPrototype);cart1.addProduct({name: 'orange', price: 1.25});cart1.addProduct({name: 'lemon', price: 1.75});console.log(cart1.getTotalPrice());//3const cart2 = Object.create(cartPrototype);console.log(cart2.getTotalPrice());//3
Tanto o cart1
e cart2
objetos que herdam o comportamento comum do cartPrototype
também compartilham os mesmos dados. Nós não queremos isso. Protótipos devem ser usados para compartilhar comportamento, não dados.
Classe
O sistema de protótipo não é uma maneira comum de construir objetos. Os desenvolvedores estão mais familiarizados com a construção de objetos fora das classes.
A sintaxe da classe permite uma maneira mais familiar de criar objetos que compartilham um comportamento comum. Ele ainda cria o mesmo protótipo nos bastidores, mas a sintaxe é mais clara e também evitamos o problema anterior relacionado a dados. A classe oferece um local específico para definir os dados distintos para cada objeto.
Aqui está o mesmo objeto criado usando a sintaxe da classe sugar:
class Cart{ constructor(){ this.products = []; } addProduct(product){ this.products.push(product); } getTotalPrice(){ return this.products.reduce((total, p) => total + p.price, 0); }}const cart = new Cart();cart.addProduct({name: 'orange', price: 1.25});cart.addProduct({name: 'lemon', price: 1.75});console.log(cart.getTotalPrice());//3const cart2 = new Cart();console.log(cart2.getTotalPrice());//0
Observe que a classe possui um método construtor que inicializou esses dados distintos para cada novo objeto. Os dados no construtor não são compartilhados entre instâncias. Para criar uma nova instância, usamos o new
palavra-chave
Eu acho que a sintaxe da classe é mais clara e familiar para a maioria dos desenvolvedores. No entanto, faz uma coisa semelhante, cria um protótipo com todos os métodos e o utiliza para definir novos objetos. O protótipo pode ser acessado com Cart.prototype
.
Acontece que o sistema de protótipo é flexível o suficiente para permitir a sintaxe da classe. Portanto, o sistema de classes pode ser simulado usando o sistema de protótipo.
Propriedades Privadas
A única coisa é que o products
A propriedade no novo objeto é pública por padrão.
console.log(cart.products);//[{name: "orange", price: 1.25}// {name: "lemon", price: 1.75}]
Podemos torná-lo privado usando o hash #
prefixo.
Propriedades privadas são declaradas com #name
sintaxe. #
faz parte do próprio nome da propriedade e deve ser usado para declarar e acessar a propriedade. Aqui está um exemplo de declaração products
como propriedade privada:
class Cart{ #products constructor(){ this.#products = []; } addProduct(product){ this.#products.push(product); } getTotalPrice(){ return this.#products.reduce((total, p) => total + p.price, 0); }}console.log(cart.#products);//Uncaught SyntaxError: Private field '#products' must be declared in an enclosing class
Funções de fábrica
Outra opção é criar objetos como coleções de fechamentos.
Encerramento é a capacidade de uma função acessar variáveis e parâmetros da outra função, mesmo após a execução da função externa. Dê uma olhada no cart
objeto construído com o que é chamado de função de fábrica.
function Cart() { const products = []; function addProduct(product){ products.push(product); } function getTotalPrice(){ return products.reduce((total, p) => total + p.price, 0); } return { addProduct, getTotalPrice }}const cart = Cart();cart.addProduct({name: 'orange', price: 1.25});cart.addProduct({name: 'lemon', price: 1.75});console.log(cart.getTotalPrice());//3
addProduct
e getTotalPrice
são duas funções internas acessando a variável products
de seus pais. Eles têm acesso ao products
evento variável após o pai Cart
foi executado. addProduct
e getTotalPrice
Existem dois fechamentos que compartilham a mesma variável privada.
Cart
é uma função de fábrica.
O novo objeto cart
criado com a função de fábrica tem o products
variável privada. Não pode ser acessado de fora.
console.log(cart.products);//undefined
As funções de fábrica não precisam do new
palavra-chave, mas você pode usá-lo se quiser. Ele retornará o mesmo objeto, independentemente de você usá-lo ou não.
Recapitular
Normalmente, trabalhamos com dois tipos de objetos, estruturas de dados que possuem dados públicos e nenhum comportamento e objetos orientados a objetos que possuem dados privados e comportamento público.
As estruturas de dados podem ser facilmente construídas usando a sintaxe literal do objeto.
O JavaScript oferece duas maneiras inovadoras de criar objetos orientados a objetos. O primeiro é usar um objeto protótipo para compartilhar o comportamento comum. Objetos herdam de outros objetos. As aulas oferecem uma boa sintaxe de açúcar para criar esses objetos.
A outra opção é definir objetos são coleções de fechamentos.
Para saber mais sobre fechamentos e técnicas de programação de funções, confira minha série de livros Programação Funcional com JavaScript e React.
o Programação Funcional em JavaScript livro está saindo.