Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

A shorter class initialisation in ECMAScript 6

Every time I create some class, I need to do the same boring procedure:

class Something {
  constructor(param1, param2, param3, ...) {
    this.param1 = param1;
    this.param2 = param2;
    this.param3 = param3;
    ...
  }
}

Is there any way to make it more elegant and shorter? I use Babel, so some ES7 experimental features are allowed. Maybe decorators can help?

like image 420
troorl Avatar asked Oct 25 '15 09:10

troorl


People also ask

Are there classes in ES6?

There are two types of Class in ES6: parent class/super class: The class extended to create new class are know as a parent class or super class. child/sub classes: The class are newly created are known as child or sub class. Sub class inherit all the properties from parent class except constructor.

What is an ES6 class?

ES6 Classes formalize the common JavaScript pattern of simulating class-like inheritance hierarchies using functions and prototypes. They are effectively simple sugaring over prototype-based OO, offering a convenient declarative form for class patterns which encourage interoperability.

Which JavaScript feature is the alternative to the ES6 classes the class keyword )?

The function keyword is replaced with the class keyword. There's a special function named 'constructor' where the initialization of the object is done.

Should I use class or prototype?

The most important difference between class- and prototype-based inheritance is that a class defines a type which can be instantiated at runtime, whereas a prototype is itself an object instance.

What is new in ECMAScript 6?

ECMAScript 6 introduces the concept of class available in traditional object-oriented languages. In ECMAScript 6, the class syntax is syntactical sugar on top of the existing prototype-based inheritance model. It does not add a new object-oriented inheritance model to JavaScript.

What happened to the semantics of classes in ECMAScript 6?

Recently, TC39 decided on the final semantics of classes in ECMAScript 6 [1]. This blog post explains how their final incarnation works. The most significant recent changes were related to how subclassing is handled. Overview  #

Can I subclass array in ECMAScript 5?

In ECMAScript 5, most built-in constructors can’t be subclassed (several work-arounds exist). To understand why, let’s use the canonical ES5 pattern to subclass Array. As we shall soon find out, this doesn’t work.

How do I subclass a point in ECMAScript 6?

In ECMAScript 6, subclassing looks as follows. classPoint{ constructor(x, y) { this.x = x; this.y = y; } ··· } classColorPointextendsPoint{ constructor(x, y, color) { super(x, y); this.color = color; } ··· } letcp = newColorPoint(25, 8, 'green'); This code produces the following objects.


3 Answers

You can use Object.assign:

class Something {
  constructor(param1, param2, param3) {
    Object.assign(this, {param1, param2, param3});
  }
}

It's an ES2015 (aka ES6) feature that assigns the own enumerable properties of one or more source objects to a target object.

Granted, you have to write the arg names twice, but at least it's a lot shorter, and if you establish this as your idiom, it handles it well when you have arguments you do want on the instance and others you don't, e.g.:

class Something {
  constructor(param1, param2, param3) {
    Object.assign(this, {param1, param3});
    // ...do something with param2, since we're not keeping it as a property...
  }
}

Example: (live copy on Babel's REPL):

class Something {
  constructor(param1, param2, param3) {
    Object.assign(this, {param1, param2, param3});
  }
}
let s = new Something('a', 'b', 'c');
console.log(s.param1);
console.log(s.param2);
console.log(s.param3);

Output:

a
b
c
like image 165
T.J. Crowder Avatar answered Oct 14 '22 21:10

T.J. Crowder


Unfortunately, all you can do are simple things like Object.assign, but if you're trying to remove the redundancy of typing all the params twice (once in constructor signature and once in assignment) there isn't much you can do.

That said, you could do a hack like this. Though I'm not sure the effort and modicum of obfuscation that comes with it is worth it.

var dependencies = ['param1', 'param2', 'param3'];

class Something {
  constructor(...params) {
    params.forEach((param, index) => this[dependencies[index]] = param);
  }
}

var something = new Something('foo', 'bar', 'baz');
// something.param1 === 'foo'

This way you're using a single array of argument names, then using that same array as a reference when creating the properties on your instance of Something. This pattern would work well in an Angular application where you're trying to preserve dependency names through minification by setting the $inject property.

Something.$inject = dependencies;

PS - Welcome to the redundant hell of classical languages that I thought I got away from when I became a JS developer :P

Honestly, you should probably just use a classic object literal unless you really need the formality of an actual class.

Edit: I suppose you could accept an object literal in your constructor if you want the ease of a literal and the formality of an actual class.

class Something {
  constructor(params) {
    Object.keys(params).forEach((name) => this[name] = params[name]);
  }
}

var something = new Something({
  param1: 'foo',
  param2: 'bar',
  param3: 'baz'
});

But now you've just turned a class into a dynamic class that can be instantiated with any properties, kinda like an object literal :P

Usually I want a class because I want to formalize the object and present a consistent and strictly testable API.

like image 29
Chev Avatar answered Oct 14 '22 21:10

Chev


We could create a static method within each class that takes the arguments object and an array of names and returns an object that can be assigned to the new instance using Object.assign.

Check it out using the Babel REPL.

class Something {
  static buildArgs (ctx, args, paramNames) {
    let obj = {}
    Array.from(args).forEach(function (arg, i) {
      let name = paramNames[i] || i
      obj[name] = args[i]
    })
    Object.assign(ctx, obj)
  }

  constructor () {
    Something.buildArgs(this, arguments, [
      'param1',
      'param2'
    ]);
    console.log(this)
  }
}

new Something('one', 'two')

Admittedly the addition of a method buildArgs means that this solution is not shorter, however the body of the constructor is and we also have these advantages:

  1. You are only writing the parameter names once.
  2. You are protected against minification.

The code above accommodates extra arguments (i >= paramNames.length) however we could modify it if this behaviour is undesirable such that these are still parsed, but not assigned to the instance:

class Something {
  static buildArgs (ctx, args, paramNames) {
    let obj = {instance: {}, extra: {}}
    Array.from(args).forEach(function (arg, i) {
      let name = paramNames[i] || i
      if (name) {
          obj.instance[name] = args[i]
      } else {
          obj.extra[i] = args[i]
      }
    })
    Object.assign(ctx, obj)
  }

  constructor () {
    let args = Something.buildArgs(this, arguments, ['param1', 'param2']);
    // Do stuff with `args.extra`
  }
}

Or ignored altogether:

  static buildArgs (args, paramNames) {
    let obj = {}
    Array.from(args).forEach(function (arg, i) {
      let name = paramNames[i]
      if (name) obj[name] = args[i]
    })
    return obj
  }
like image 44
sdgluck Avatar answered Oct 14 '22 21:10

sdgluck