3

When you're writing ES6 classes you can use destructuring in the constructor to make it easier to use default options:

class Person {
    constructor({
        name = "Johnny Cash",
        origin = "American",
        profession = "singer"
    } = {}) {
        this.name = name;
        this.origin = origin;
        this.profession = profession;
    }

    toString() {
        return `${this.name} is an ${this.origin} ${this.profession}`;
    }
}

This allows you do to things like this:

const person = new Person();
console.log(person.toString());
// Returns 'Johnny Cash is an American singer'

const nina = new Person({
    name : "Nina Simone"
})
console.log(nina.toString());
// Returns 'Nina Simone is an American singer'

However, you need to repeat the arguments in the constructor to assign them to the class instance. I already found out you can do this to make it less verbose:

Object.assign(this, { name, origin, profession });

But you still need to repeat those three variables. Is there any way to make the assignment even shorter without repeating the variables?

0

3 Answers 3

3

You can just have your constructor take one "config" parameter, and then say Object.assign(this, config). To have defaults, you can just set up a default object, or assign them outside the constructor as class properties.

EDIT To T.J. Crowder's point, technically anything passed in while using Object.assign() will be added to your object, which could be harmful in a number of ways. I have added a way to strip the config object to only contain a set of properties that you define. T.J.'s solution for this will work as well, I just did it differently so you have options.

function stripObjToModel(model, obj) {
  // this method will modify obj
  Object.keys(obj).forEach(function (key) {
    if (!model.hasOwnProperty(key)) {
      delete obj[key];
    }
  });
  return obj;
}
class Person {
    constructor(config = {}) {
        var defaults = {
            name: "Johnny Cash",
            origin: "American",
            profession:  "singer"
        };
        Object.assign(this, defaults, stripObjToModel(defaults, config));
    }

    toString() {
        return `${this.name} is an ${this.origin} ${this.profession}`;
    }
}

const person = new Person();
console.log(person.toString());
// Returns 'Johnny Cash is an American singer'

const nina = new Person({
    name : "Nina Simone",
    foo: "bar",
    toString: function () {return "Hello World!";}
})
console.log(nina.toString());
// Returns 'Nina Simone is an American singer'

Sign up to request clarification or add additional context in comments.

14 Comments

@T.J.Crowder Yeah, that was my mistake - it has been fixed
What bothers me about an Object.assign solution like this is that all properties from the options object are copied onto this, whether they're name, origin, profession, or something else (could even be toString).
@T.J.Crowder Yeah, maybe I misspoke - I think a better word instead of "applied" would be "given priority"
@Husky Yeah, I think that's the general consensus - your original code is fine. It's a tad verbose, but I don't think there's a good alternative.
@T.J.Crowder Looking at the spec, you're right. Mechanically, it's done from left to right.
|
1

Not that I'm advocating it, but you can technically do

constructor(options = {}) {
  ({
    name: this.name = "Johnny Cash",
    origin: this.origin = "American",
    profession: this.profession = "singer",
  } = options);
}

Personally I think your example code with a bit of repetition looks totally fine.

10 Comments

I get an Unexpected token this when i try running that...
LOL - still repeats the names, though. :-) And you couldn't in a subclass, this isn't available in the parameters scope of a subclass constructor.
@Husky: Try it now (Logan had : where he meant =, amusingly the opposite of the mistake I made in my answer that he corrected...).
I guess this solution is also a bit moot because it's repeating the variable names again.
Logan - Yeah. Like you, I think @Husky's original code is the best way to go. Simple, clear, and not too much repetition.
|
0

Sadly, I don't think there's any built-in way to accept an options object with individual defaults and grab only those properties from the object you receive (and not other alien properties that may also be specified) without repeating yourself. This is unfortunate. (It's also not the first time I've wanted the destructuring and a reference to the object being destructured.)

So that means we have to go out on our own, but it's simple enough:

function applyDefaults(target, options, defaults) {
    for (const key in defaults) { // Or `const key of Object.keys(defaults)` for just own props
        target[key] = (typeof options[key] === undefined ? defaults : options)[key];
    }
}

then:

constructor(options = {}) {
    applyDefaults(this, options, {
        name: "Johnny Cash",
        origin: "American",
        profession: "singer"
    });
}

(If recreating the object on every constructor call worries you, you could move that out of the class, but I wouldn't worry about it.)

But then the individual property defaults aren't described in the method signature, which for me would be strong enough reason to go ahead and repeat the names as you have it now.

2 Comments

Yes, this is a nice solution as well, and probably how i would have handled it if i couldn't write ES6. But maybe repeating the properties is not so bad as i initially thought: at least it is explicit.
@Husky: Yeah, I'd stick with what you have. But if you want an alternative, this is short and to the point, and only lists the names once. ;-)

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.