O autor do material, cuja tradução publicamos hoje, diz que os objetos JavaScript contêm muitas coisas, cuja existência você nem pode suspeitar, usando-as no trabalho diário. Os objetos em JavaScript são muito fáceis de criar, convenientes para trabalhar, parecem compreensíveis e flexíveis e muitos programadores simplesmente não pensam no fato de que os objetos não são tão simples assim.

Nota: as informações da publicação na prática devem ser aplicadas com muito cuidado e sob a supervisão de colegas mais experientes.
Aqui, falamos sobre o que está oculto nas profundezas dos objetos e discutimos os meandros de trabalhar com eles.
Depois de dominar este material, você saberá as respostas para as seguintes perguntas:
- Como tornar uma propriedade de um objeto inviável?
- O que são propriedades com métodos de acesso e quais são seus recursos?
- Como tornar uma propriedade imutável ou oculta?
- Por que algumas propriedades não são visíveis nos loops de entrada ou nos resultados do método Object.keys()e outras são visíveis?
- Como "proteger" um objeto de modificação?
- Como entender um pedaço de código semelhante ao seguinte:
 obj.id = 5; console.log(obj.id)  
Tipos de propriedades do objeto
▍ Propriedades de armazenamento de dados
Você provavelmente criou inúmeros objetos semelhantes a este:
 const obj = { name: 'Arfat', id: 5 } obj.name  
As propriedades de 
name e 
id do objeto 
obj são chamadas de propriedades de 
obj dados ou "Propriedades de Dados". Essas são propriedades familiares que são constantemente encontradas no código JavaScript. Quais outros tipos de propriedades os objetos podem ter?
With Propriedades com métodos de acesso
Essas propriedades também são conhecidas como getters e setters; também são encontradas em outras linguagens de programação como C # ou Python. Uma propriedade com Accessor Property é uma combinação de duas funções - 
get e 
set .
Ao declarar essas propriedades, em vez de usar a construção de tipo de 
: tradicional, a seguinte sintaxe é usada:
 const accessorObj = { get name() {   return 'Arfat'; } }; accessorObj.name;  
Dê uma olhada no objeto 
accesorObj e compare-o com o objeto 
dataObj . Aparentemente, agora eles mostram o mesmo comportamento. Ao descrever o primeiro objeto, usamos a palavra-chave 
get , seguida pela declaração da função. Para acessar uma propriedade semelhante, embora seja representada por uma função, não é necessário colocar parênteses após o nome da propriedade para chamar essa função. Ou seja, um design como 
accessorObj.name(); incorreto.
Ao tentar acessar a propriedade 
accessorObj.name , ou seja, ao tentar lê-la, a função correspondente é executada e o valor retornado a ela se torna o valor da propriedade 
name .
As funções 
get são chamadas getters, são responsáveis por obter os valores. Se continuarmos nosso exemplo e tentarmos alterar o valor da propriedade 
name do objeto 
accessorObj , digamos, executando o comando 
accessorObj.name = 'New Person'; , acontece que nada vai acontecer. O ponto aqui é que a função setter não está associada à tecla de 
name . Essas funções permitem personalizar a ordem de atribuição de novos valores às propriedades dos objetos, cujo acesso é organizado usando getters.
Aqui está a aparência de uma declaração de objeto com um getter e um setter:
 const accessorObj = { _name: 'Arfat', get name() {   return this._name; }, set name(value) {   this._name = value; } }; 
