Why class name can be referenced inside class even after it was redefined?

Viewed 112

The following as you'd expect doesn't work:

let User = {
    foo() {
        User.prop = 1;
    }
};

let User2 = User;
User = null;

User2.foo();  // Cannot set property of null
console.log(User2.prop);

This works, though:

class User {
    static foo() {
        User.prop = 1;
    }
}

let User2 = User;
User = null;

User2.foo();
console.log(User2.prop);  // 1

Since functions and classes are objects, and in both cases I set a property for it, why is it that the results differ? Where is it getting User reference?

3 Answers

Similar to named function expressions, classes are wrapped in an extra scope that contains an immutable binding with their name and value.

If we desugar the class syntax to ES5 faithfully, we'd get something like

let User = (() => {
    const User = function() {};
//  ^^^^^^^^^^
    User.foo = function() {
        User.prop = 1;
    };
    return User;
})();

let User2 = User;
User = null;

User2.foo();
console.log(User2.prop);  // 1

This inner User declaration is the one that the foo method is closing over. Overwriting the outer variable doesn't matter to it.

Inside the declaration of a class, the name of that class is permanently bound. This is just one of the distinctions between old-school constructor-function classes and proper ES2015 classes. (Another important distinction that functions of a given name can be safely redeclared, but classes of the same name cannot.)

In second example you get 1 because are referring to a static variable even it is inside a class that is not instantiated before use it. Removing the static and instantiating correctly it return 1 anyway:

class User {
    foo() {
        this.prop = 1;
    }
}

let User2 = new User();
User = null;

User2.foo();
console.log(">>" + User2.prop);  // 1

Note User = null is irrelevant.

Related