DEV Community

Cover image for Emulating "Private" Variables in JavaScript with Closures and Factory Functions

Emulating "Private" Variables in JavaScript with Closures and Factory Functions

Basti Ortiz on November 16, 2018

Despite the recent implementation of classes in JavaScript, there has never been a native way of controlling the visibility of an object's property...
Collapse
 
journiq profile image
journiq

Loved your article as it elucidates, in a very well-written way, the importance of closures for JS. I always use Apps Script to create stuff, and I have tested many approaches. Eventually, the chosen one was using classes. It's better for it gives you correct completions, allows you to properly add JSdoc @type and works fine in a GAS file environment. Personally, "free" variables, unattached to any object inside a function, makes me feel I'm dealing with a mess. I prefer attaching things to "this" and putting an underscore as prefix. Once I'm the only one using my programs, I have no security problems with private variables.

Collapse
 
ironydelerium profile image
ironydelerium • Edited

The one place where I never particularly liked this method personally is where the type of the object is also relevant - with your factory, each object is it's own thing, not using any sort of prototypical inheritance or the like.

For the times where I find it necessary to actually use truly private variables and still take advantage of JavaScript's built-in inheritance, there's also the WeakMap:

let NamedThing = (function() {
  const Private = new WeakMap();
  return class NamedThing {
    constructor(name) {
      // Yes, we're only storing one value in the "private object".
      // Of course, the example is a bit contrived.
      Private.set(this, { name });
    }

    // For comparison to the original. I'd use 'get name()' and 
    // 'set name(name)' instead, personally, but that I think is more
    // a matter of personal preference.
    getName() { return Private.get(this).name; }
    setName(name) { Private.get(this).name = name; }
  }
})();

let Animal = (function(NamedThing) {
  const Private = new WeakMap();
  return class Animal extends NamedThing {
    constructor(name, job) {
      super(name);
      Private.set(this, { job });
    }
    getJob() { return Private.get(this).job; }
    setJob(job) { Private.get(this).job = job; }

    // Just for exposition: this should always return undefined,
    // since NamedThing has it's own WeakMap, separate from this one
    getNameFromHere() { return Private.get(this).name; }
  }
})(NamedThing);

const presto = new Animal('Presto', 'Digger');
console.log(presto instanceof NamedThing); // true
console.log(presto.getName());             // Presto
console.log(presto.name);                  // undefined
console.log(presto.getNameFromHere())      // undefined
console.log(presto instanceof Animal);     // true
console.log(presto.getJob());              // Digger
presto.setJob('Bone Finder');
console.log(presto.getJob());              // Bone Finder
Enter fullscreen mode Exit fullscreen mode

Of course, if IE <11 support is something you care about, this isn't viable (even after being fed through Babel) unless leaking memory is OK with you.

Collapse
 
somedood profile image
Basti Ortiz

I like how clever this is. Surely, this has its uses, but for me, it's too much of a price to pay just to enable inheritance. It's one big soup of JavaScript obscurities (like how classes can be passed in as arguments to a function). I can imagine in my head how a traditional object-oriented developer would look at this and be like, "WHAT IS THIS??? JAVASCRIPT IS A HORRIBLE LANGUAGE!"

Collapse
 
ironydelerium profile image
ironydelerium

It depends on how important inheritance is for your use case. And while I haven't gone looking, I bet the Babel plugin for #name likely does much the same thing.

Collapse
 
qm3ster profile image
Mihail Malo

I wonder what the memory performance is like compared to this:

const privates = new WeakMap()
class Animal {
  constructor(name, job) {
    privates.set(this, { name, job })
    this.aPublicOne = true
  }

  // Real Getters
  get name() {
    return privates.get(this).name
  }
  get job() {
    return privates.get(this).job
  }
  // Real Setters
  set name(name) {
    privates.get(this).name = name
  }
  set job(job) {
    privates.get(this).job = job
  }
}
Enter fullscreen mode Exit fullscreen mode