A função setter recebe o que eles estão tentando atribuir à propriedade do objeto como um parâmetro. Agora você pode salvar algo na propriedade do objeto. Nesse caso, criamos uma propriedade "privada" do objeto 
_name . O primeiro caractere do nome dessa propriedade é um sublinhado, que nada mais é do que uma dica para o programador, indicando que essa propriedade é destinada às necessidades internas do objeto. Além disso, trabalhamos com ele ao acessar a propriedade do objeto 
name , cujo acesso é regulado pelo getter e setter.
Ao mesmo tempo, na função getter, antes de retornar o valor da propriedade 
_name , podemos modificá-lo.
Aqui está o que pode parecer:
 const obj = { get name() {   return this._name.toUpperCase(); }, set name(value) {   this._name = value; }, get id() {   return this._id.toString(2);  
A propósito, este programa contém a resposta para uma das perguntas apresentadas no início do artigo, que diz respeito à análise do código incompreensível à primeira vista.
Por que alguém precisaria de propriedades com métodos de acesso se você pode trabalhar com segurança com propriedades comuns? Por exemplo, eles podem ser necessários para registrar informações sobre leituras de propriedades ou para armazenar um histórico de alterações nos valores das propriedades. As propriedades com métodos de acesso oferecem todas as possibilidades de processamento de dados usando funções e a característica de simplicidade de trabalhar com propriedades comuns. Leia mais sobre como usar essas propriedades 
aqui .
Como o JavaScript distingue entre propriedades comuns que armazenam dados de propriedades com métodos de acesso? Descubra isso.
Descritores de Propriedade de Objeto
À primeira vista, pode parecer que existe uma correspondência direta entre as chaves e os valores armazenados nos objetos. No entanto, isso não é totalmente verdade.
▍ Atributos de propriedade
Cada chave do objeto está associada a um conjunto de atributos que determinam as características do valor associado a essa chave. Esses atributos também podem ser considerados como metadados que descrevem um par 
: .
Os atributos são usados para definir e descrever o estado das propriedades dos objetos. O conjunto de atributos de propriedade é chamado de descritor. Existem seis atributos de propriedade:
- [[Value]]
- [[Get]]
- [[Set]]
- [[Writable]]
- [[Enumerable]]
- [[Configurable]]
Por que os nomes de atributos de propriedades nesta lista estão incluídos na construção 
[[]] ? Parênteses duplos indicam que essas são entidades usadas pelos mecanismos internos do idioma. Um programador JS não pode acessar essas propriedades diretamente. Para influenciá-los, métodos apropriados são usados.
Considere a seguinte imagem, tirada 
daqui , na qual você pode ver o objeto e os atributos de suas propriedades.
Objeto e atributos de suas propriedadesNosso objeto tem 2 chaves - 
x e 
y . Além disso, um conjunto de atributos está associado a cada um deles.
Como, usando JavaScript, obter informações sobre o objeto, semelhantes às mostradas na figura anterior? Você pode usar a função 
Object.getOwnPropertyDescriptor() para isso. Ele pega um objeto e o nome de sua propriedade e, em seguida, retorna um objeto que contém os atributos dessa propriedade. Aqui está um exemplo:
 const object = { x: 5, y: 6 }; Object.getOwnPropertyDescriptor(object, 'x');  
Note-se que a composição dos atributos de uma propriedade específica depende do seu tipo. Todos os seis atributos da mesma propriedade não foram encontrados.
- Se estamos falando de propriedades com dados, elas terão apenas os atributos [[Value]],[[Writable]],[[Enumerable]]e[[Configurable]].
- As propriedades com métodos de acesso, em vez dos atributos [[Value]]e[[Writable]], possuem os atributos[[Get]]e[[Set]].
▍ [[valor]]
Este atributo armazena o que é retornado ao tentar obter o valor da propriedade de um objeto. Ou seja, se no exemplo anterior usamos uma construção do formulário 
object.x , obtemos o que é armazenado no atributo 
[[Value]] . O mesmo acontece quando você tenta ler as propriedades de um objeto usando colchetes.
▍ [[Get]]
Este atributo armazena uma referência a uma função, que é uma propriedade getter. Essa função é chamada sem argumentos ao tentar ler o valor de uma propriedade.
▍ [[Definir]]
É aqui que o link para a função declarada ao criar a propriedade setter é armazenado. É chamado com um argumento que representa o valor que eles tentaram atribuir à propriedade, ou seja, é chamado durante cada operação de atribuição de um novo valor a uma propriedade.
 const obj = { set x(val) {   console.log(val)    
Neste exemplo, o lado direito da expressão é passado como um argumento 
val para a função setter. 
Aqui está o código que demonstra o uso de setters e getters.
[[[Gravável]]
Este atributo possui um valor booleano. Indica se o valor da propriedade pode ser substituído ou não. Se 
false for armazenado aqui, as tentativas de alterar o valor da propriedade falharão.
▍ [[Enumerável]]
Um valor lógico também é armazenado aqui. Este atributo controla a emissão da propriedade 
for-in loops de entrada. Se estiver definido como 
true , será possível trabalhar com a propriedade usando esses ciclos.
▍ [[Configurável]]
Este atributo também é representado por um valor booleano. É o que acontece se o 
false for armazenado nele:
- A propriedade não pode ser excluída.
- Você não pode converter propriedades que armazenam dados em propriedades com métodos de acesso e vice-versa. Tentativas de realizar essas transformações não levarão a nada.
- Não será permitido alterar os valores dos atributos de propriedade. Ou seja, os valores atuais dos atributos [[Enumerable]],[[Configurable]],[[Get]]e[[Set]]permanecerão inalterados.
O efeito de definir esse atributo como 
false também depende do tipo de propriedade. Este atributo, além dos efeitos acima nas propriedades, atua sobre elas e assim:
- Se essa é uma propriedade que armazena dados, o atributo [[Writable]]só pode ser alterado detrueparafalse.
- Até que o atributo [[Writable]]seja definido comofalse, o atributo[[Value]]pode ser alterado. Porém, depois que os atributos[[Writable]]e[[Configurable]]forem definidos comofalse, a propriedade se tornará gravável, não passível de exclusão e imutável.
Trabalhar com descritores
Agora que nos familiarizamos com os atributos, nos perguntamos como podemos influenciá-los. O JavaScript possui funções especiais para trabalhar com descritores de propriedades. Vamos conversar sobre eles.
Método Método Object.getOwnPropertyDescriptor ()
Já conhecemos esse método. Ele, levando um objeto e o nome de sua propriedade, retorna 
undefined ou um objeto com um descritor de propriedade.
▍ Método Object.defineProperty ()
Este é um método estático de 
Object que permite adicionar propriedades a objetos ou alterar propriedades existentes. São necessários três argumentos - um objeto, um nome de propriedade e um objeto com um descritor. Este método retorna um objeto modificado. Considere um exemplo:
 const obj = {};  
Pode ser executado no Node.js. O código acabou sendo bastante grande, mas, de fato, é bastante simples. Vamos analisá-lo, focando nos comentários do formulário 
// #n .
No fragmento 
#1 usamos a função 
defineProperty , passando o objeto 
obj , o nome da propriedade 
id e um objeto descritor que contém apenas a propriedade 
value , indicando que 
42 será gravado no atributo 
[[Value]] . Lembre-se de que se você não passar valores para atributos como 
[[Enumerable]] ou 
[[Configurable]] neste objeto, eles serão configurados como 
false por padrão. Nesse caso, os atributos 
[[Writable]] , 
[[Enumerable]] e 
[[Configurable]] propriedade 
id são definidos como 
false .
No local marcado como 
#2 , estamos tentando exibir uma representação de string do objeto no console. Como sua propriedade 
id não é enumerável, ela não será exibida. Além disso, a propriedade existe, o que comprova sua conclusão bem-sucedida pelo comando 
#3 .
Criando um objeto (fragmento 
#4 ), definimos uma lista completa de atributos. Em particular, defina 
[[Writable]] como 
false .
Com os comandos 
#5 e 
#7 exibimos o valor da propriedade 
name . Mas entre eles (fragmento 
#6 ) tentamos mudar esse valor. Esta operação não alterou o valor da propriedade porque seu atributo 
[[Writable]] está definido como 
false . Como resultado, os dois comandos emitem a mesma coisa para o console.
O comando 
#8 é uma tentativa de remover a propriedade 
id . Lembre-se de que seu atributo 
[[Configurable]] está definido como 
false , o que significa que não pode ser excluído. Isso é comprovado pela equipe 
#9 .
O fragmento 
#10 mostra o uso da função 
Object.defineProperties () . Funciona da mesma maneira que a função 
defineProperty() , mas permite, em uma chamada, afetar várias propriedades do objeto, enquanto 
defineProperty() trabalha com apenas uma propriedade do objeto.
Proteção de Objetos
De tempos em tempos, o desenvolvedor precisa proteger objetos contra interferências externas. Por exemplo, dada a flexibilidade do JavaScript, é muito fácil alterar por engano as propriedades de um objeto que não deve ser alterado. Existem três maneiras principais de proteger objetos.
▍ Método Object.preventExtensions ()
O método 
Object.preventExtensions() impede que o objeto se expanda, ou seja, adicione novas propriedades a ele. Ele pega um objeto e o torna não expansível. Observe que você pode remover propriedades desse objeto. Considere um exemplo:
 const obj = { id: 42 }; Object.preventExtensions(obj); obj.name = 'Arfat'; console.log(obj);  
Para descobrir se um objeto não é expansível, você pode usar o método 
Object.isExtensible() . Se retornar 
true , você poderá adicionar novas propriedades ao objeto.
▍ Método Object.seal ()
O método 
seal() parece "selar" objetos. Aqui está o que estamos falando:
- Seu uso impede que novas propriedades sejam adicionadas ao objeto (nisso é semelhante a Object.preventExtensions()).
- Torna todas as propriedades existentes de um objeto não configuráveis.
- Os valores das propriedades existentes, se o atributo [[Writable]]não estiver definido comofalse, poderão ser alterados.
Como resultado, verifica-se que esse método impede a adição de novas propriedades ao objeto e a remoção das propriedades existentes nele.
Considere um exemplo:
 const obj = { id: 42 }; Object.seal(obj); delete obj.id  
Para verificar se o objeto está "selado" ou não, você pode usar o método 
Object.isSealed() .
▍ Método Object.freeze ()
O método 
freeze() permite "congelar" objetos, equipando-os com o nível mais alto possível de proteção em JavaScript. Veja como funciona:
- Sela um objeto usando Object.seal().
- Proíbe completamente a modificação de quaisquer propriedades existentes do objeto.
- Proíbe a modificação de descritores de propriedades.
Aqui está um exemplo:
 const obj = { id: 42 }; Object.freeze(obj); delete obj.id  
Você pode verificar se o objeto está "congelado" usando o método 
Object.isFrozen() .
▍ Visão geral dos métodos usados para proteger objetos
É importante observar que os métodos acima usados para proteger objetos afetam apenas suas propriedades que não são objetos.
Aqui está uma tabela de resumo dos métodos considerados para proteção de objetos, extraídos 
daqui .
|  | Criação de Propriedade 
 | Leitura de propriedades 
 | Substituição de propriedade 
 | Remoção de propriedade 
 | 
| Object.freeze()
 | - | + 
 | - | - | 
| Object.seal()
 | - | + 
 | + 
 | - | 
| Object.preventExtensions()
 | - | + 
 | + 
 | + 
 | 
Sumário
Dada a frequência com que os objetos são usados no JavaScript, é importante que todo desenvolvedor saiba como eles estão organizados. Esperamos que o que você aprendeu lendo este material seja útil para você. Além disso, agora você sabe as respostas para as perguntas listadas no início do artigo.
Caros leitores! Como você protege objetos JavaScript?