WeakMap for JavaScript Private Data

Private Data in JavaScript

A common way that developers expose a public interface for interacting with private data in JavaScript is by using a closure.

const EnginePart = (() => {

   // Don't want any other code to have direct access to this
   const _part_info = {
      part_number: "4104928345103",
      weight_in_kg: 1.7
   };

   const Part = Object.create(null, {
      getPartNumber: {
         value: function () {
            return _part_info.part_number;
         }
      }
   });

   return Part;
})();

Now I can create many objects with EnginePart as their prototype. Each one will have the correct part number, and that part number only accessible through the getter method. It can't be modified by other objects.

const part_one = Object.create(EnginePart);
const part_two = Object.create(EnginePart);

part_one.part_number = "99999"
>> "99999"
part_one.getPartNumber()
>> "4104928345103"

The problem is that the closure ensures that the data in the private variable remains in memory if EnginePart needs to garbage collected, because the private _part_info.part_number variable reference made by the getter is never lost.

This is called a memory leak. During the execution of the application, if there are no more references to EnginePart, the JavaScript engine will attempt to reclaim the memory being used. The closure prevents that.

WeakMap Storage

WeakMaps help solve this because the keys are weakly referenced. Therefore, if I define a WeakMap inside the closure, and make the key the object instance itself, then when that instance is garbage collected, it's value will also be collected!

No memory leaks.

const EnginePart = (() => {

   // Store `this` in the WeakMap so that it's weakly referenced
   const _privateStore = new WeakMap();

   // Function to store the reference key, and get its value
   function storage (object) {
      if (!_privateStore.has(object))
           _privateStore.set(object, Object.create(null));
      return _privateStore.get(object);
   };

   const Part = Object.create(null, {
      init: {
         value: function () {
           storage(this).part_number = "4104928345103";
           storage(this).weight_in_kg = 1.7;
           return this;
         }
      },
      getPartNumber: {
         value: function () {
            return storage(this).part_number;
         }
      }
   });

   return Part.init();
})();

Put it in the Old Tool Belt

Since I plan on using this mechanism in several places in my application, I needed to abstract it away into a utility.

/*
  gutil is the handy, global object I create in most apps
  to hold utility functions
*/
let gutil = Object.create(null);

/*
  The privy allows modules to have weakly-held private data contained
  in a new WeakMap. It returns a function to access that WeakMap.
*/
gutil.privy = Object.create(null, {
  init: {
    value: function () {
      const _private = new WeakMap();

      return function (object) {
        if (!_private.has(object))
          _private.set(object, Object.create(null));
        return _private.get(object);
      };
    }
  }
});

Then I can use it inside an IIFE that builds a module of my application.

"use strict";

var Gauntlet = function (global) {
  const storage = gutil.privy.init(); // Private store

  // The Horde will contain all monster combatants
  const Horde = Object.create(null);

  // Initialization
  Object.defineProperty(Horde, "init", {
    value: function () {
      storage(this).horde = new WeakMap();
      storage(this).names = new Set();
      return this;
    },
    enumerable: true
  });

  // Return all monsters
  Object.defineProperty(Horde, "all", {
    value: function () {
      return storage(this).horde;
    },
    enumerable: true
  });


  // Get a specific type of monster
  Object.defineProperty(Horde, "soldier", {
    value: function () {
      return Object.create(storage(this).horde.get(type));
    },
    enumerable: true
  });

  global.Horde = Horde.init();
  return global;
}(Gauntlet || {});