Liek, I know instantiating closures isn't the same as actually declaring separate different functions. But it also is more expensive than object methods, to my great disdain.
Will this be somewhere in the middle, or actually worse than both?

Collapse
 
somedood profile image
Basti Ortiz

I'm not sure myself, honestly. I'd love to know. When I have the time, I'll try to create a rigorous experiment for this using the DevTools Memory tools.

If I were to make an educated guess, I'd say the difference in memory efficiency would be negligible.

Collapse
 
qm3ster profile image
Mihail Malo

How funny is it, though, that this method is still using closures, the 5 functions capturing privates :D

Thread Thread
 
somedood profile image
Basti Ortiz • Edited

It makes you realize that even though closures are so ubiquitous in JavaScript, not everyone truly understands it.

Collapse
 
denik1981 profile image
denik1981
const privates = new WeakMap()
class Animal {
  constructor(name, job) {
    privates.set(this, { name, job })
    console.log('I'm a total GENIUS!')
  }
}
// A few lines later on the code ....
function privates() {
  console.log("No, you are not. Do you remember the NO-GLOBALS rules?")
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
qm3ster profile image
Mihail Malo

1. Uncaught SyntaxError: missing ) after argument list

You would have caught this one if you used highlighting:

const privates = new WeakMap()
class Animal {
  constructor(name, job) {
    privates.set(this, { name, job })
    console.log('I'm a total GENIUS!')
  }
}
// A few lines later on the code ....
function privates() {
  console.log("No, you are not. Do you remember the NO-GLOBALS rules?")
}
Enter fullscreen mode Exit fullscreen mode

vs

const privates = new WeakMap()
class Animal {
  constructor(name, job) {
    privates.set(this, { name, job })
    console.log("I'm a total GENIUS!")
  }
}
// A few lines later on the code ....
function privates() {
  console.log("No, you are not. Do you remember the NO-GLOBALS rules?")
}
Enter fullscreen mode Exit fullscreen mode

2. Uncaught SyntaxError: Identifier 'privates' has already been declared (pointing at the function, not the const)

Have you forgotten declaration collision? (TypeScript would also have caught it statically)

3. "globals" are fine when you are thinking in modules

Collapse
 
swarupkm profile image
Swarup Kumar Mahapatra

I have been practising Factory Functions for a while in this repo
github.com/swarupkm/practise-js/tr...

The same I have done in Java as typical Object Oriented.

In this link medium.freecodecamp.org/class-vs-f... I found this quotations

I think Class Free Object Oriented Programming is JavaScript’s gift to humanity.
— Douglas Crockford “The Better Parts”

Collapse
 
somedood profile image
Basti Ortiz

Douglas Crockford truly is the legend of JavaScript. I totally agree with that quote.

I bet I would go insane if I needed to write a class for every time I needed to instantiate objects. Just imagine the hassle it would be to pass in an object as an argument to a function if classes were required to instantiate that object.

// Method #1 and Method #2 achieve the exact same thing.

// METHOD #1
class Options {
  constructor(value, isWritable) {
    this.value = value;
    this.writable = isWritable;
  }
}

Object.defineProperty(window, 'dev', new Options('to', false));

// METHOD #2
Object.defineProperty(window, 'dev', {
  value: 'to',
  writable: false
});
Collapse
 
qm3ster profile image
Mihail Malo

That's actually similar to how options work in Rust.
You construct a struct.
If it's big, you often use a builder to not have to construct it all at once.
However, it all works out great in the end, and everyone is ecstatic :/

Collapse
 
anodynos profile image
Angelos Pikoulas • Edited

Has anyone tried something as naive as this?

The 101 smoke test worked, it seems like _flees are gonna be garbage collected, what could go wrong? You can also attach getters and setters and improve it quite more, make it readonly or writeonly, etc.

class Dog {
  constructor() {
    let _flees = 0;
    this.getFlees = () => _flees;
    this.setFlees = (f) => {_flees = f};
  }
}

const a = new Dog;
const b = new Dog;

a.setFlees(1);
b.setFlees(2);
console.log(a.getFlees());
console.log(b.getFlees());

a.setFlees(11);
b.setFlees(22);
console.log(a.getFlees());
console.log(b.getFlees());
// output 1 2 11 22
Enter fullscreen mode Exit fullscreen mode

The obvious side effect of this simplicity is of that _flees won't be able to be accessed by other methods at all, they all have to use the accessor methods.

So they are super-private.

But is this a wrong thing necessarily? I'm only entertaining the idea, but unless one uses it in practice, one can't know really...

Any thoughts?

Collapse
 
somedood profile image
Basti Ortiz

Honestly, I doubt that anyone has the time to add this level of complexity just for privacy and encapsulation in JavaScript. I wrote this post to inform people that it is indeed possible, albeit rather impractical. Most of us would just prefer using the _ naming convention to denote member privacy.

I wouldn't say it's a "wrong thing", per se. It's still a cool party trick and all, but if one hopes to use this in production code, they better hope that the next person maintaining their code knows enough about JavaScript to understand why all the boilerplate code is necessary for member privacy.

Collapse
 
swarupkm profile image
Swarup Kumar Mahapatra

Nice article. Can I guess that you have been influenced by youtube.com/watch?v=ImwrezYhw4w ?

I have been influenced for sure, and I deeply appreciate the multi-paradigm possibility in JavaScript

Collapse
 
somedood profile image
Basti Ortiz • Edited

Ooooooh! I am so glad to find a fellow fan of Fun Fun Function here. Unfortunately, no, I was not entirely influenced by the video. "Entirely". I did learn about the concept from that video, though.

The inspiration behind this article comes from a thought experiment I had while I was in my shower. I just wondered how to implement private variables in JavaScript and POOF! I made an article about it.

Collapse
 
swarupkm profile image
Swarup Kumar Mahapatra • Edited

Yup, I am a big fan of Fun Fun Functions. Fluffykins is the word I keep hearing about in his videos , hence I thought you might know about it.

Factory Functions . I have recently started working on nodejs and previously I had background in Java, Python , Ruby and I followed traditional OOPS programming.

However by going through FFF videos, I am convinced to think functionally and create Objects that way. Ultimate goal is to createsOBJECTS whatever way it may be.

Implementation of Classes in JS feels like implementation of Functional Programming in Java. Basically they are retrofitting stuff.

This particular rant against classes by FFF was awesome. youtube.com/watch?v=Tllw4EPhLiQ&li...

Thread Thread
 
somedood profile image
Basti Ortiz

Yes! Someone finally caught on with the Fluffykins reference. I've been using it in my code examples recently in my recent posts in the hopes of finding a fan of the show. You, my friend, are the first that I have found.

That is why I find JavaScript to be one of the best languages out there. It just finds a way to combine various paradigms, which makes you think differently about programming. Traditional programming makes you strictly think in a specific way. For me, it doesn't open my mind to new possibilities enough, which is why I grew to love JavaScript.

Collapse
 
Sloan, the sloth mascot
Comment deleted
Collapse
 
jreina profile image
Johnny Reina

Prepending private variables with an underscore is a convention that has been used by c/c++ developers since the beginning of time. I still do it to the same effect but it is becoming less common with newer devs entering the workforce who may not have been exposed to this convention.

Collapse
 
theycallmenucci profile image
Steve Antonucci • Edited

Interesting. Thank you for the insight.

As an older dev who has long grown past his C/C++ zenith, I can't say it was a convention I was ever exposed to in the workforce myself; surprisingly. Can't say I ever really bothered to look it up either. 😖 Sounds like I need to get out more. 😀

Collapse
 
somedood profile image
Basti Ortiz

Ooh. Now this is interesting.