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?
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.
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.
The function keyword is replaced with the class keyword. There's a special function named 'constructor' where the initialization of the object is done.
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.
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.
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 #
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.
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.
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
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.
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:
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
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With