Private Variables

Topics: Language Specification
Oct 2, 2012 at 8:40 AM

The following TypeScript allows the variable to be treated as private in TypeScript but doesn't result in a scoped variable in JavaScript:

class MyClass {
	private example: string;
    constructor() { 
		this.example = "Test";
	}
}

Results in:

var MyClass = (function () {
    function MyClass() {
        this.example = "Test";
    }
    return MyClass;
})();

It would be great if private really meant private - for example, but resulting in the following JavaScript:

var MyClass = (function () {
    var example;
    function MyClass() {
        example = "Test";
    }
    return MyClass;
})();
This means that in plain JavaScript, example is still private as it is scoped.

Developer
Oct 2, 2012 at 12:49 PM

The problem with using constructor function local variables for private storage is that they can't be accessed from functions on the prototype object (which is what methods on a class become in the generated JavaScript). Instead you need to create local function objects and that consumes a lot more memory. It would look something like this:

class StringBox {
    getValue: () => string;
    constructor (value: string) {
        this.getValue = () => value;
    }
}

As you can see, you need to create one function object per instance instead of a single function object shared by all instances through the prototype.

Anders

Oct 3, 2012 at 9:17 AM

Hi Anders,

That is a compelling reason to leave it as it is.

This is really just an education issue for people writing libraries with TypeScript that are being consumed from vanilla JavaScript.

Thanks

Steve

Oct 3, 2012 at 1:30 PM
Edited Oct 3, 2012 at 1:31 PM

It makes sense to use the underscore convention for "private" variables.  So that this:

 

 

class MyClass {
  private foo: string;

  constructor() {
    this.foo = "bar";
  }
}

 

Will result in this JavaScript:

 

 

var MyClass = (function() {
  function MyClass() {
    this._foo = "bar";
  }

  return MyClass;
})();

 

JavaScript developers are familiar with this convention and are aware that an underscore property/function is use-at-your-own-peril.

Oct 3, 2012 at 5:43 PM

Agree. Private members should be at least somehow distinguished from public members.

Oct 20, 2012 at 7:50 PM
Edited Oct 20, 2012 at 8:36 PM

I wanted to give my two cents on this. With regards to the criticism above... that is talking about accessing inherited private members (which is a protected scope, not a private scope).

If we are talking solely about a private scope and the point of TypeScript is to make javascript suitable for "application scale projects" I don't see why you would dismiss or minimize the importance of private scope at runtime. It can save a lot of grief when working with other peoples code!

Consider a scenario where you write a redistributable framework in TypeScript but consumers write javascript code against the compiled library. To me that feels like the most likely TypeScript usage scenario, where a few core maintainers work with TypeScript and the consumers largely do not.

Furthermore, why use a coding convention when it is technically possible to simulate private scope in javascript, by taking advantage of javascript's closure scope. 

function SomeConstructor() {
     var _private = "private";

     this.getPrivate = function () {
          return _private;
     }
}

TypeScript simulates all kinds of other abstractions that aren't explicitly part of javascript (like class based inheritance), why not simulate runtime private scope as well? All the technical means to do it are part of ES standards.

Oct 22, 2012 at 6:47 AM
Edited Oct 22, 2012 at 6:52 AM

I'd better give my "plus one" to the topic starter. I'm not quite sure I understand why everybody try to declare private variables inside the constructor?! We have a perfect closure as a class body provided by compiler. I don't see no probrem at all to have all our privates compiled just like this:

 

var MyClass = (function () {
    var example;
    function MyClass() {
        example = "Test";
    }
    return MyClass;
})();

In this case they are perfectly accessable from any prototype function defined in the scope of the closure (and this is exectly how the js code generated, btw). No need of any unnecessery "internal" getters-setters-helper-functions to reach them (as it was mention in some similiar thread here).

The only purpose of the way it's implemented now is variables with sort of protected access in case of inheritance.

Correct me if I miss the point somewhere.

 


