The Object defineProperties() method

By

Learn how the JavaScript Object.defineProperties() method creates or configures several object properties at once using property descriptor objects.

~~~

Object.defineProperties() creates or configures multiple object properties at once, using property descriptors. It returns the object it modified.

It takes 2 arguments. The first is an object upon which we’re going to create or configure the properties. The second is an object of properties.

Example:

const dog = {}
Object.defineProperties(dog, {
  breed: {
    value: 'Siberian Husky'
  }
})
console.log(dog.breed) //'Siberian Husky'

I didn’t just say breed: 'Siberian Husky' but I had to pass a property descriptor object. A descriptor describes the value of the property and how the property behaves: can it be changed, does it show up in loops, can it be deleted.

Watch out for the defaults

Here’s the pitfall. When you define a property this way, every descriptor flag you don’t set defaults to false.

That means the breed property above is not writable, not enumerable, and not configurable:

dog.breed = 'Labrador'
console.log(dog.breed) //'Siberian Husky' (unchanged)

console.log(Object.keys(dog)) //[]
console.log(JSON.stringify(dog)) //'{}'

The assignment fails silently in normal code, and throws a TypeError in strict mode. The property is also invisible to Object.keys() and to JSON.stringify(), which trips people up when the object looks empty in the output.

This is the opposite of what happens with a plain assignment like dog.breed = 'Siberian Husky', where the property is writable, enumerable and configurable.

If you want a normal, editable property, say so in the descriptor:

const dog = {}
Object.defineProperties(dog, {
  breed: {
    value: 'Siberian Husky',
    writable: true,
    enumerable: true,
    configurable: true
  }
})

When would you reach for this?

The locked-down defaults are the point. Use Object.defineProperties() when you want constants on an object that nothing can overwrite, or internal properties that stay out of loops and serialization.

You can also define getters instead of values:

const dog = {}
Object.defineProperties(dog, {
  breed: { value: 'Siberian Husky', enumerable: true },
  intro: {
    get() {
      return `A beautiful ${this.breed}`
    }
  }
})
console.log(dog.intro) //'A beautiful Siberian Husky'

intro is computed every time you access it.

It can be used in conjunction with Object.getOwnPropertyDescriptors() to copy properties over from another object:

const wolf = { /*... */ }
const dog = {}
Object.defineProperties(dog, Object.getOwnPropertyDescriptors(wolf))

This copies getters, setters and all the descriptor flags exactly as they are, which a plain Object.assign() does not do. Object.assign() reads the values and assigns them, so getters get flattened into static values.

~~~

Related posts about js: