Sam Sam - 8 months ago 21
Javascript Question

ES6 classes : what about instrospection?

In ES5, I could check the existence of a "class" (constructor function) on the window object:

if (window.MyClass) {
... // do something
}


In ES6, according to this article, globally-declared classes are globals, but not properties of the global object (
window
, on browsers):


But there are now also global variables that are not properties of the global object. In global scope, the following declarations create such variables:


  • let
    declarations

  • const
    declarations

  • Class declarations




So if I can't use
if (window.MyClass)
, is there a way to do the same?

Actually is there a proper way to do this without using window object ?

Answer Source

In ES5, we could wheck the existence of a class on the window object

Only if the constructor function was a global, which is poor practice.

In ES6, according to this article, globally-declared classes are globals, but not properties of the global object...

Correct. (The same is true of let and const declarations at global scope.) This is defined in §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.

(My emphasis) So the things that used to go on the global object in ES5 and earlier still do (plus generators, because it would have been even more confusing if they didn't), but the new things (let, const, and class declarations) don't. They're globals, but not properties of the global object.

Back to your question...

So if I can't use if (window.MyClass), is there a way to do the same?

You could use

if (typeof MyClass === "function") {

...since typeof on an unresolvable symbol doesn't throw a ReferenceError. This also has the advantage of checking whether MyClass is in scope for the code, even if it's not global.

There's a gotcha there though: If that code is in the same scope where MyClass is declared via class (or let or const) but it's above MyClass in that scope, even the typeof check will throw a ReferenceError, because you can't access the binding it creates at all (not even with typeof) before the class (or let or const).

E.g., this will throw:

if (typeof MyClass === "function") {  // ReferenceError here
    // Yup, it's defined
    // ...
}
// ...
class MyClass {
}

The space from the beginning of the scope to the class, let, or const line is called the temporal dead zone (TMZ) and you can't access the variable binding at all. Consequently, you have to catch the ReferenceError:

let exists = false;
try {
    exists = typeof MyClass === "function";
} catch (e) {
}

Actually is there a proper way to do this without using window object ?

Until JavaScript modules make it to broad browser support, there are a couple of ways:

  1. Use an Asynchronous Module Definition library of some kind to handle loading your modules. Some examples: RequireJS, SystemJS, CommonJS

  2. Have a single global variable that you'll use to refer to an object, and make your various application globals properties of that object. Here's a typical way to do that:

    var MyApp = MyApp || {};
    if (!MyApp.ThisModule) {                  // You can leave this `if` out
                                              // if there's no chance of the file
                                              // being loaded more than once
        MyApp.ThisModule = function(module) {
            module.MyClass = class MyClass {
                // ...class definition here...
            }
        }({});
    }
    

This also gives you a handy scope (the anonymous function) in which to put any module-level globals.