DEV Community

Cristian Curteanu
Cristian Curteanu

Posted on

How else you can define properties for JavaScript objects

JavaScript is an awesome technology. It's really flexible, and allows cool runtime object manipulation, which are not such a headache like in case with Ruby (though that's my personal opinion after working with both).

The common way of properties definition

Well, there are several ways of default properties definition. First one is using object initializer literal. For instance let's have the Car object:

var Car = {
    brand: 'Toyota',
    model: 'Prius',
    engine: {
        state: 'off'
    },
    turnOn: function() {
        this.engine.state = 'on'
        console.log('Engine is:', this.engine.state)
    }
}
Enter fullscreen mode Exit fullscreen mode

This way object's properties are accessible from the outside world, like:

Car.brand // => "Toyota"
Car.brand = 'Honda'
Car.brand // => "Honda"

Car.engine.state // => "off"
Car.turnOn() // => "Engine is: on"
Car.engine.state // => "on"
Enter fullscreen mode Exit fullscreen mode

And can be changed with values of other type.

Another way of property definition is in the function constructor using this keyword, which will reference to current Function object:

function Car() {
    this.brand = 'Toyota'
    this.model = 'RAV 4'
    this.engine = {
        state: 'off'
    }
}
Enter fullscreen mode Exit fullscreen mode

and they are also accessible from the outside world:

var car = new Car()

car.brand // => "Toyota"
car.brand = 'Honda'
car.brand // => "Honda"
Enter fullscreen mode Exit fullscreen mode

But there are some additional features for property definitions in JavaScript which will be described in next section.

Defining property using Object.defineProperty method

According to documentation, this is a Object's static method, which takes an object as first parameter, the name of new property as the second parameter, and an object with options. Let's see the next example:

var Car = {
    brand: 'Toyota'
}

Object.defineProperty(Car, 'brand', {
  writable: false,
})

Car.brand // => "Toyota"
Car.brand = 'BMW'
Car.brand // => "Toyota"
Enter fullscreen mode Exit fullscreen mode

This way, the brand property can not be overwritten from the outside world. This way, it is possible to set all the aspects of an object property, and gives a good portion of control over the objects' properties. Here are some other options, that should be taken in account:

  • configurable - has false as default, and will allow to change the type of this property or delete the property from this current object if the value will be true

  • enumerable - this will indicate if this property should be shown during enumeration, and it will do so only if it's value will be true. By default it is false

  • writable - true if this property should be changed with an assignment operator =. Default to false

  • value - it would take any value of any valid, specific type, like number, object, function, etc.

  • get - this property can take a function as a value and it will override the property value extraction of object through which the property is accessed

  • set - this property can take a function as a value with a single argument, which can override the logic of assignment operator of object through which the property is accessed.

Let's consider a more complex example. If there is a need to manipulate some other data inside an object, without specific method invocation, it is wise to customise the set property of object property, as follows:

function CustomGettersSetters() {
    var a = null;
    var history = [];

    Object.defineProperty(this, 'a', {
        get() {
            console.log('Inside getter')
            return a
        },
        set(arg) {
            console.log('Inside setter')
            history.push(arg)
            a = arg
            return true
        }
    })

    this.getHistory = function() { return history }
}

var custom = new CustomGettersSetters();

custom.a // will log "Inside getter" and return `null`
custom.a = '321' // will log "Inside setter", push a value to `history`, and will set value to `a`
custom.a = 764 // will log "Inside setter", push a value to `history`, and will set value to `a`
custom.a // will log "Inside getter" and return `764`
custom.history // will return `undefined`
custom.getHistory() // will return an array ["321", 764]

Enter fullscreen mode Exit fullscreen mode

This way there was added an additional functionality for the current object, without any need for any additional method calls.

Conclusion

The Object.defineProperty method is a very powerfull tool for object property manipulation, as it allows some sort of control over it, which can be useful at some point. There is also Object.defineProperties which can create multiple properties in similar way. The difference is that it takes two parameters: first is the object which will have new properties, and second would be an object of properties, which also will contain an object of options mentioned above.

Top comments (3)

Collapse
 
dekadentno profile image
Matej

Thanks for the post, I've found it very useful.
Found a little mistake in your code. In your initialization, you have:

...
engine: {
        stage: 'off'
    }
...

But everywhere else, you've used (e.g.):

this.engine.state = 'on'

Collapse
 
cristicurteanu profile image
Cristian Curteanu

Hey Matej!

I have noticed it, thanks for pointing out! I have added the proper changes, and now it should be fine!

Cheers!

Collapse
 
ryanmaffey profile image
Ryan Maffey

I had no idea you could do so much with Object.defineProperty! Really useful info - thanks for the post :)