Oct 23, 2012 at 11:53 AM
var MyClass = (function () {
    var example;
    function MyClass(value) {
        example = value;
    }
    MyClass.prototype.getExample = function() {
        return example
    }
    return MyClass;
})();

var test1 = new MyClass("test1");
var test2 = new MyClass("test2");

console.log(test1.getExample()) //test2
console.log(test2.getExample()) //test2

Here is the problem with what you described hyperartist

Oct 23, 2012 at 2:42 PM

JavaScript doesn't support private variables, full stop. Any attempt to hack it on, such as putting everything in the constructor (horrible idea) will just result in degraded performance.

The rather obvious solution, one that practically every JavaScript developer already does, is to make "private" variables begin with an underscore, thus telling anyone using the code "use at your own risk".

Oct 24, 2012 at 10:07 PM
Edited Oct 24, 2012 at 10:19 PM
mateo2 wrote:

JavaScript doesn't support private variables, full stop. 

 

Douglas Crockford thinks it does.

http://www.crockford.com/javascript/private.html

 

mateo2 wrote:

Any attempt to hack it on, such as putting everything in the constructor (horrible idea) will just result in degraded performance.

 

RE: "Hacks"

So it's OK to "hack in" class based inheritance patterns using javascript prototypes, but it is not OK to "hack in" private variables using javascript's closure scope. I guess I don't see consistency in this thought process.

 

RE: "Performance Concerns"

The inheritance abstraction chosen in TypeScript has some trivial memory overhead affiliated with it as well as does any abstraction. This doesn't mean abstractions are a "horrible idea", sometimes they are a reasonable trade off. The goal of TypeScript is to introduce maintainability and productivity and I'd argue private scope (at both compile time and runtime) is a part of that vision.

Oct 25, 2012 at 1:23 PM

 

Eric, constructors have "private" variables like every other closure. But JavaScript OBJECTS do not, ever.  For example, a "private" variable declared in the constructor is not available to any members of the prototype. Example:

 

function SomeConstructor() {
  var imPrivate = "ha!";
}

SomeConstructor.prototype.getImPrivate = function() {
  return this.imPrivate;
};

The latter will always return undefined. imPrivate is only available within the constructor.

Adding class inheritance isn't a hack. The Constructor/Prototype combination is a very close parallel to how classes work in other languages. There is no downside to doing it.

Hacking in private variables, which JavaScript does not support, has an obvious downside: your code will run slow. There seems to be very little (if any) upside compared to just adding a leading underscore.

 

Oct 25, 2012 at 3:25 PM

Currently, TypeScript doesn't have private properties, either (only intent, 8.2.1):

class MyClass {
	private secret: string;
	constructor () {
		this.secret = Math.random();
	}
}
var obj = new MyClass();
alert(obj["secret"]);

As Anders already points out, access to instance-private data seems to require methods-per-instance. That doesn't mean copies of function bodies, though: implementations are free to store closures (function pointers+private env). It is a trade-off, not an impossibility, and there are alternatives.

hypeartist shows a class-private property, which could be used as a secret map, to store per-instance data behind per-instance, public keys. That would protect the instance-private data from the outside, though other class instances could accidentally gain access. It would not require methods-per-instance.

Here is another approach (untested:-):

var MyClass = (function () {

    function MyClass(id) {
       this.id = id;
       var private_this = Object.create(this);  // this and more
       private_this.secret = Math.random().toString();
       this.guess = guess.bind(private_this);
    }

    function guess(guess) {
      var check = guess===this.secret
                ? "I'm not telling!"
                : "That guess is wrong!";
      console.log("("+this.id+"'s secret is: "+this.secret+")");
      console.log(this.id+' says: '+check);
    }

    return MyClass;
})();

var myObj1 = new MyClass("instance1");
var myObj2 = new MyClass("instance2");

console.log(myObj1,myObj2);

var guess = Math.random().toString();
console.log("guessing: "+guess);
myObj1.guess(guess);
myObj2.guess(guess);
Depending on how method binding is implemented, this requires only method-pointers-per-instance, giving private methods access to an extended this.

