Why can I set [enumerability and] writability of unconfigurable property descriptors?
Asked Answered
G

1

6

https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/defineProperty states:

configurable: True if and only if the type of this property descriptor may be changed and if the property may be deleted from the corresponding object. Defaults to false.

So, I have a

var x = Object.defineProperty({}, "a", {
    value:true,
    writable:true,
    enumerable:true,
    configurable:false
});

Now I can play with x.a = false, for(i in x) etc. But even though the descriptor is should be unconfigurable, I can do

Object.defineProperty(x, "a", {writable:true}); // others defaulting to false
Object.defineProperty(x, "a", {}); // everything to false
Object.freeze(x); // does the same to the descriptors

The other way round, setting them to true again, or trying to define an accessor descriptor, raises errors now. To be exact: Object.defineProperty: invalid modification of non-configurable property.

Why can I "downgrade" descriptors though they say they were non-configurable?

Glycine answered 22/3, 2012 at 20:16 Comment(0)
M
16

First, even when configurable is false, writable can be changed from true to false. This is the only attribute change allowed when configurable is false. This transition was allowed because some built-in properties including (most notably) the length property of arrays (including Array.prototype) are specified to be writable: true, configurable: false. This is a legacy of previous ECMAScript editions. If configurable: false prevented changing writable from true to false then it would be impossible to freeze arrays.

Object.defineProperty doesn't work quite like you're assuming. In particular, how it processes the property descriptor works differently depending upon whether or not the property already exists. If a property does not exist, the descriptor is supposed to provide a definition of all attributes so any missing attributes in the descriptor are assigned default values before the descriptor is used to create the property. However, for an already existing property the descriptor is taken as a set of delta changes from the current attribute settings of the property. Attributes that are not listed in the descriptor are not changed. Also, a attribute that has the same value in the delta descriptor as the current property attribute value is also consider no change. So the following are all legal:

Object.defineProperty(x, "a", {writable:false}); // can always change writable to false.
                                                //others attributes, not changed
Object.defineProperty(x, "a", {});     // no attributes, so nothing changes
Object.freeze(x); // same as Object.defineProperty(x, "a", {writable:false});
Object.defineProperty(x, "a", {enumerable:true, configurable: false}); //no change, 
Muth answered 23/3, 2012 at 16:49 Comment(3)
Ah, thanks for the explanation. I thought I even had changed enumerability of an unconfigurable property in one of my tests (explicit, not with these misleading default-values), but I can't reproduce now.Glycine
Object.freeze(x) is is not same as setting writable to false! It is in fact setting writable and configurable to false rendering the object completely immutable (i.e. turns into a constant).Feedback
@Feedback That is not true, according to the MDN docs for Object.freeze(). It is true that freeze() is shallow, however; see those docs for an example of a deep-freeze function.Investigate

© 2022 - 2024 — McMap. All rights reserved.