58

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}
7
  • 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. Commented Sep 17, 2015 at 10:05
  • You don't need an overloaded constructor for that. Just declare autoLoad with a default. Commented Sep 17, 2015 at 10:13
  • 3
    possible duplicate of method overloading in Javascript (it's no different in a class except that multiple definitions are a syntax error there) Commented Sep 17, 2015 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? Commented Sep 17, 2015 at 13:54
  • Almost the same question: Why don't JavaScript objects allow you to specify multiple values for one key? Commented Aug 15, 2016 at 1:42

8 Answers 8

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;
    }
}
Sign up to request clarification or add additional context in comments.

3 Comments

Why are we so resolutely refusing to use parameter defaults?
@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.
It's generally not considered good practice to use the OR double pipe || to set default values. Especially since es6 supports it ootb. codereadability.com/…
22

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;
  }
}

2 Comments

If he's using class, he's using ES6. ES6 has parameter defaults. Use them.
@torazaburo: default values don't work well with variadic functions, though
12

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}');

5 Comments

Nice one. . . .
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 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.
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 I suppose so, I can see pros and cons in both approaches.
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).

Comments

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.

3 Comments

Not really recommending it. But out of curiosity, why would you say it's slow, and how do you define "idiomatic"?
@BardiHarborow Thanks for your reply. I have modified my answer to say "hack" instead of "general solution".
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.
2

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)

Comments

0

Use object.assigne with arguments with this

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

Comments

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.