Dec 29, 2012 at 5:42 AM
Edited Dec 29, 2012 at 5:43 AM

There's a couple of ways you can get the desired private member variables and still maintain the current style of placing all method declarations on the prototype. The first possibility is to simply place the private member declaration outside of the constructor function, and at the same scope level that the prototype function declarations are at. The other option is to move the prototype function declarations inside of the constructor definition. This would definitely give the prototype methods access to the private members for the Greeter constructor without causing a single shared instance of the private member. I've included some examples below:

var Greeter = (function () {
    var greeting = message;
    function Greeter(message) {
        
    }
    Greeter.prototype.greet = function () {
        return "Hello, " + greeting;
    };
    return Greeter;
})();
var greeter = new Greeter("world");
var button = document.createElement('button');
button.innerText = "Say Hello";
button.onclick = function () {
    alert(greeter.greet());
};
document.body.appendChild(button);
 
var Greeter = (function () {
    
    function Greeter(message) {
        var greeting = message;
        Greeter.prototype.greet = function () {
            return "Hello, " + greeting;
        };
    }
    
    return Greeter;
})();
var greeter = new Greeter("world");
var button = document.createElement('button');
button.innerText = "Say Hello";
button.onclick = function () {
    alert(greeter.greet());
};
document.body.appendChild(button);


Dec 30, 2012 at 12:57 PM

@dharric, as pointed out by @fdcamderron the first example will fail because the "greeting" variable is shared across instances:

 

var Greeter = (function () {
    var greeting;
    function Greeter(message) {
        greeting = message;
    }
    Greeter.prototype.greet = function () {
        return "Hello, " + greeting;
    };
    return Greeter;
})();
var greeter = new Greeter("world");
var greeter2 = new Greeter("armageddon");

var button = document.createElement('button');
button.innerText = "Say Hello";
button.onclick = function () {
    alert(greeter.greet()); // Hello, armageddon
};
document.body.appendChild(button);

The second example will also fail for the same reason. In addition it is more inefficient because the "greet" function is redefined every time the constructor runs.

 

Noel

Jan 3 at 10:22 PM

After having played (ie rewritten a project that was in JavaScript) with TypeScript for a good portion of the holidays, I would agree with the convention of prefixing an underscore on private variables.  It follows what every other JavaScript library does 90% of the time to handle private variables.

Now TypeScript does have it's own conventions that already prefix items with _, like the this scope in lamba functions, as an example, so it may make sense to prefix private variables with two underscores, or perhaps modify the existing convention, if there is any chance of a collision.

Jan 5 at 10:24 AM

TypeScript doesn't prevent you from adopting underscore prefix, neither does it prevent any other naming convention.

 

It would be bad for TypeScript to enforce a convention though.

Jan 8 at 6:47 PM
mihailik wrote:

TypeScript doesn't prevent you from adopting underscore prefix, neither does it prevent any other naming convention.

 

It would be bad for TypeScript to enforce a convention though.

I agree on this. As a C# developer I'm used to the convention of prefixing private members with an underscore. I follow that convention when I'm doing TypeScript and Javascript development too.

As you can mix TypeScript and Javascript it would be very annoying if the TypeScript compiler would alter private member names of the TypeScript specific parts of a script while the Javascript specific parts of the script stayed unaltered. That would result in broken conventions in the compiled Javascript, and that would suck.

C#/Javascript doesn't enforce me to follow any convention and TypeScript shouldn't either.

Jan 9 at 1:41 AM

The typescript "private" seems leading in too much problems in my point of view, mainly because it's allowing on the subclass to override properties and method without any error.

Why not just give up the private and completely replace it by "protected", i think that at least it will allow us to receive proper warning/error.

Jan 24 at 8:40 AM
Edited Jan 24 at 9:00 AM

Why can not we make a PRIVATE METHOD to a closure?

 

example:

Typescript:

class Greeter {
    greeting: string;
    private hello(){
        return this.greeting;
    }
    public hi(){
        alert(this.hello());
    }
    constructor(message: string) {
        this.greeting = message;
    }
    greet() {
        return "Hello, " + this.greeting;
    }
}

