131

Previously, when I needed to store a number of related variables, I'd create a class.

function Item(id, speaker, country) {
  this.id = id;
  this.speaker = speaker;
  this.country = country;
}
var myItems = [new Item(1, 'john', 'au'), new Item(2, 'mary', 'us')];

But I'm wondering if this is a good practice. Are there any other, better ways to simulate a struct in JavaScript?

Mario Petrovic
  • 5,868
  • 12
  • 32
  • 54
nickf
  • 520,029
  • 197
  • 633
  • 717

9 Answers9

197

The only difference between object literals and constructed objects are the properties inherited from the prototype.

var o = {
  'a': 3, 'b': 4,
  'doStuff': function() {
    alert(this.a + this.b);
  }
};
o.doStuff(); // displays: 7

You could make a struct factory.

function makeStruct(names) {
  var names = names.split(' ');
  var count = names.length;
  function constructor() {
    for (var i = 0; i < count; i++) {
      this[names[i]] = arguments[i];
    }
  }
  return constructor;
}

var Item = makeStruct("id speaker country");
var row = new Item(1, 'john', 'au');
alert(row.speaker); // displays: john
Markus Jarderot
  • 83,508
  • 19
  • 134
  • 137
  • I like this approach, however be careful if you use the closure compiler. The tuple can only be accessed as string in this case, because the properties are renamed. (At least in advanced mode) – kap Mar 24 '16 at 08:15
  • 3
    I was curious about the efficiency of this method over the object literals. – c0degeas Jul 05 '19 at 13:22
  • It is maybe also possible to use JS class. – SphynxTech Apr 04 '20 at 12:58
37

I always use object literals

{id: 1, speaker:"john", country: "au"}
vava
  • 23,905
  • 11
  • 61
  • 78
  • 3
    wouldn't that make it much harder to maintain (should you want to add a new field in the future), and also much more code (retyping "id", "speaker", "country" every time)? – nickf Feb 02 '09 at 06:53
  • 6
    It is exactly as maintainable as solution with classes because JavaScript doesn't care about number of arguments you call the function with. Retyping is not an issue if you using right tools like Emacs. And you can see what equals what which makes mistakes like swapping arguments obsolete. – vava Feb 02 '09 at 07:22
  • 5
    But the biggest pro is that you would write less code and it'll be cleaner :) – vava Feb 02 '09 at 07:24
  • 3
    @vava retyping is still an issue, as there are more jumps than copying the new ___ ( , , , ) archetype. Also, there is no readability. Once you get used to coding, `new READABLE_PART(ignore everything in here)` becomes very scannable and self documenting, as opposed to `{read: "ignore", everything: "ignore", in: "ignore", here: "ignore"} // and then come up with READABLE_PART` in your head. The first version is readable while paging up rapidly. The second, you refactor into structs merely to understand. – Chris Apr 04 '18 at 15:28
24

The real problem is that structures in a language are supposed to be value types not reference types. The proposed answers suggest using objects (which are reference types) in place of structures. While this can serve its purpose, it sidesteps the point that a programmer would actual want the benefits of using value types (like a primitive) in lieu of reference type. Value types, for one, shouldn't cause memory leaks.

EDIT: There is a proposal in the works to cover this purpose.

//today
var obj = {fname: "Kris", lname: "Kringle"}; //vanilla object
var gifts = ["truck", "doll", "slime"]; //vanilla array

//records and tuples - not possible today
var obj = #{fname: "Buddy", lname: "Hobbs"};
var skills = #["phone calls", "basketball", "gum recycling"];

Mario
  • 6,363
  • 3
  • 39
  • 71
9

I think creating a class to simulate C-like structs, like you've been doing, is the best way.

It's a great way to group related data and simplifies passing parameters to functions. I'd also argue that a JavaScript class is more like a C++ struct than a C++ class, considering the added effort needed to simulate real object oriented features.

I've found that trying to make JavaScript more like another language gets complicated fast, but I fully support using JavaScript classes as functionless structs.

