镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

super() with an options object (possibly a FAQ) #7644

Closed

Description

(Apologies if this is known, but I search a bunch of places and didn't find anything.)

Is there a good way to deal with a constructor of a derived class that is getting an options kind of value that needs to be merged with a defaults value? I started with something like this:

export class Foo extends Bar {
    ...
    private options: any = {};
    ...
    constructor(x: Something, y: SomethingElse, options: any = {}) {
        this.options = $.extend({}, defaults, options);
        super(this.options.blah);
        ...
    }
}

This failed and I eventually resorted to:

    constructor(x: Something, y: SomethingElse, options: any = {}) {
        super(options.blah || defaults.blah);
        this.options = $.extend({}, defaults, options);
        ...
    }

which is duplicating functionality (and sloppy).

Hopefully I'm not the only one who ran into this problem and there's good way to do that?

Activity

  1. Arnavion commented on Mar 22, 2016

    @Arnavion

    The assignment to this.options before calling super() is illegal per ES6 semantics, so that can't be done anyway.

    You could assign to a temporary (eg back to options), do the super() call, then assign options to this.options

    constructor(x: Something, y: SomethingElse, options: any = {}) {
        options = $.extend({}, defaults, options);
        super(options.blah);
        this.options = options;
    }

    ... but that still hits the error that the super() call needs to be first.

    I've worked around a similar issue (needed to swap derived class constructor parameters before calling base class constructor to maintain backward compatibility) by abusing an IIFE as the super() parameter.

    constructor(x: Something, y: SomethingElse, options: any = {}) {
        super((() => {
            options = $.extend({}, defaults, options);
            return options.blah;
        })());
        this.options = options;
    }

    Edit: Since you're always assigning to this.options in the constructor, you can remove the initialiazer for the class member options. Then the first alternative I gave will also compile.

  2. elibarzilay commented on Mar 23, 2016

    @elibarzilay
    ContributorAuthor

    The problem is that all of these are ugly hacks, and it looks very strange that the usual facility for keyword arguments thing cannot work in such constructors.

    BTW, I couldn't get your hack to work, perhaps the TS version I'm using is too old. But if we're talking about such workarounds, then here's another ugly hack that works around it (based on an old Lisp hack of abusing optional arguments similarly):

    constructor(x: Something, y: SomethingElse, _options: any = {},
                options: any = $.extend({}, Constants, _options)) {
        super(options.blah);
        this.options = options;
        ...
    }
    

    In any case, I find it surprising that there is no proper way to do this, given that it's a common enough JS pattern that it's been embraced as the proper way to do keyword arguments.

  3. RyanCavanaugh commented on Mar 23, 2016

    @RyanCavanaugh
    Member

    Not 100% sure what the best approach is, but since this is really an ES6 thing (must invoke super before using this) you might find some good answers on Stack Overflow by just posting under the [javascript] tag

  4. elibarzilay commented on Mar 23, 2016

    @elibarzilay
    ContributorAuthor

    Ryan Cavanaugh (@RyanCavanaugh), so I looked at it more, and I still think that there's a specific TS issue here. I can understand where the ES6 restriction is coming from, but trying it via TS still seem weird.

    I dug some more, and it looks like usually initialized properties are handled after the super() call, except when there is some expression before the super, and even when that expression doesn't try to access this. Here's an example that shows this:

    class Bar { private x; constructor(x: any) { this.x = x; }}
    
    class Foo extends Bar {
        private mu = 9999;
        private options;
        constructor(x: string, y: number, options: any = {}) {
            // options = [123, options];
            super(options.blah);
            this.options = options;
        }
    }
    

    This works fine, and the output has this.mu = 9999; after the super call. Now uncomment the options setting line, and for some reason the mu initialization moves up to before the the super call. Is that intentional?

    As a side note, I also think that it'll be good to add it to your FAQ since people will probably do what I did, and look for the error message. So a quick entry saying that it's an ES6 issue would be better than nothing...

  5. Arnavion commented on Mar 24, 2016

    @Arnavion

    Eli Barzilay (@elibarzilay) I did say

    Since you're always assigning to this.options in the constructor, you can remove the initialiazer for the class member options. Then the first alternative I gave will also compile.

  6. elibarzilay commented on Mar 24, 2016

    @elibarzilay
    ContributorAuthor

    Arnav Singh (@Arnavion), see the example in my reply to Ryan Cavanaugh (@RyanCavanaugh) above: the problem is that if there is any initialized member TS does the assignment before the super call. In my case it's obviously ok to drop the initialization of options, but there are other members that are initialized.

  7. locked and limited conversation to collaborators on Jun 19, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    QuestionAn issue which isn't directly actionable in code

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions