JavaScript Function Flags and Descriptors

JavaScript Function Flags and Descriptors

·

6 min read

We know that objects contain properties that are key-value pairs, but there's more to that — it includes the flag attributes.


Property flags

The 3 other attributes besides the value property are:

  • writable — override (change) current value, if true.
  • enumerable — properties are listed in a loop if true.
  • configurable — deleted property can have a configurable attribute if true.

We have been able to change, loop, and delete properties in an object because the 3 attributes listed above are true by default.

We can make the above attributes false to prevent changes, looping, and deletion of properties.

First, there are some methods we need to get or use.

It is recommended to use use strict at the top of your program or function when using any of the methods below:

  • Object.defineProperty: It returns a mutable object by modifying the existing property object. It doesn't affect the original object or property. That is it updates or changes the property flag.

Syntax:

Object.defineProperty(obj, key, descriptor)
  • Object.defineProperties: We can also define many properties at once with the method Object.defineProperties.

Syntax:

Object.defineProperties(obj, {
  prop1: descriptor1,
  prop2: descriptor2
  // ...
});

See the example below:

Object.defineProperties(person, {
  surname: { value: "Bello", writable: false },
  firstName: { value: "Osagie", writable: false },
  // ...
});
  • Object.getOwnPropertyDescriptor: returns a mutable object to describe a specific property (key). It doesn't affect the original object or property.

Syntax:

descriptor = Object.getOwnPropertyDescriptor(obj, key)
  • Object.getOwnPropertyDescriptor: We can also get all property descriptors at once.

Syntax:

Object.defineProperties({}, Object.getOwnPropertyDescriptors(obj));

For a better cloning use Object.defineProperties

The example below does not copy flags even though we clone the object, person

...    ...    ...
for (let key in person) {
  clone[key] = person[key]
}
...    ...    ...

for..in ignores symbolic properties, but Object.getOwnPropertyDescriptors doesn't, it returns all property descriptors including symbolic ones.

  • JSON.stringify: It converts an object to a JSON string. It optionally contains the replacer argument to replace values and an optional space argument to specify the amount of padding from the left. The spacer can be any character besides a number.

Syntax:

JSON.stringify(obj, [replacer[, space]] )

For the case of this topic, the syntax above can be rewritten as shown below:

JSON.stringify(descriptor, [replacer[, space]] )

See the example below:

const person = {
  name: "Bello"
};

const descriptor = Object.getOwnPropertyDescriptor(person, 'name');

console.log( JSON.stringify(descriptor, null, '- ' ) );
/*
{
- "value": "Bello",
- "writable": true,
- "enumerable": true,
- "configurable": true
}
*/

Let's change the property of the existing property.

see the example below:

const person = {
  name: "Bello"
};

Object.defineProperty(person, "name", {
  value: "John"
});

const descriptor = Object.getOwnPropertyDescriptor(person, 'name');
console.log( JSON.stringify(descriptor, null, '- ' ) );

/*
{
- "value": "Bello",
- "writable": true,
- "enumerable": true,
- "configurable": true
}
*/

{
- "value": "John",
- "writable": true,
- "enumerable": true,
- "configurable": true
}

The attributes are all true, but we can change any one of them or all to false (when an existing object is an empty object).

const person = {};

Object.defineProperty(person, "name", {
  value: "John"
});

const descriptor = Object.getOwnPropertyDescriptor(person, 'name');
console.log( JSON.stringify(descriptor, null, '- ' ) );

/*
{
- "value": "John",
- "writable": false,
- "enumerable": false,
- "configurable": false
}
*/

Apart from getting the values of the attribute, we can also set the attribute values.

Let's specify these attributes below in the object.


Falsy flag properties

Non-writable

Let’s make person.name non-writable. See below:

'use strict'

const person = {
  name: "Bello"
};

Object.defineProperty(person, "name", {
  writable: false
});

person.name = "John"; // TypeError: Cannot assign to read only property 'name' of object '#<Object>'

The existing person name can be changed only if the method defineProperty specifies the writable flag to be true.

See the example below:

'use strict'

const person = { };

Object.defineProperty(person, "name", {
  value: "John",
  writable: true
});

console.log(person.name); // John
person.name = "Bello"; // Bello

Non-enumerable

Properties are listed in a loop if true, if false then they are skipped.

In the example below, the property, greet is listed in the for..in when enumerable is true.

const person = {
  name: "Bello",
  greet() {
    return `Hello ${this.name}.`;
  }
};

/*
Object.defineProperty(person, "greet", {
  enumerable: false
});
*/

for (let key in person) { 
  console.log(key);
  /*
  name
  greet
  */
}

The above example is the default behavior. To avoid the greet, enumerable must be set to false.

See the example below:

const person = {
  name: "Bello",
  greet() {
    return `Hello ${this.name}.`;
  }
};

Object.defineProperty(person, "greet", {
  enumerable: false
});

for (let key in person) { 
  console.log(key);
  /*
  name
  */
}

The claim above is also true for Object.keys. See below:

const person = {
  name: "Bello",
  greet() {
    return `Hello ${this.name}.`;
  }
};

Object.defineProperty(person, "greet", {
  enumerable: false
});

console.log(Object.keys(person));

/*
  name
*/

Non-configurable

The configurable flag set to false is sometimes used on built-in objects and properties.

By default, we can change constant values when the configurable flag is true.

See the example below:

let x = Math.PI; // const x = Math.PI
x = 3;
console.log(x); //

Note: const should be used on constants like Math.PI above, birthday, etc. For practice, we will use let.

A non-configurable property can not be deleted.

See the example below:

'use strict'

const descriptor = Object.getOwnPropertyDescriptor(Math, 'PI');
console.log( JSON.stringify(descriptor, null, 2 ) );
/*
{
  "value": 3.141592653589793,
  "writable": false,
  "enumerable": false,
  "configurable": false
}
*/

Math.PI = 3; 
// TypeError: Cannot assign to read only property 'PI' of object

Also, you can't delete Math.PI when configurable is false.

see the example below:

'use strict'

const descriptor = Object.getOwnPropertyDescriptor(Math, 'PI');
console.log( JSON.stringify(descriptor, null, 2 ) );
/*
{
  "value": 3.141592653589793,
  "writable": false,
  "enumerable": false,
  "configurable": false
}
*/

delete Math.PI; // TypeError: Cannot delete property 'PI' of #<Object>

See another example.

'use strict'

const Personbirth= {
  birthday: "1993 Nov, 3"
};

Object.defineProperty(Personbirth, "birthday", {
  configurable: false
});

Personbirth.birthday = "1993 Nov, 4";
delete Personbirth.birthday;

A property that is become non-configurable cannot be changed back with defineProperty.

Other methods besides the methods mentioned earlier are listed below:

  • Object.preventExtensions(obj): Prevents adding of properties to an object. mplication implies writable: false.

  • Object.seal(obj): Prevents adding or removing of properties to an object. Implication implies configurable: false.

  • Object.freeze(obj): Prevents adding, removing, or changing of properties to an object. Implication implies configurable: false, writable: false.

  • Object.isExtensible(obj): Returns false if all current properties are non-writable writable: false, true otherwise.

  • Object.isSealed(obj): Returns true if all current properties are non-configurable configurable: false, false otherwise.

  • Object.isFrozen(obj): Returns true if all current properties are non-configurable and non-writable configurable: false, writable: false, false otherwise.


Buy me a Coffee