25

I have come across an interesting issue, at least I think it's interesting, and a little annoying. I have a class, for this question I will keep it extremely simple...

class Foo {
    static pageChange() {
        console.log('The page changed');
    }
}

Now, I can access that with Foo.pageChange() no problem, everything works as expected. The hard part, and interesting bit comes in when I try to access this dynamically. I have a separate object that monitors events and handles dispatching them as needed. This pertains to the Google visualization library, where I have a table, and the table has events tied to it. I have an object that is responsible for creating all of this from PHP output. It is one heck of a system, in an easy explanation you can do something like this in PHP...

GoogleVisLibrary::renderChart(
    array(
        'chartType' => 'table',
        'chartData' => $this->chartData,
        'chartOptions' => $this-chartOptions,
        'events' => array(
             'sort' => 'Foo.pageChange'
        )
);

now that will create teh table and all that good stuff. The problem is accessing that static method in the Foo class in javascript. What I had prior to creating the Foo as a class was this.

var Foo = {
    pageChange: function() {
         console.log('page changed');
    }
}

then in my event library handler thingy it would look something like this..

for(var i = 0, l = events.length; i < l; i++) {
    window[events.className][events.fnName].apply();
}

and that would work fine because Foo can be accessed through window['Foo'] but when you use the class definition shown in the first code snippet, you can no longer access it from the window super global, it just outputs 'undefined'.

So, is there any way to access a static method in a class, through a dynamic reference like you can with the Foo object through the window global?

I am hoping that makes sense and I am explaining it correctly. If anyhting doesn't make sense please feel free to ask, I will try to explain better. Thank you in advance for any help you can give.

Neglected Sanity
  • 1,770
  • 6
  • 23
  • 46
  • 1
    Possible duplicate of either [ES6 classes: what about instrospection?](http://stackoverflow.com/q/30321636/1048572) or [Do `let` statements create properties on the global object?](http://stackoverflow.com/q/28776079/1048572)? – Bergi Jun 08 '16 at 19:52
  • Why would `events` contain the class name? Just let them directly contain a reference to the class itself. – Bergi Jun 08 '16 at 19:53
  • Related: [What is the “best” way to set a global variable in JavaScript?](http://stackoverflow.com/q/3352020/218196) – Felix Kling Jun 09 '16 at 02:49

2 Answers2

24

To get a reference on the window object, you'll need to do that explicitly:

window.Foo = class Foo { ... }

Read more about classes not being properties of the window object in this answer, which also quotes the ECMA2015 Specification, Section 8.1.1.4: Global Environment Records:

A global Environment Record is logically a single record but it is specified as a composite encapsulating an object Environment Record and a declarative Environment Record. The object Environment Record has as its base object the global object of the associated Realm. This global object is the value returned by the global Environment Record’s GetThisBinding concrete method. (E.g., the global object referenced by window on browsers — T.J.) The object Environment Record component of a global Environment Record contains the bindings for all built-in globals (clause 18) and all bindings introduced by a FunctionDeclaration, GeneratorDeclaration, or VariableStatement contained in global code. The bindings for all other ECMAScript declarations in global code are contained in the declarative Environment Record component of the global Environment Record.

Using a dedicated object

It would be better not to use the global object for this, and dedicate a specific object to contain your classes and base your event managing library on that object, as illustrated in this simplified snippet:

(function () {
    var classes = {
        Foo: class Foo {
            static pageChange() {
                console.log('The page changed');
            }
        }
    }

    /////////////
    var events = [{
        className: 'Foo',
        fnName: 'pageChange'
    }];
    for(var event of events) {
        classes[event.className][event.fnName].apply();
    }
}());
Community
  • 1
  • 1
trincot
  • 317,000
  • 35
  • 244
  • 286
  • 1
    Can you also suggest a better solution that to pollute the global scope? – Bergi Jun 08 '16 at 20:10
  • Sure, but OP's *event library handler thingy* is based on `window`, and I understood the question to be *...through the window global?*. Maybe I misunderstood? – trincot Jun 08 '16 at 20:14
  • No, you understood the question correctly, I just wanted you to add something like "…and while you're at explicitly assigning the class to somewhere, better don't use the global namespace but a dedicated object containing the classes you want to work with your events". I assumed the library thingy could be changed as well. – Bergi Jun 08 '16 at 20:21
  • @Bergi, OK, I see. I added a section on that. – trincot Jun 08 '16 at 20:36
  • I can see something like this working. I think the hardest part was, I was actually developing this as a library for other developers in my company to use. The majority of them are not super strong in JS anf would just want to have some simple function pageChange() {} in a JS file. While I built mine into a class. so I was hoping to find a solution that would work for both programming styles, since window[fnName].apply() would work for them, but I prefer to have a class and have the methods defined that way. – Neglected Sanity Jun 08 '16 at 21:09
  • I'd argue that `window.Foo = class Foo {};` is explicit. `Foo = ....` will throw in strict mode anyway. – Felix Kling Jun 09 '16 at 02:47
4

I got the same problem myself for quite a while, and I finally discovered a very simple solution : using eval.

Create your class :

class Foo {
  static pageChange() {...}
}

Get it dynamically :

eval('Foo');   // return the Foo constructor
eval('Foo').pageChange;   // return the pageChange static method
eval('Foo')['pageChange'];  // same

I dislike eval, but in this case it is as simple as fast. And it's the only way I found without adding the class to window.

(I also tried (new Function("return Foo;"))() and it appears eval is 2-3 times faster)

Gin Quin
  • 966
  • 7
  • 15