2

Before I proceed, I would like to point out that I have asked a few questions already regarding TypeScript, its compiler and what it has and has not been able to achieve throughout its life and roadmap towards version 1.0

This question relates to the use of public and private keywords in TypeScript, and how these relate to compiled JavaScript.

Consider the following TypeScript class:

class Example {
    private messageA: string;
    public messageB: string;

    constructor(message?: string) {
        this.messageA = "private: " + message;
        this.messageB = "public: " + message;
    }

    public showMessageA(): void {
        alert(this.messageA);
    }

    private showMessageB(): void {
        alert(this.messageB);
    }
}

var example = new Example("Hello World");

Now, when I type example. intellisense (TypeScript) tells me that I can access messageB, and showMessageA, because they are both public. However, this behavior (whilst possible) is not evident in the compiled JavaScript.

Here is the JavaScript compilation for my class:

var Example = (function () {
    function Example(message) {
        this.messageA = "private: " + message;
        this.messageB = "public: " + message;
    }
    Example.prototype.showMessageA = function () {
        alert(this.messageA);
    };

    Example.prototype.showMessageB = function () {
        alert(this.messageB);
    };
    return Example;
})();

var example = new Example("Hello World");

Now, if I paste this example into my browser console (I'm using Chrome), I can access messageA, messageB, showMessageA, showMessageB which implies that in JavaScript, all access modifiers are ignored.

Personally, I think this is wrong! JavaScript is capable of modelling access modifiers so it is my belief that TypeScript should follow suit.

Consider the following hand written JavaScript, which models the private and public variables and functions correctly:

var Example = (function() {
    return function Example(message) {
        var messageA = "private: " + message;
        this.messageB = "public: " + message;

        this.showMessageA = function() {
            alert(messageA);
        }

        var showMessageB = function() {
            alert(this.messageB);
        }
    }
})();

var example = new Example("Hello World");

Now, if I paste this example into my browser console, I can only access messageB and showMessageA, which in accordance with what I was trying to achieve with TypeScript is correct.

QUESTIONS

  1. Why does the TypeScript compiler ignore access modifiers when compiling to JavaScript?
  2. Why does TypeScript bind all methods to the prototype, rather than on a per-instance basis?
  3. If there is anything favorable about the way TypeScript compiles classes in comparison to my custom implementation, what is it, and why?
DCoder
  • 12,962
  • 4
  • 40
  • 62
Matthew Layton
  • 39,871
  • 52
  • 185
  • 313
  • 2
    1) already addressed 2) and 3) design decision by language authors. TypeScript is all about the developer experience, and as such, if you use TypeScript, you won't have called any private functions as the compiler would have prevented it. No closure is necessary for the `prototype` way. – WiredPrairie Dec 30 '13 at 11:51

2 Answers2

5

The problem with using a closure to imitate private access is that every instance needs its own copy of each method. This means that every time you create an instance, each method function has to be compiled and space in memory has to be reserved for the new function. That is not ideal, and not what TypeScript is trying to achieve.

sbking
  • 7,630
  • 24
  • 33
  • Cool, that makes things clearer. Okay looking at this in terms of other runtimes like .NET and JVM, I'm assuming that your statement: "each method function has to be compiled and space in memory has to be reserved for the new function" is true for these languages? – Matthew Layton Dec 30 '13 at 12:24
  • 3
    There is a big difference between compiled, class-based languages and interpreted, prototype-based languages. In JavaScript, we save memory by storing methods on a prototype object, and all objects with that prototype call the same function in memory, just with a different context. Using a closure to imitate privacy means you're not taking advantage of prototypes, and each object has its own copy of each method. Class-based languages do not use prototypes and do not have JavaScript's concept of context, so it is difficult to make an analogy. – sbking Dec 30 '13 at 12:58
  • It is not correct that every instance would require each method to be recompiled. It merely would require allocating a fresh closure, i.e., function object -- still bad enough, but not as bad. – Andreas Rossberg Dec 30 '13 at 14:45
  • @AndreasRossberg Interesting, for some reason I was under the impression that function objects were compiled on every instantiation. – sbking Dec 30 '13 at 14:58
  • Oh, and these problems would _not_ apply to C# or Java languages. In their object models, you cannot "extract" methods (i.e., perform this-stealing) like in JavaScript. So instead of allocating separate closures, you could put all closure environments as hidden state on the new object itself (which can never be disassociated from the methods like in JavaScript). – Andreas Rossberg Dec 30 '13 at 14:58
3

Semantically, the methods-as-closures model you describe is, in many ways, superior to JavaScript's model of putting methods on the prototype -- even for public methods. And some people on the EcmaScript 6 committee (on whose class system TypeScript is based), would have preferred a class system along these lines. In fact, early internal versions of TypeScript implemented such a model.

Unfortunately, such a model cannot be made efficient in JavaScript. It generally would require that every method is allocated as a separate closure for every object instance -- i.e., creating a single object instance would involve creating many function objects. And with JavaScript's saddeningly weak treatment of this, and its notion of function identity, there is no easy way in which these allocations could be optimised away in general.

Consequently, EcmaScript 6 settled on the current model (though it has only public for now), and TypeScript followed regretfully (for both public and private). Private members then merely provide static checks in TypeScript, with no encapsulation guarantees.

FWIW, the proper way to provide privates in this model would be private symbols (a.k.a. private names), but unfortunately, those didn't make it into ES6, and are no option for TypeScript anyway.

Andreas Rossberg
  • 34,518
  • 3
  • 61
  • 72
  • Another great answer, thank you. Unfortunately, once again throwing a spanner in the works of what I was trying to achieve, but better I know now rather than further down the line where I would have 100s of 1000s of lines of code to refactor! SIGH! – Matthew Layton Dec 30 '13 at 14:49
  • @AndreasRossberg could you provide some insight into why a class system is preferable to a proper prototype system? It seems like optimizations to `Object.create()` could allow inheritance to be done in a purely prototype-based way that would be more expressive and flexible than classical inheritance. As of now it is simply not an option as the `new` operator is orders of magnitude faster. – sbking Dec 30 '13 at 14:57
  • 1
    @Cuberto, difficult in a comment. Just 2 remarks. First, classes are not important, but rather how `this` is handled: as a random method argument or as a definite reference. The latter provides much stronger invariants, and hence better encapsulation and optimisation characteristics. A true prototype language (where instances actually are clones) could do the latter, but not JavaScript's more hacky notion of prototype. Second, "flexible" isn't always better -- expressive power is not just about what your code can do, but also, dually, what your code can prevent other code from doing. – Andreas Rossberg Dec 30 '13 at 15:56