peter
  • 5,977
  • 2
  • 30
  • 44
  • 2
    I'd love to have something like structs, tuples - something that allows strongly typed collections of data - that is dealt with at compiletime and doesn't have the overhead of hashmaps like objects – derekdreery Mar 23 '16 at 09:20
  • 1
    @derekdreery Agreed. Right now you can use bit arrays but it's a huge hassle so it's only for performance optimisations. – EnderShadow8 May 09 '21 at 06:58
8

Following Markus's answer, in newer versions of JS (ES6 I think) you can create a 'struct' factory more simply using Arrow Functions and Rest Parameter like so:

const Struct = (...keys) => ((...v) => keys.reduce((o, k, i) => {o[k] = v[i]; return o} , {}))
const Item = Struct('id', 'speaker', 'country')
var myItems = [
    Item(1, 'john', 'au'),
    Item(2, 'mary', 'us')
];

console.log(myItems);
console.log(myItems[0].id);
console.log(myItems[0].speaker);
console.log(myItems[0].country);

The result of running this is:

[ { id: 1, speaker: 'john', country: 'au' },
  { id: 2, speaker: 'mary', country: 'us' } ]
1
john
au

You can make it look similar to Python's namedtuple:

const NamedStruct = (name, ...keys) => ((...v) => keys.reduce((o, k, i) => {o[k] = v[i]; return o} , {_name: name}))
const Item = NamedStruct('Item', 'id', 'speaker', 'country')
var myItems = [
    Item(1, 'john', 'au'),
    Item(2, 'mary', 'us')
];

console.log(myItems);
console.log(myItems[0].id);
console.log(myItems[0].speaker);
console.log(myItems[0].country);

And the results:

[ { _name: 'Item', id: 1, speaker: 'john', country: 'au' },
  { _name: 'Item', id: 2, speaker: 'mary', country: 'us' } ]
1
john
au
typoerrpr
  • 1,496
  • 19
  • 18
  • It looks like the struct in C/C++ and other languages, but actually it is not - the , properties in objects are not guaranteed to be ordered described as following: Definition of an Object from ECMAScript Third Edition (pdf): 4.3.3 Object An object is a member of the type Object. It is an unordered collection of properties each of which contains a primitive value, object, or function. A function stored in a property of an object is called a method – tibetty Jan 07 '19 at 03:15
3

I use objects JSON style for dumb structs (no member functions).

Robert Gould
  • 67,019
  • 60
  • 184
  • 270
2

It's more work to set up, but if maintainability beats one-time effort then this may be your case.

/**
 * @class
 */
class Reference {

    /**
     * @constructs Reference
     * @param {Object} p The properties.
     * @param {String} p.class The class name.
     * @param {String} p.field The field name.
     */
    constructor(p={}) {
        this.class = p.class;
        this.field = p.field;
    }
}

Advantages:

  • not bound to argument order
  • easily extendable
  • type script support:

enter image description here

Manuel
  • 13,054
  • 5
  • 45
  • 107
1

I made a small library to define struct if you work with ES6 compatibility.

It is a JKT parser you may checkout the project repository here JKT Parser

For an example you may create your struct like this

const Person = jkt`
    name: String
    age: Number
`

const someVar = Person({ name: "Aditya", age: "26" })

someVar.name // print "Aditya"
someVar.age // print 26 (integer)

someVar.toJSON() // produce json object with defined schema 
Aditya Kresna Permana
  • 11,266
  • 7
  • 38
  • 46
0

This is an old problem that it doesn't seem has been addressed yet. For what it's worth, I use immutability to get similar behavior. Using Typescript:

export class Point {
   public readonly X: number;
   public readonly Y: number;

   constructor(x: number, y: number)
   {
       this.X = x;
       this.Y = y;
   }

   public static SetX(value: number) : Point {
       return new Point(value, this.Y);
   }

   public static SetY(value: number) : Point {
       return new Point(this.X, value);
   }
}

This gets you a key benefit of a complex value type, namely that you can't accidentally modify the object via a reference to it.

The drawback of course is that if you DO want to modify a member you have to make a new instance, hence the static SetX and SetY functions.

It's a lot of syntactic sugar but I think it's worth it for special cases, like Point, that could potentially get used A LOT and lead to A LOT of bugs if values are changed accidentally.

Peter Moore
  • 1,122
  • 10
  • 16