JavaScript:

var Greeter = (function () {
    var hello = function(){
       return this.greeting;
    }
    function Greeter(message) {
        this.greeting = message;
    }
    Greeter.prototype.hi = function () {
        alert(hello.call(this));
    };
    Greeter.prototype.greet = function () {
        return "Hello, " + this.greeting;
    };
    return Greeter;
})();

 

 

p.s. Another implementation of private members:

var Greeter = (function () {
    var privates = {};

    var setPrivate = function(key, value){
          privates[this.__id][key] = value;
    };

    var getPrivate = function(key){
         return privates[this.__id][key];
    }
    function Greeter(message) {
        this.__id = generateUniqId();
        private[this.__id] = {};
        setPrivate.call(this, 'greeting', message);
    }
    Greeter.prototype.greet = function () {
        return "Hello, " + getPrivate.call(this, 'greeting');
    };
    return Greeter;
})();
Mar 12 at 5:53 AM
Edited Mar 12 at 6:01 AM
Private fields are a hack in any language, not just in JavaScript. It worries me to see this many people suggesting
implementing private members through closures, and incorrectly at that.

Since member visibility is not possible to implement on a hardware level, it can only be emulated or simulated in
software - most compilers do this using a combination of name mangling and compile-time visibility information.
For example, in GCC's C++ compiler, all functions, classes and other constructs are treated as distinct objects, with
each class being given their own vtable, containing references to functions for that class - this would be loosely
analogous to JavaScript's prototype object. Furthermore, each distinct object, once instantiated inside the compiler's
list, is name mangled according to certain rules, resulting in unique names for each class and function, based on
namespace and parameter information. Each class instance gets a pointer to its vtable, ensuring the correct
function is called at runtime, regardless of apparent name conflicts (i.e. private methods declared with the same
name in a parent and child class). If you were to make direct calls to these name-mangled functions, you'd be able
to access them just fine, as the compiler (at that point) won't be checking for accessess of variables it believes are
outside the visibility of the programmer, instead preferring to do scope and access level checks on a higher
semantic level.
(note: I'm writing this, having woken up about 20 minutes earlier - coffee hasn't settled in yet. Any mistakes and/or
generalizations or over-simplifications can be attributed to that, and to trying to make a point understandable by
people other than compiler specialists).


So why am I telling you this?
Quite simply, because I'm wondering why TypeScript doesn't already do what most other compilers do - name
mangle private fields. Since private fields really should be private to the class being compiled, we can safely assume
all references to that private field happen inside the file being processed. Hence, we can safely generate a mangled
name, doing a serach-and-replace for all accesses to that private member.

What would need to happen, though, would be to come up with some sane name mangling scheme. For example,
a simple solution would be to concatenate module[] + class name and MD5 hash the value, then call the resulting
field
_p_hash_fieldname
This shouldn't be too difficult to implement in a future release. People depending on access to private fields outside
a class are (or, at the very least, should be) depending on undefined behaviour anyway, so the fallout of such a
change should be minimal, but the potential gains in program reliability would be enormous.


My two cents.
-cb


edit: why are my lines split arbitrarily in the actual post, but correctly in preview?
edit2: manually inserted line breaks
Mar 12 at 5:54 AM
Edited Mar 12 at 5:54 AM
Accidentally hit reload and got my post posted twice...
Coordinator
Mar 12 at 9:42 PM
@codebeard - There was an ECMAScript 6 recommendation at one point for private names, which is effectively what you're suggesting. It would be names that you couldn't "guess", but would rather have to have a handle to them. This would be pretty straightforward to adopt once ES6 adopted it.
Mar 12 at 10:14 PM
Edited Mar 12 at 10:17 PM
Except for the fact that what I'm suggesting would be implementable now, and completely backwards-compatible
down to the first versions of JavaScript.
Not to mention the fact that ES6 support would need to appear in browsers until that feature could be reliably
used, no?