Question

I see very often to pass around unnamed data objects in JavaScript, e.g. { a: 1, b: 2}. Is it a good practice or is better to make a simple data class for that like in other languages:

class MyDataClass {
  constructor({ a, b }) {
     this.a = a;
     this.b = b;
  }
}

E.g. to improve code quality, maintainability, robustness, mistypes, etc. Some other advantages may be: parameter checking may be added, fields may be hidden by readonly properties (getters), ...

Addendum: JavaScript unnamed objects are like C# ExpandoObject. If any C# programmer would toss ExpandoObject without real purpose, then they would be fired. But unnamed objects in JavaScript are accepted.

Was it helpful?

Solution

This is trending to be a very opinion based question, but here is my attempt at a non opinion based answer.


Data objects, as you are calling them, are more formally known as a Data Transfer Objects (DTO). A data transfer object is just a bag of data used to transport information between processes, be it across a network or from one literal process on a computer to another. It usually involves serializing and deserializing data.

Data transfer objects are a workaround required by strongly typed languages when you want to deal with strongly typed data. Well, "workaround" is probably the wrong word here. "Necessity" is probably a better word. The compiler needs to know ahead of time what the data types will be for all members in a data transfer object in languages like C#, Java and C++.

JavaScript and ECMA Script do not have these constraints. Not only is knowing data types ahead of time unnecessary, but a simple string representation of these data structures is natively parseable by browsers (JSON). Defining a class for a DTO is not only unnecessary, but impossible if you want to deserialize it using JSON.parse() or serialize it using JSON.stringify().

Using "data objects" or anonymously typed objects in JavaScript/ES for DTOs is not just a "good practice" but it is a requirement of the language's native data format: JSON. The same can be said about XML and HTML. The code that sits between an AJAX request and your other application code in the front end must use anonymously typed objects, but there are other use cases where anonymous types are "good practice" as well.

Using "options arguments" to a function is a common example where defining a custom type in JavaScript/ES is usually unnecessary. Most jQuery plugins are examples of this. This pattern boils down to passing configuration data. These data structures are usually very simple and have few constraints about their construction. They usually have no behavior associated with them, aside from callback functions invoked during an event. Often options arguments are used as a means to support function overloading in a language that does not support function overloading, where the function changes its behavior depending on the properties in the options argument.

The last use case for anonymous types in JavaScript/ES is for simple data structures that are not functionally considered configuration, and whose runtime data do not change the function behavior. Think of cases like foo.moveTo({ x: 3, y : -2 }) where the argument is a simple x,y point. This pattern seems to take the place of named arguments in other languages: foo.moveTo(x: 3, y: -2). This works well for simple data structures, and helps code authors call the function correctly when it takes arguments with the same type:

let x = 3;
let y = -2;

// Oops! I transposed the x and y, but the function runs without error
// and produces unexpected output:
foo.moveTo(y, x)

// Now order doesn't matter. This:
foo.moveTo({ y: y, x: x })

// Executes the same as this:
foo.moveTo({ x: x, y: y })

All of the use cases for data transfer objects above have two things in common:

  • No constraints exist at the time of object construction
  • No behavior is coupled to the data

This finally leads us to knowing when defining a custom type in JavaScript/ES is beneficial. If you need to enforce constraints at the time of object construction, then you need a constructor function:

class TimestampRange {
    constructor(begin, end) {
        if (begin > end) {
            throw new Error("Begin date cannot occur before end date");
        }

        this.begin = begin;
        this.end = end;
    }
}

If you must combine behavior with this data, then define a custom type with instance methods:

class TimestampRange {
    getElapsedTime() {
        return this.end - this.begin;
    }
}

If you do not need either of these features, then a class just bloats your code base without any benefits.

Data (transfer) objects, by their very nature, do not have constraints applied at the time of object construction and have no behavior, so anonymous types are the better choice, because JavaScript/ES is a loosely typed language.

OTHER TIPS

Not if you're going to name it MyDataClass. Yuck. That's a terrible name.

I'll go to a lot of trouble just so that I can give something a good name. But a bad name is worse than no name. No names come in many forms: anonymous functions, lambdas, and tuples to name a few. A known structure doesn't need a name if use is enough to make it's correct form clear.

When you give a pile of data a class name it should have meaning. The name should make clear what belongs in the class and what doesn't. The name should help make clear what forms are valid.

When your naming skills can't live up to that then please avoid naming it MyFooClass if you can. We don't need meaningless noise.

If you are using JavaScript or ES6, in such a case you should use ES6 objects as they are more efficient and easier for you as well as for other developers also. Obviously, if ES6 is providing you some better functionalities then it must be more efficient. So, I suggest you use ES 6 syntax.

Licensed under: CC-BY-SA with attribution
scroll top