56

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}
GFoley83
  • 3,439
  • 2
  • 33
  • 46
Manhhailua
  • 1,062
  • 3
  • 13
  • 33
  • 3
    "Why doesn't" is often a pretty unproductive question. Because, well, *it doesn't*. Many languages *don't*, ES6 is by far not the only one. – deceze Sep 17 '15 at 10:05
  • You don't need an overloaded constructor for that. Just declare `autoLoad` with a default. –  Sep 17 '15 at 10:13
  • 3
    possible duplicate of [method overloading in Javascript](http://stackoverflow.com/q/12694588/1048572) (it's no different in a `class` except that multiple definitions are a syntax error there) – Bergi Sep 17 '15 at 13:46
  • Because JavaScript didn't have and needed a concept like this so far (i.e. overloading). Why should `constructor` be the exception? – Felix Kling Sep 17 '15 at 13:54
  • Almost the same question: _Why don't JavaScript objects allow you to specify multiple values for one key?_ – Patrick Roberts Aug 15 '16 at 01:42
  • @deceze False. Usually asking a question like this may gave you either or both of: A. An understanding of why the language can't/shouldn't support what you think it should, or B. Good alternatives to support what you wanted to do. Good examples here: https://stackoverflow.com/a/32626901/1599699 https://stackoverflow.com/a/41051984/1599699 https://stackoverflow.com/a/48287734/1599699 – Andrew Mar 31 '20 at 17:51
  • With `static` may you don't have to think about `constructor` refer https://stackoverflow.com/a/74892429/3057246 to see how to use static to create two different instance of class. – Vinod Srivastav Jun 29 '23 at 05:54

8 Answers8

32

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;
    }
}
CodingIntrigue
  • 75,930
  • 30
  • 170
  • 176
  • 8
    Why are we so resolutely refusing to use parameter defaults? –  Sep 17 '15 at 10:15
  • 4
    @torazaburo *We* aren't. *I* am answering the question of how to emulate overloading. I may have missed some optimizations in other aspects of the original code, param defaults being one of what I assume could be multiple. – CodingIntrigue Sep 17 '15 at 10:18
  • It's generally not considered good practice to use the OR double pipe `||` to set default values. Especially since es6 supports it ootb. http://www.codereadability.com/javascript-default-parameters-with-or-operator/amp/ – GFoley83 Dec 14 '16 at 19:01
21

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;
  }
}
Bardi Harborow
  • 1,803
  • 1
  • 28
  • 41
  • 3
    If he's using `class`, he's using ES6. ES6 has parameter defaults. Use them. –  Sep 17 '15 at 10:14
  • 4
    @torazaburo: default values don't work well with variadic functions, though – Bergi Sep 17 '15 at 13:47
10

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).

GFoley83
  • 3,439
  • 2
  • 33
  • 46
10

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}');
Diogo Eichert
  • 437
  • 6
  • 17
  • 1
    Nice one. . . . – Andrew Mar 31 '20 at 17:54
  • 1
    Wouldn't it better to use `new Thing()` here though rather than `new this()`, since that would be safer (`this` could change meanings depending on what's in the static functions) and clearer? – Andrew Mar 31 '20 at 18:05
  • @Andrew I have to disagree, using `this` here makes the code agnostic to the class it belongs to, more portable and less redundant, as the class is named only once within its definition. It should also avoid issues in case the class is extended. – Diogo Eichert Apr 08 '20 at 07:57
  • 1
    I suppose it really depends on how you're using it. The static constructor functions in your example are rather generic, whereas the way I'm using it the functions are quite specific to the class. I'm not sure I consider making code agnostic to a class superior, I would just use refactoring in that case. It may require less change with `this`, but using the class name also forces the developer to notice that the class name is being used there, if they try to change things, which is okay because it's all contained within the class's definition. – Andrew Apr 08 '20 at 17:38
  • 1
    @Andrew I suppose so, I can see pros and cons in both approaches. – Diogo Eichert Apr 08 '20 at 21:29
6

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.

  • Not really recommending it. But out of curiosity, why would you say it's slow, and how do you define "idiomatic"? –  Sep 17 '15 at 15:40
  • 2
    @BardiHarborow Thanks for your reply. I have modified my answer to say "hack" instead of "general solution". –  Sep 17 '15 at 17:20
  • I've been using something akin to this for years without issue. While it adds tax to the call, it improves the read. Look at many of the jQuery source functions and you'll see that for many the first 5-20 lines are rearranging args according to the intended overload of the call -- fast but a horrible read. – Mario Jun 29 '18 at 01:03
1

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)
mahdi shahbazi
  • 1,882
  • 10
  • 19
0

Use object.assigne with arguments with this

This={...this,...arguments}
Marcello B.
  • 4,177
  • 11
  • 45
  • 65
Azat
  • 1
0

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.

Bogatitus
  • 21
  • 1
  • 5