O material, cuja tradução publicamos hoje, é dedicado ao estudo de objetos - uma das principais essências do JavaScript. Ele foi desenvolvido principalmente para desenvolvedores iniciantes que desejam otimizar seus conhecimentos sobre objetos.

Objetos em JavaScript são coleções dinâmicas de propriedades que, além disso, contêm uma propriedade "oculta" que é um protótipo do objeto. Propriedades de objetos são caracterizadas por chaves e valores. Vamos começar a conversa sobre objetos JS com chaves.
Chaves de propriedade do objeto
A chave de propriedade do objeto é uma sequência exclusiva. Você pode usar dois métodos para acessar propriedades: acessando-as por um período e especificando a chave do objeto entre colchetes. Ao acessar propriedades por meio de um ponto, a chave deve ser um identificador JavaScript válido. Considere um exemplo:
let obj = { message : "A message" } obj.message //"A message" obj["message"] //"A message"
Ao tentar acessar uma propriedade inexistente de um objeto, uma mensagem de erro não aparecerá, mas o valor
undefined
será retornado:
obj.otherProperty
Ao usar colchetes para acessar propriedades, você pode usar chaves que não são identificadores JavaScript válidos (por exemplo, a chave pode ser uma sequência que contém espaços). Eles podem ter qualquer valor que possa ser convertido em uma sequência:
let french = {}; french["merci beaucoup"] = "thank you very much"; french["merci beaucoup"];
Se valores não-string forem usados como chaves, eles serão automaticamente convertidos em strings (usando, se possível, o
toString()
):
et obj = {}; //Number obj[1] = "Number 1"; obj[1] === obj["1"]; //true //Object let number1 = { toString : function() { return "1"; } } obj[number1] === obj["1"]; //true
Neste exemplo, o objeto
number1
é usado como chave. Ao tentar acessar uma propriedade, ela é convertida na linha
1
e o resultado dessa conversão é usado como chave.
Valores da propriedade do objeto
As propriedades do objeto podem ser valores, objetos ou funções primitivos.
Objeto como um valor de propriedade do objeto
Objetos podem ser colocados em outros objetos. Considere
um exemplo :
let book = { title : "The Good Parts", author : { firstName : "Douglas", lastName : "Crockford" } } book.author.firstName;
Uma abordagem semelhante pode ser usada para criar namespaces:
let app = {}; app.authorService = { getAuthors : function() {} }; app.bookService = { getBooks : function() {} };
▍ Funcionar como um valor de propriedade do objeto
Quando uma função é usada como um valor de propriedade do objeto, ela geralmente se torna um método de objeto. Dentro do método, para acessar o objeto atual, use a
this
.
Essa palavra-chave, no entanto, pode ter significados diferentes, dependendo de como a função foi chamada.
Aqui você pode ler sobre situações em que
this
perde contexto.
A natureza dinâmica dos objetos
Objetos em JavaScript, por natureza, são entidades dinâmicas. Você pode adicionar propriedades a qualquer momento, o mesmo vale para a exclusão de propriedades:
let obj = {}; obj.message = "This is a message"; // obj.otherMessage = "A new message"; // delete obj.otherMessage; //
Objetos como matrizes associativas
Os objetos podem ser considerados como matrizes associativas. Chaves de matriz associativas são os nomes de propriedade do objeto. Para acessar a chave, não é necessário examinar todas as propriedades, ou seja, a operação de acesso à chave de uma matriz associativa baseada em um objeto é realizada em O (1).
Protótipos de Objetos
Os objetos têm um link "oculto",
__proto__
, apontando para um objeto protótipo do qual o objeto herda propriedades.
Por exemplo, um objeto criado usando um literal de objeto tem um link para
Object.prototype
:
var obj = {}; obj.__proto__ === Object.prototype;
▍ Objetos vazios
Como acabamos de ver, o objeto "vazio",
{}
, na verdade não é tão vazio, pois contém uma referência ao
Object.prototype
. Para criar um objeto verdadeiramente vazio, você precisa usar a seguinte construção:
Object.create(null)
Graças a isso, um objeto sem protótipo será criado. Esses objetos são geralmente usados para criar matrizes associativas.
Chain Cadeia de protótipo
Objetos protótipos podem ter seus próprios protótipos. Se você tentar acessar uma propriedade de um objeto que não está nele, o JavaScript tentará encontrar essa propriedade no protótipo desse objeto e, se a propriedade desejada não estiver lá, será feita uma tentativa de encontrá-la no protótipo do protótipo. Isso continuará até que a propriedade desejada seja encontrada ou até o fim da cadeia de protótipos.
Valores de tipo primitivo e wrappers de objeto
O JavaScript permite que você trabalhe com os valores de tipos primitivos como objetos, no sentido em que a linguagem permite acessar suas propriedades e métodos.
(1.23).toFixed(1); //"1.2" "text".toUpperCase(); //"TEXT" true.toString(); //"true"
Além disso, é claro, os valores dos tipos primitivos não são objetos.
Para organizar o acesso às “propriedades” dos valores dos tipos primitivos, o JavaScript, se necessário, cria objetos de wrapper que, depois de se tornarem desnecessários, são destruídos. O processo de criação e destruição de objetos wrapper é otimizado pelo mecanismo JS.
Os wrappers de objetos têm valores de tipos numérico, de sequência e lógico. Objetos dos tipos correspondentes são representados pelas funções de construtor
Number
,
String
e
Boolean
.
Protótipos incorporados
Os objetos Number herdam propriedades e métodos do protótipo
Number.prototype
, que é o descendente de
Object.prototype
:
var no = 1; no.__proto__ === Number.prototype; //true no.__proto__.__proto__ === Object.prototype; //true
O protótipo de objetos string é
String.prototype
. O protótipo de objetos booleanos é
Boolean.prototype
. O protótipo de matrizes (que também são objetos) é
Array.prototype
.
Funções em JavaScript também são objetos que possuem um protótipo
Function.prototype
. Funções têm métodos como
bind()
,
apply()
e
call()
.
Todos os objetos, funções e objetos que representam valores de tipo primitivo (exceto valores
null
e
undefined
) herdam propriedades e métodos do
Object.prototype
. Isso leva ao fato de que, por exemplo, todos eles têm um
toString()
.
Estendendo objetos incorporados com polyfills
O JavaScript facilita a extensão de objetos incorporados com novos recursos usando os chamados polyfills. Um polyfill é um pedaço de código que implementa recursos que não são suportados por nenhum navegador.
▍Uso de polyfills
Por exemplo, há um
polyfill para o método
Object.assign()
. Permite adicionar uma nova função ao
Object
se ela não estiver disponível.
O mesmo se aplica ao
polyfill Array.from()
, que, se o método
from()
não estiver no objeto
Array
, o equipará com esse método.
F Polifill e protótipos
Com a ajuda de polyfills, novos métodos podem ser adicionados aos protótipos de objetos. Por exemplo, o
polyfill para
String.prototype.trim()
permite equipar todos os objetos de string com o método
trim()
:
let text = " A text "; text.trim(); //"A text"
O polyfill para
Array.prototype.find()
permite equipar todas as matrizes com o método
find()
. O
polyfill para
Array.prototype.findIndex()
funciona de maneira semelhante:
let arr = ["A", "B", "C", "D", "E"]; arr.indexOf("C");
Herança única
O comando
Object.create()
permite criar novos objetos com um determinado objeto de protótipo. Este comando é usado no JavaScript para implementar um único mecanismo de herança. Considere
um exemplo :
let bookPrototype = { getFullTitle : function(){ return this.title + " by " + this.author; } } let book = Object.create(bookPrototype); book.title = "JavaScript: The Good Parts"; book.author = "Douglas Crockford"; book.getFullTitle();
Herança múltipla
O comando
Object.assign()
copia propriedades de um ou mais objetos para o objeto de destino. Pode ser usado para implementar vários esquemas de herança. Aqui está
um exemplo :
let authorDataService = { getAuthors : function() {} }; let bookDataService = { getBooks : function() {} }; let userDataService = { getUsers : function() {} }; let dataService = Object.assign({}, authorDataService, bookDataService, userDataService ); dataService.getAuthors(); dataService.getBooks(); dataService.getUsers();
Objetos imutáveis
O comando
Object.freeze()
permite congelar um objeto. Você não pode adicionar novas propriedades a esse objeto. As propriedades não podem ser excluídas, nem seus valores podem ser alterados. Ao usar este comando, um objeto se torna imutável ou imutável:
"use strict"; let book = Object.freeze({ title : "Functional-Light JavaScript", author : "Kyle Simpson" }); book.title = "Other title";//: Cannot assign to read only property 'title'
O comando
Object.freeze()
executa o chamado "congelamento superficial" dos objetos. Isso significa que objetos aninhados em um objeto "congelado" podem ser modificados. Para realizar um "congelamento profundo" de um objeto, é necessário "congelar" recursivamente todas as suas propriedades.
Clonando Objetos
Para criar clones (cópias) de objetos, você pode usar o comando
Object.assign()
:
let book = Object.freeze({ title : "JavaScript Allongé", author : "Reginald Braithwaite" }); let clone = Object.assign({}, book);
Este comando executa cópia superficial de objetos, ou seja, copia apenas propriedades de nível superior. Objetos aninhados acabam sendo comuns para objetos originais e suas cópias.
Literal do objeto
Literais de objeto fornecem aos desenvolvedores uma maneira simples e direta de criar objetos:
let timer = { fn : null, start : function(callback) { this.fn = callback; }, stop : function() {}, }
No entanto, esse método de criação de objetos tem desvantagens. Em particular, com essa abordagem, todas as propriedades do objeto estão disponíveis ao público, os métodos do objeto podem ser redefinidos, eles não podem ser usados para criar novas instâncias dos mesmos objetos:
timer.fn;//null timer.start = function() { console.log("New implementation"); }
Método Object.create ()
Os dois problemas mencionados acima podem ser resolvidos através do uso conjunto dos métodos
Object.create()
e
Object.freeze()
.
Aplicamos essa técnica ao nosso exemplo anterior. Primeiro, crie um protótipo congelado
timerPrototype
que contenha todos os métodos necessários para várias instâncias do objeto. Depois disso, crie um objeto sucessor do
timerPrototype
:
let timerPrototype = Object.freeze({ start : function() {}, stop : function() {} }); let timer = Object.create(timerPrototype); timer.__proto__ === timerPrototype; //true
Se o protótipo estiver protegido contra alterações, o objeto que é seu herdeiro não poderá alterar as propriedades definidas no protótipo. Agora, os métodos
start()
e
stop()
não podem ser substituídos:
"use strict"; timer.start = function() { console.log("New implementation"); }
A
Object.create(timerPrototype)
pode ser usada para criar vários objetos com o mesmo protótipo.
Função construtora
O JavaScript possui as chamadas funções construtoras, que são "açúcar sintático" para executar as etapas acima para criar novos objetos. Considere
um exemplo :
function Timer(callback){ this.fn = callback; } Timer.prototype = { start : function() {}, stop : function() {} } function getTodos() {} let timer = new Timer(getTodos);
Você pode usar qualquer função como construtor. O construtor é chamado usando a
new
palavra-chave. Um objeto criado usando uma função de construtor chamada
FunctionConstructor
receberá um protótipo
FunctionConstructor.prototype
:
let timer = new Timer(); timer.__proto__ === Timer.prototype;
Aqui, para impedir uma alteração no protótipo, novamente, você pode congelar o protótipo:
Timer.prototype = Object.freeze({ start : function() {}, stop : function() {} });
▍ Palavra-chave novo
Quando um comando do formulário
new Timer()
é executado, as mesmas ações são executadas como a função
newTimer()
executa abaixo:
function newTimer(){ let newObj = Object.create(Timer.prototype); let returnObj = Timer.call(newObj, arguments); if(returnObj) return returnObj; return newObj; }
Um novo objeto é criado aqui, cujo protótipo é
Timer.prototype
. Em seguida, a função
Timer
é chamada, configurando os campos para o novo objeto.
Palavra-chave da classe
O ECMAScript 2015 introduziu uma nova maneira de executar as ações acima, que é outro lote de "açúcar sintático". Estamos falando da palavra-chave da
class
e das construções relacionadas a ela associadas. Considere
um exemplo :
class Timer{ constructor(callback){ this.fn = callback; } start() {} stop() {} } Object.freeze(Timer.prototype);
Um objeto criado usando a palavra-chave da
class
base em uma classe chamada
ClassName
terá o protótipo
ClassName.prototype
. Ao criar um objeto com base em uma classe, use a
new
palavra-chave:
let timer= new Timer(); timer.__proto__ === Timer.prototype;
O uso de classes não torna os protótipos imutáveis. Se necessário, eles terão que ser "congelados" da mesma maneira que já fizemos:
Object.freeze(Timer.prototype);
Herança baseada em protótipo
No JavaScript, os objetos herdam propriedades e métodos de outros objetos. As funções e classes do construtor são "açúcar sintático" para criar objetos protótipos que contêm todos os métodos necessários. Utilizando-os, são criados novos objetos que são herdeiros do protótipo, cujas propriedades são específicas para uma instância específica, são definidas usando a função construtora ou os mecanismos de classe.
Seria bom se as funções e classes do construtor pudessem automaticamente tornar os protótipos imutáveis.
Os pontos fortes da herança do protótipo são a economia de memória. O fato é que um protótipo é criado apenas uma vez, após o qual todos os objetos criados em sua base o utilizam.
Problem O problema da falta de mecanismos de encapsulamento embutidos
O modelo de herança do protótipo não usa a separação das propriedades dos objetos em privada e pública. Todas as propriedades dos objetos estão disponíveis ao público.
Por exemplo, o comando
Object.keys()
retorna uma matriz que contém todas as chaves de propriedade do objeto. Ele pode ser usado para iterar sobre todas as propriedades de um objeto:
function logProperty(name){ console.log(name); // console.log(obj[name]); // } Object.keys(obj).forEach(logProperty);
Há um padrão que imita propriedades privadas, contando com o fato de que os desenvolvedores não acessarão aquelas propriedades cujos nomes começam com um sublinhado (
_
):
class Timer{ constructor(callback){ this._fn = callback; this._timerId = 0; } }
Recursos de fábrica
Objetos encapsulados em JavaScript podem ser criados usando funções de fábrica. É assim:
function TodoStore(callback){ let fn = callback; function start() {}, function stop() {} return Object.freeze({ start, stop }); }
Aqui a variável
fn
é privada. Somente os métodos
start()
e
stop()
estão disponíveis publicamente. Esses métodos não podem ser modificados externamente. A palavra-chave this não é usada aqui, portanto, ao usar esse método de criação de objetos, o problema de perder
this
contexto é irrelevante.
O comando
return
usa um literal de objeto contendo apenas funções. Além disso, essas funções são declaradas em encerramento e compartilham um estado comum. Para congelar uma API pública de um objeto, o comando
Object.freeze()
já conhecido é
Object.freeze()
.
Aqui, nos exemplos, usamos o objeto
Timer
.
Neste material, você pode encontrar sua implementação completa.
Sumário
Em JavaScript, os valores de tipos primitivos, objetos comuns e funções são tratados como objetos. Os objetos têm uma natureza dinâmica, podem ser usados como matrizes associativas. Objetos são herdeiros de outros objetos. As funções e classes do construtor são "açúcar sintático", pois permitem criar objetos com base em protótipos. Você pode usar o método
Object.create()
para organizar herança única e
Object.create()
para organizar herança múltipla. Você pode usar as funções de fábrica para criar objetos encapsulados.
Caros leitores! Se você veio ao JavaScript de outros idiomas, conte-nos sobre o que você gosta ou não nos objetos JS, em comparação com a implementação de objetos nos idiomas que você já conhece.
