Descritores de Propriedades JavaScript
Aprenda como flags e descritores de propriedades JavaScript (writable, enumerable, configurable) funcionam, incluindo defineProperty, getOwnPropertyDescriptors, freeze, seal e strict mode.
As flags e descritores de propriedades do JavaScript oferecem controle preciso sobre as propriedades de objetos, permitindo o desenvolvimento de aplicações robustas e seguras. Este artigo explora esses recursos em detalhes, fornecendo insights práticos e exemplos de código para ajudá-lo a gerenciar eficazmente o comportamento das propriedades.
Compreendendo os Atributos de Propriedades do JavaScript
Objetos JavaScript são coleções de propriedades, e cada propriedade possui atributos associados que definem seu comportamento. Esses atributos, frequentemente chamados de flags de propriedade, incluem:
- Writable: Determina se o valor da propriedade pode ser alterado.
- Enumerable: Controla se a propriedade é visível durante a enumeração, como em um loop
for...in. - Configurable: Especifica se a propriedade pode ser excluída ou modificada.
Essas flags são fundamentais para controlar o acesso às propriedades do objeto, garantindo a integridade dos dados e implementando encapsulamento em aplicações JavaScript.
Explorando os Descritores de Propriedades
Os descritores de propriedades fornecem informações detalhadas sobre a propriedade de um objeto, encapsulando seu valor e flags. Eles são recuperados usando Object.getOwnPropertyDescriptor(obj, propName) e definidos usando Object.defineProperty(obj, propName, descriptor). Um objeto descritor de propriedade pode conter:
value: O valor associado à propriedade.writable: Indica se o valor da propriedade pode ser alterado.enumerable: Indica se a propriedade é enumerável.configurable: Determina se o descritor da propriedade pode ser alterado e se a propriedade pode ser excluída do objeto.
Observação: Quando você cria uma propriedade da forma normal (user.name = "John"), as três flags são definidas como true. Mas ao definir uma nova propriedade via Object.defineProperty, qualquer flag não especificada tem como padrão false.
Object.getOwnPropertyDescriptor analisa apenas as propriedades próprias do objeto. Se você consultar uma propriedade que o objeto herda de seu protótipo (ou uma propriedade que não existe), ele retorna undefined.
Para saber mais sobre como os objetos herdam propriedades, veja Herança Prototípica.
Descritores de Dados vs. Descritores de Acesso
Até agora descrevemos descritores de dados, que armazenam um value junto com a flag writable. O JavaScript também suporta descritores de acesso, que substituem value/writable por funções getter e setter:
get: uma função chamada quando a propriedade é lida (não recebe argumentos).set: uma função chamada quando a propriedade é atribuída (recebe o novo valor).
Um descritor é ou um descritor de dados ou um descritor de acesso — nunca os dois ao mesmo tempo. Combinar value/writable com get/set lança um erro. Ambos os tipos ainda compartilham as flags enumerable e configurable.
As propriedades de acesso são a forma de computar um valor na leitura ou validá-lo na escrita. Aqui expõemos um acessor fullName baseado em duas propriedades de dados:
Para um tratamento mais completo da sintaxe get/set (incluindo a forma abreviada dentro de literais de objeto), veja Getters e Setters de Propriedades. Como getters e setters são executados com this vinculado ao objeto, também é útil entender Métodos de Objeto e "this".
Definindo e Lendo Várias Propriedades de Uma Vez
Para trabalhar com várias propriedades em um único passo, o JavaScript fornece os equivalentes plurais dos métodos acima:
Object.defineProperties(obj, descriptors)define múltiplas propriedades a partir de um mapa de descritores.Object.getOwnPropertyDescriptors(obj)retorna os descritores de todas as propriedades próprias (incluindo não enumeráveis e chaves symbol) como um único objeto.
Object.getOwnPropertyDescriptors é especialmente útil para clonar um objeto com suas flags — um spread simples ou Object.assign copia valores, mas redefine todas as flags como true e ignora os acessores.
Manipulando Flags de Propriedades
Compreender e manipular flags de propriedades é fundamental para um desenvolvimento JavaScript eficaz. Vamos explorar como controlar essas flags para ajustar finamente o comportamento das propriedades.
Tornando uma Propriedade Não-gravável
Impedir modificações em uma propriedade garante a consistência dos dados. Isso pode ser feito definindo a flag writable como false.
O comportamento da atribuição falha depende do modo em que o código é executado. No modo não-strict, escrever em uma propriedade não-gravável falha silenciosamente: a atribuição é simplesmente ignorada, nenhum erro é lançado e a execução continua — o que pode ocultar bugs. No modo strict ("use strict", e o padrão dentro de módulos ES e corpos de classe), a mesma atribuição lança um TypeError. A regra se aplica a qualquer operação que viole uma flag: excluir uma propriedade não-configurável ou adicionar uma propriedade a um objeto não-extensível também falha silenciosamente no modo não-strict e lança no modo strict.
Ocultando uma Propriedade da Enumeração
Às vezes, é necessário ocultar propriedades dos processos de enumeração, como loops for...in. Isso pode ser feito definindo a flag enumerable como false.
Impedindo a Exclusão e Modificação de Propriedades
Para garantir que uma propriedade permaneça uma parte constante de um objeto, defina a flag configurable como false.
Marcar uma propriedade como não-configurável é uma operação irreversível — não há flag para torná-la configurável novamente, e você não pode mais alternar enumerable ou converter a propriedade entre um descritor de dados e um descritor de acesso.
No entanto, há duas exceções importantes enquanto uma propriedade é não-configurável:
- Você pode alterar
writabledetrueparafalse(mas não de volta defalseparatrue). - Se a propriedade ainda for
writable: true, você pode alterar seuvalue— seja por atribuição direta ou viaObject.defineProperty.
Em outras palavras, configurable: false bloqueia a forma da propriedade, mas não necessariamente seu valor. Para verdadeiramente congelar o valor de uma propriedade, defina tanto configurable: false quanto writable: false.
APIs de Alto Nível Baseadas nessas Flags
Raramente é necessário definir flags uma propriedade por vez. O JavaScript fornece três métodos embutidos que alteram essas flags em um objeto inteiro:
Object.preventExtensions(obj)— impede que novas propriedades sejam adicionadas. As propriedades existentes ainda podem ser alteradas ou excluídas.Object.seal(obj)— impede adicionar e excluir propriedades, marcando cada propriedade existente comoconfigurable: false. Os valores ainda podem ser alterados.Object.freeze(obj)— o mais restritivo: sela o objeto e torna cada propriedadewritable: false, de modo que nada pode ser adicionado, removido ou alterado.
Cada método possui uma verificação correspondente: Object.isExtensible, Object.isSealed e Object.isFrozen. Observe que esses operam em apenas um nível — Object.freeze não congela objetos aninhados (é um congelamento "superficial").
Conclusão
Flags e descritores de propriedades oferecem controle preciso sobre como as propriedades de objeto se comportam:
- Um descritor de dados combina um
valuecomwritable; um descritor de acesso usa funçõesget/setem vez disso. Ambos compartilhamenumerableeconfigurable. - Leia flags com
Object.getOwnPropertyDescriptor(uma propriedade) ouObject.getOwnPropertyDescriptors(todas as propriedades próprias); escreva-as comObject.definePropertyouObject.defineProperties. Propriedades herdadas e ausentes retornamundefined. configurable: falseé irreversível e bloqueia a forma da propriedade, embora uma propriedade aindawritablepossa ter seu valor alterado e sua flagwritabledesativada.- Violar uma flag falha silenciosamente no modo não-strict, mas lança um
TypeErrorno modo strict. - Use
Object.freeze,Object.sealeObject.preventExtensionsquando quiser bloquear um objeto inteiro em vez de flags individuais.
Próximos passos: aprofunde-se em Getters e Setters de Propriedades para a sintaxe de acessor, Métodos de Objeto e "this" para como this se comporta dentro deles, e Herança Prototípica para ver como a busca de propriedades percorre a cadeia de protótipos.