Why doesn't JavaScript ES6 support multi-constructor classes?

JavascriptClassConstructorEcmascript 6

Javascript Problem Overview


I want to write my Javascript class like below.

class Option {
    constructor() {
        this.autoLoad = false;
    }
    
    constructor(key, value) {
        this[key] = value;
    }

    constructor(key, value, autoLoad) {
        this[key] = value;
        this.autoLoad = autoLoad || false;
    }
}

I think it would be nice if we can write out class in this way. Expect to happen:

var option1 = new Option(); // option1 = {autoLoad: false}
var option2 = new Option('foo', 'bar',); // option2 = {foo: 'bar'}
var option3 = new Option('foo', 'bar', false); // option3 = {foo: 'bar', autoLoad: false}

Javascript Solutions


Solution 1 - Javascript

> I want to write my Javascript class like below

You can't, in the same way you can't overload standard functions like that. What you can do is use the arguments object to query the number of arguments passed:

class Option {
    constructor(key, value, autoLoad) {
        // new Option()
        if(!arguments.length) {
            this.autoLoad = false;
        }
        // new Option(a, [b, [c]])
        else {
            this[key] = value;
            this.autoLoad = autoLoad || false;
        }
    }
}

Babel REPL Example

Of course (with your updated example), you could take the approach that you don't care about the number of arguments, rather whether each individual value was passed, in which case you could so something like:

class Option {
    constructor(key, value, autoLoad) {
        if(!key) { // Could change this to a strict undefined check
            this.autoLoad = false;
            return;
        }
        this[key] = value;
        this.autoLoad = autoLoad || false;
    }
}

Solution 2 - Javascript

What you want is called constructor overloading. This, and the more general case of function overloading, is not supported in ECMAScript.

ECMAScript does not handle missing arguments in the same way as more strict languages. The value of missing arguments is left as undefined instead of raising a error. In this paradigm, it is difficult/impossible to detect which overloaded function you are aiming for.

The idiomatic solution is to have one function and have it handle all the combinations of arguments that you need. For the original example, you can just test for the presence of key and value like this:

class Option {
  constructor(key, value, autoLoad = false) {
    if (typeof key !== 'undefined') {
      this[key] = value;
    }
    this.autoLoad = autoLoad;
  }
}

Solution 3 - Javascript

Another option would be to allow your constructor to take an object that is bound to your class properties:

class Option { // Assign default values in the constructor object constructor({key = 'foo', value, autoLoad = true} = {}) { this.key = key; // Or on the property with default (not recommended) this.value = value || 'bar'; this.autoLoad = autoLoad;

      console.log('Result:', this);

} }

var option1 = new Option(); // Logs: {key: "foo", value: "bar", autoLoad: true}

var option2 = new Option({value: 'hello'}); // Logs: {key: "foo", value: "hello", autoLoad: true}

This is even more useful with Typescript as you can ensure type safety with the values passed in (i.e. key could only be a string, autoLoad a boolean etc).

Solution 4 - Javascript

Guessing from your sample code, all you need is to use default values for your parameters:

class Option {
    constructor(key = 'foo', value = 'bar', autoLoad = false) {
        this[key] = value;
        this.autoLoad = autoLoad;
    }
}

Having said that, another alternative to constructor overloading is to use static factories. Suppose you would like to be able to instantiate an object from plain parameters, from a hash containing those same parameters or even from a JSON string:

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

    static fromHash(hash) {
        return new this(hash.a, hash.b);
    }

    static fromJson(string) {
        return this.fromHash(JSON.parse(string));
    }
}

let thing = new Thing(1, 2);
// ...
thing = Thing.fromHash({a: 1, b: 2});
// ...
thing = Thing.fromJson('{"a": 1, "b": 2}');

Solution 5 - Javascript

Here's a hack for overloading based on arity (number of arguments). The idea is to create a function from a number of functions with different arities (determined by looking at fn.length).

function overloaded(...inputs) {
  var fns = [];

  inputs.forEach(f => fns[f.length] = f);

  return function() {
    return fns[arguments.length].apply(this, arguments);
  };
}

var F = overloaded(
  function(a)    { console.log("function with one argument"); },
  function(a, b) { console.log("function with two arguments"); }
);

F(1);
F(2, 3);

Of course this needs a lot of bullet-proofing and cleaning up, but you get the idea. However, I don't think you'll have much luck applying this to ES6 class constructors, because they are a horse of a different color.

Solution 6 - Javascript

you can use static methods,look at my answer to same question

class MyClass {
    constructor(a,b,c,d){
        this.a = a
        this.b = b
        this.c = c
        this.d = d
    }
    static BAndCInstance(b,c){
        return new MyClass(null,b,c)
    }
}

//a Instance that has b and c params
MyClass.BAndCInstance(b,c)

Solution 7 - Javascript

Use object.assigne with arguments with this

This={...this,...arguments}

Solution 8 - Javascript

Its not the overload I wanted, but this is a basic version of how I faked my way through creating an obj1 with some different initialization behavior. I realize I could have expanded the arguments as stated above, but I already had a nasty set of arguments and relatively different data sources to deconstruct that would have really distorted my objectives; this just made it cleaner for my situation...

class obj1{
  constructor(v1, v2){
    this.a = v1;
    this.b = v2;
  }
}

class obj1Alt{
  constructor(v1, v2){
    return new obj1(v1*2,v2*2);
  }
}

new obj1(2,4) // returns an obj1
new obj1Alt(2,4) // also returns an obj1

Disclaimer: I've been programming for a long time, but I am fairly new to JS; probably not a best practice.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionManhhailuaView Question on Stackoverflow
Solution 1 - JavascriptCodingIntrigueView Answer on Stackoverflow
Solution 2 - JavascriptBardi HarborowView Answer on Stackoverflow
Solution 3 - JavascriptGFoley83View Answer on Stackoverflow
Solution 4 - JavascriptDiogo EichertView Answer on Stackoverflow
Solution 5 - Javascriptuser663031View Answer on Stackoverflow
Solution 6 - Javascriptmahdi shahbaziView Answer on Stackoverflow
Solution 7 - JavascriptAzatView Answer on Stackoverflow
Solution 8 - JavascriptBogatitusView Answer on Stackoverflow