2

How do you make a Javascript private method that is not redefined each time you call the constructor ?

As far as I know, in OOP-JS, private methods are methods defined in the "constructor method" of one's "class", called each time one instantiates a new "object". I was thinking maybe a function declaration (i.e. function name(), as opposed to function expression var name = function()) would do the trick, but how can I be sure that the following code only declares my function once ?

​function Tester() {
    function test () {
        console.log("executed");
    }
}
var t1 = new Tester();
var t2 = new Tester();
ejoubaud
  • 5,013
  • 6
  • 37
  • 39

2 Answers2

4

How do you make a Javascript private method that is not redefined each time you call the constructor ?

You can't (well, see below for a bit of wiggle room). But unless you're going to have thousands of instances of Tester, don't worry about it too much; most engines probably reuse the underlying code across the multiple function objects that get created. (The code, mind; not the function object or the context it closes over, which must be unique and allocated each time. But they need not be large. Of course, quite a function functions are fairly small as well...)

...how can I be sure that the following code only declares my function once ?

You can be sure that it doesn't; it declares the function each time Tester is called. Witness:

​function Tester() {
    this.test = test;
    function test () {
        console.log("executed");
    }
}
var t1 = new Tester();
var t2 = new Tester();
console.log(t1.test === t2.test); // "false"

Note that you can have functions that are private to the implementation, but not assigned to any instance of the object. The module pattern is handy for doing that:

var Tester = (function() {

    function Tester(name) {
        this.name = name;
    }
    Tester.prototype.publicFunction = function() {
        privateFunction.call(this);
    };

    function privateFunction() {
        console.log("My name is " + this.name);
    }

    return Tester;
})();

var t = new Tester("Fred");
t.publicFunction(); // Outputs "My name is Fred" via the private function

There, privateFunction is completely private, accessible only to the code within the anonymous function. And there's only one copy of it, but you can call it as though you were calling a method of a Tester instance using privateFunction.call(this).

Alternately, of course, since using call is slightly slower than doing a normal call, you could just pass the instance as an argument:

var Tester = (function() {

    function Tester(name) {
        this.name = name;
    }
    Tester.prototype.publicFunction = function() {
        privateFunction(this);
    };

    function privateFunction(t) {
        console.log("My name is " + t.name);
    }

    return Tester;
})();

var t = new Tester("Fred");
t.publicFunction(); // Outputs "My name is Fred" via the private function

Of course, the extra cost of call is only a problem if and when it's a problem; unless you're calling something hundreds of thousands of times in a tight loop, it's unlikely to matter. So whether to use call and this or pass an argument would be primarily a style choice.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • Do you have any proof of the reuse? In chrome 18 at least, a closure object is created for each instance and function created which can be seen in heap screenshot. – Esailija May 05 '12 at 15:34
  • @Esailija: I said the underlying code, not the context. The context (and the function object) *must* be created on each call (and retained as long as there are outstanding references to them), barring optimizations that have no external side-effects. Re code reuse, a v8 dev told me that on the V8 mailing list, that the code will be reused for functions defined in the same place in the source "...in most cases..." and he would expect (but didn't actually know) that most other engines would do the same. E.g., the *implementation* and the context/function object are distinct. Which makes sense. – T.J. Crowder May 05 '12 at 15:36
  • @Esailija: Finally found that exchange, which I've actually referred to here on SO [before](http://stackoverflow.com/a/10160351/157247). – T.J. Crowder May 05 '12 at 15:41
2

It took me a while (coming form an ActionScript 3 background), but I feel that I should share with you how I learned to stop worrying and love the lack of private methods ;)

Many popular JavaScript libraries, such as Backbone.js and js-Signals simply make use of a naming convention where a leading underscore denotes private members as opposed to using slightly esoteric syntax (although each to their own!). Just to give this some additional context, Python's documentation goes as far as saying that Python does not support private members at all and suggests using an underscore instead.

JavaScript is a very dynamic language; there's no strict type checking and some really exciting scoping; and there are some really cool libraries out there which take advantage of those facts, such as SinonJS which makes it effortless to achieve meaningful test coverage in your codebase; for example:

var instance = new Car("ford");

// Replace the car's engine with test stub.
// Alternative syntax: sinon.stub(instance, '_engine');
instance._engine = sinon.stub(instance._engine);

car.start();

// As the '_engine' object has been mocked it gains new attributes.
ok(instance._engine.checkOil.calledOnce, 'Oil level was checked');

Sorry this answer doesn't really answer your question (T.J's answer is pretty much textbook in that regard) - I just thought it would be worthwhile to offer another possible solution.

Community
  • 1
  • 1
JonnyReeves
  • 6,119
  • 2
  • 26
  • 28
  • 1
    In practice I think I'm going to follow your advice : TJ's answer is exactly the one I was looking for but his solution is a bit too complex, and the redefine-at-each-instantiation a bit too costly, for the low value provided by private methods. – ejoubaud May 05 '12 at 19:28
  • That was the conclusion I came to as well - I'm happier for it ;) – JonnyReeves May 05 '12 at 19:37