Detalhes do objeto JavaScript

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) // => '101' (5    ) 

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 // => 'Arfat' 

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; // => 'Arfat' const dataObj = { name: 'Arfat', }; dataObj.name; // => 'Arfat' 

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); //        }, set id(value) {   this._id = value; } } obj.name = 'Arfat'; obj.name; // => 'ARFAT' obj.id = 5; obj.id; // => '101 

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 propriedades

Nosso 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'); /* { value: 5, writable: true, enumerable: true, configurable: true } */ 

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)   // => 23 } } obj.x = 23; 

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 de true para false .
  • Até que o atributo [[Writable]] seja definido como false , o atributo [[Value]] pode ser alterado. Porém, depois que os atributos [[Writable]] e [[Configurable]] forem definidos como false , 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 = {}; // #1 Object.defineProperty(obj, 'id', { value: 42 }); // #2 console.log(obj); // => { } // #3 console.log(obj.id); // => 42 // #4 Object.defineProperty(obj, 'name', { value: 'Arfat', writable: false, enumerable: true, configurable: true }); // #5 console.log(obj.name); // => 'Arfat' // #6 obj.name = 'Arfat Salman' // #7 console.log(obj.name); // => 'Arfat' // (  'Arfat Salman') Object.defineProperty(obj, 'lastName', { value: 'Salman', enumerable: false, }); console.log(Object.keys(obj)); // => [ 'name' ] // #8 delete obj.id; // #9 console.log(obj.id); // => 42 //#10 Object.defineProperties(obj, { property1: {   value: 42,   writable: true }, property2: {} }); console.log(obj.property1) // => 42 

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); // => { id: 42 } 

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 como false , 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 // ( ) obj.name = 'Arfat'; // ( ) console.log(obj); // => { id: 42 } Object.isExtensible(obj); // => false Object.isSealed(obj); //=> true 

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 // ( ) obj.name = 'Arfat'; // ( ) console.log(obj); // => { id: 42 } Object.isExtensible(obj); // => false Object.isSealed(obj); //=> true Object.isFrozen(obj); // => true 

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?

Source: https://habr.com/ru/post/pt438794/


All Articles