20

I am sure that this must be a pretty common question but after scouring the internets for several hours, I have not found an answer. Here is the question:

Suppose that I have an interface called mammal. Every Mammal has to be able to sleep and eat. (In the future I may throw exceptions for the Mammal class to force children to implement the function).

function Mammal() {};

Mammal.prototype.eat = function() {};

Mammal.prototype.sleep = function() {};

Now suppose that I have a Dog class who implements the Mammal class:

function Dog() {};

Dog.prototype = new Mammal();

Dog.prototype.eat = function() {
    ...
};

Dog.prototype.sleep = function() {
    ... 
};

The dog's eat function is very complicated and I would like to use a helper function. I have not been able to figure out what the best way to do this is. Here are the points of consideration:

  • The helper function should never be called from outside of the dog class so ideally it should be private.
  • The eat function does not have access to private functions (prototypes do not have access to private functions)
  • I could put the helper function into a privalaged function but:
    • This would still be a public function -> ie: everyone has the right to call it
    • It would not be part of the prototype so every dog would need to have its own helper function. If there were lots of dogs this seems inefficient.
  • I cannot make the eat function a privaliged function because in order for prototype inheritance to work it needs to be part of the prototype.

Question: How can I call a private function from a prototype function? Or more generally: When an object (child object) inherits from another object (parent object) how should children methods use helper functions and is it possible to make these private?

Ry-
  • 218,210
  • 55
  • 464
  • 476
sixtyfootersdude
  • 25,859
  • 43
  • 145
  • 213
  • I really don't understand your question, can you provide a usage example, please? – Ry- Dec 20 '11 at 19:03

3 Answers3

36

Define your Dog "class" in a closure. Then you can have shared priveliged functions. Just know you will have to be careful about this binding properly when you call it.

var Dog = (function() {
  function Dog() {};

  // Common shared private function, available only to Dog.
  var privateHelper = function() { ... };

  Dog.prototype = new Mammal();

  Dog.prototype.eat = function() {
    privateHelper()
    // Or this if the helper needs to access the instance.
    // privateHelper.call(this);
    ...
  };

  return Dog;
})();

A function on a prototype is still just a function. So follows the same scope and closure access rules as any another function. So if you define your entire Dog constructor and prototype and a secluded island of scope that is the self executing function, you are welcome to share as much as you like without exposing or polluting the public scope.

This is exactly how CoffeeScript classes compile down to JS.

Alex Wayne
  • 178,991
  • 47
  • 309
  • 337
  • The only reason I wanted to make the helper function private was to improve readability and reduce errors. Do you think that this block of code improves readability? Would it be simpler to just make the function public? Is this good practice? – sixtyfootersdude Dec 20 '11 at 19:10
  • 2
    @sixtyfootersdude making the function public put prefixing it with `_` to say it's internal is best practice – Raynos Dec 20 '11 at 19:16
  • 1
    Putting your constructor/prototype configuration in a closure is good practice for sure. Sharing a helper function in this way is less standard, I guess. I would say it depends. It depends on how dangerous the function is, and how much the cleanliness of your API matters. If it's not dangerous, then I would say the code is probably more readable if all methods are on the prototype. Doing a class based style in JS takes a lot of code, and the more standard that is the more readable it will be. – Alex Wayne Dec 20 '11 at 19:17
  • @Raynos I've seen that a lot, but I personally really dislike it. If it's private, make it private, and don't make the exposed API all ugly. Though I acknowledge that the rest of the JS world may not agree with me here... – Alex Wayne Dec 20 '11 at 19:19
  • 2
    @AlexWayne but private confers a run time penalty. – Raynos Dec 20 '11 at 19:25
  • @Raynos if thats true it's pretty negligible: http://jsperf.com/private-or-public-proto-helper – Alex Wayne Dec 20 '11 at 19:45
  • 1
    @AlexWayne Still having one issue. The private methods you are describing are static. Ie: suppose that Dog has a variable energy -> the private member does not have access to that. – sixtyfootersdude Dec 20 '11 at 20:40
  • The helper is shared at the constructor level, not the instance. Which is why I had that bit commented out there... `privateHelper.call(this)`. Then in `privateHelper` you can use `this` as the instance. – Alex Wayne Dec 20 '11 at 20:48
  • 2
    @AlexWayne Maybe it was negligible but with Chrome 33.0.1729 there's currently a huge difference of almost 70%. See: http://jsperf.com/private-or-public-proto-helper#bs-results – Nick Russler Dec 08 '13 at 13:24
1

It's not possible. If you need access to private variables/functions, you must use privileged (helper) functions.

It sometimes is possible to use helper functions in a local closure scope instead of private helper functions (in the constructor scope), which would be accessible from prototype functions. See Alex Wayne's or Raynos' answers for that.

You said:

I cannot make the eat function a privaliged function because in order for prototype inheritance to work it needs to be part of the prototype.

Why not?

MammalPrototype = {
    eat: function() {},
    sleep: function() {}
};

function Dog(color) {
    ...
    this.eat = function() {
        ... // using private functions
        MammalPrototype.eat.call(this, options) // ?
    };
};
Dog.prototype = Object.create(MammalPrototype); // inherit from an object

new Mammal() is OK when Mammal really is only a empty function. See https://stackoverflow.com/a/5199135/1048572 for how this works. The shim proposed there is of course not what a native implementation does, but it is exactly what we need here. Do not create a instance just to inherit from it!

Dog.prototype.sleep = function() {
    ... 
};

function Dalmatian() {
    Dog.call(this, "black.white");
    ...
}
Dalmatian.prototype = Object.create(Dog.prototype); // sic
Dalmatian.prototype.xyz = function() {};

To call the super constructor on this means to receive all privileged methods, and their functionality. You will need to do this if you are using private variables and/or functions in "classes" you inherit from, otherwise all calls to inherited privileged functions will affect only the one instance you've set your prototype to.

Community
  • 1
  • 1
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • It is possible, you just need a mechanism which exposes private state from a Parent class in scope of a Child class – Raynos Dec 20 '11 at 19:57
  • @Raynos: Yes, indeed. But refering to accessing local/private attributes/variables (methods/functions) of constructor scope from outside, the only mechanism is privileged functions. – Bergi Dec 20 '11 at 20:13
  • Not really, you just need to use a different mechanism for private state then local variables in constructors. – Raynos Dec 20 '11 at 20:25
0

When an object (child object) inherits from another object (parent object) how should children methods use helper functions and is it possible to make these private?

You need some layers of abstraction to achieve what you want: Live Example

Uses klass

var Mammal = klass(function (privates) {
    privates.local_helper = function () {
        console.log("local_helper invoked"); 
    }

    return {
        constructor: function () {
            console.log("mammal constructed");
            privates(this).caneat = true;
        },
        eat: function () { 
            privates.local_helper();
            console.log("mammal eat");
        },
        sleep: function () { 
            console.log("mammal sleep");
        } 
    }; 
});

var Dog = klass(Mammal, function (privates, $super) {
    return {
        constructor: function () {
            $super.constructor.call(this);
            console.log("dog constructed"); 
        },
        eat: function () {
            $super.eat.call(this);
            privates.local_helper();
            console.log("dog eat");
            console.log(privates(this).caneat);
        }
    };
});

var dog = new Dog();
dog.eat();
Raynos
  • 166,823
  • 56
  • 351
  • 396