1

I'm following Crockford's guide to private methods in Javascript, and I'm struggling with something. I'm trying to optimize this code

function Container(param) {

    function dec() {
        if (secret > 0) {
            secret -= 1;
            return true;
        } else {
            return false;
        }
    }

    this.member = param;
    var secret = 3;
    var that = this;

    this.service = function () {
        return dec() ? that.member : null;
    };
}

by defining the functions outside of the constructor so that a new function object isn't created each time a new instance is created.

I still have no idea of how to do this for those he refers to as private methods (any help is well appreciated). For those he calls privileged methods this is what I'm trying to do:

function Container(param) {

    function dec() {
        if (secret > 0) {
            secret -= 1;
            return true;
        } else {
            return false;
        }
    }

    this.member = param;
    var secret = 3;
    var that = this;
}
Container.prototype.service = function() {
    return dec() ? that.member : null; 
};

but if I test it like this

d1 = new Container("content");
d1.service();

I get this error:

ReferenceError: dec is not defined

Does this mean there's no way of using the advantages of the private/privileged methods Crockford uses AND optimizing memory usage by linking all instances of the class to the same function object? I hope you'll prove me wrong.

memememe
  • 663
  • 6
  • 21
  • 1
    You cannot do that, the idea is to hide values in the scope of a constructor. – zerkms May 20 '19 at 00:30
  • 1
    'there's no way of using private/privileged methods" --- there are no "private"/"privileged" methods in JS, making up terms make more harm than good. – zerkms May 20 '19 at 00:31
  • the point is not really to use the made-up term, but the design benefits the concept of information hiding provides. – memememe May 20 '19 at 00:32
  • If you stop using those terms - then you would be able to answer your question yourself. "hiding" here only works thanks to closures. If you don't have access to a closure/function - you cannot call it. – zerkms May 20 '19 at 00:33
  • so is there really no way of using something resembling private methods without the memory waste? – memememe May 20 '19 at 00:35
  • And again, if you did not use that made up term - the answer would be immediately obvious to you... – zerkms May 20 '19 at 00:41
  • it's not. maybe the techniques I've been reading about with those "made up names" you so despise don't allow me to but there might be other design patterns allowing it. – memememe May 20 '19 at 00:43
  • It's not that I'm "not allowing" you, but my point is that: those terms don't have a well defined meaning, using them makes everything more confusing. Those are not "private" methods, those are _just_ functions. There is nothing special in those functions. – zerkms May 20 '19 at 00:45
  • I'm going to edit the question so you're happy. However I was referring to the techniques, not to you: maybe Crockford's patterns don't let you have both benefits but there might be a way to get around this limitation. – memememe May 20 '19 at 00:46

1 Answers1

3

If you don't want to create a new dec on every call of Container, you can make Container into an IIFE - define dec when Container is defined, and then return the actual Container constructor from the IIFE, so that dec is only referenceable from inside the Container. To encapsulate the secrets, use a Map indexed by instance instead of using a plain var secrets in the constructor, so that the (shared) dec and service functions can see and use the Map.

Actually, as comment notes, it'd probably be better to use a WeakMap so that an instance's associated secret can be garbage collected once the instance is GC'd:

const Container = (() => {
  const secretsByInstance = new WeakMap();
  function dec(instance) {
    const secret = secretsByInstance.get(instance);
    if (secret > 0) {
      secretsByInstance.set(instance, secret - 1);
      return true;
    } else {
      return false;
    }
  }
  function Container(param) {
    secretsByInstance.set(this, 3);
    this.member = param;
  }
  Container.prototype.service = function() {
   return dec(this) ? this.member : null;
  };
  return Container;
})();

d1 = new Container("content");
console.log(d1.service());
console.log(d1.service());
console.log(d1.service());
console.log(d1.service());
CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
  • 2
    `WeakMap` probably would be a better choice here. – zerkms May 20 '19 at 00:46
  • This seems really smart! Only one doubt: why is `secretsByInstance` defined by instance if it's declared the same way `dec` is basically? – memememe May 20 '19 at 00:58
  • @memememe You need some way to save data associated with every instance, thus the WeakMap. The `dec`, on the other hand, given said WeakMap and an instance, can be completely static. – CertainPerformance May 20 '19 at 01:01
  • Yeah I just can't fully understand how we are saying to the interpreter that we want the WeakMap to be created when we instantiate the object if we're basically creating it in the IIFE and not in the constructor method that is being returned by it – memememe May 20 '19 at 01:04
  • The WeakMap variable needs to be scoped at the level of the IIFE (and not inside the constructor) so that the other functions defined at the level of the IIFE can access it - the `dec` and the `service` functions. If the WeakMap was defined inside the constructor, it would not be referenceable outside (and to make things work, you'd have to define the functions inside the constructor too, like you were doing originally, which is less efficient). – CertainPerformance May 20 '19 at 01:09
  • 1
    @memememe In the constructor, we're setting the value at the *current instance* of the WeakMap to 3. The WeakMap can contain any number of key-value pairs, eg, for both `d1` and `d2` in `d1 = new Container("content1"); d2 = new Container("content2");`. So we need to define the data structure up front, where all the functions can see it, and then we need to put an instance's `secret` into the data structure, when an instance is created – CertainPerformance May 20 '19 at 01:09
  • 1
    @memememe Yep, see how `Container` is defined using an IIFE - that means that `secretsByInstance`, `dec`, and the `Container` constructor and `service` method are all defined exactly *once*, and not repeatedly. Then, when an instance is created with `new Container` and gets used, those different functions/data structures get used (but not created anew) – CertainPerformance May 20 '19 at 01:14
  • This is much clearer now! What exactly are we using as the key for `secretsByInstance` actually? The instantiated object itself? – memememe May 20 '19 at 01:15
  • 1
    @memememe Yes, that's the `this` – CertainPerformance May 20 '19 at 01:16