Is there any way to make “private” variables (those defined in the constructor), available to prototype-defined methods?
TestClass = function(){
var priv
Was playing around with this today and this was the only solution I could find without using Symbols. Best thing about this is it can actually all be completely private.
The solution is based around a homegrown module loader which basically becomes the mediator for a private storage cache (using a weak map).
const loader = (function() {
function ModuleLoader() {}
//Static, accessible only if truly needed through obj.constructor.modules
//Can also be made completely private by removing the ModuleLoader prefix.
ModuleLoader.modulesLoaded = 0;
ModuleLoader.modules = {}
ModuleLoader.prototype.define = function(moduleName, dModule) {
if (moduleName in ModuleLoader.modules) throw new Error('Error, duplicate module');
const module = ModuleLoader.modules[moduleName] = {}
module.context = {
__moduleName: moduleName,
exports: {}
}
//Weak map with instance as the key, when the created instance is garbage collected or goes out of scope this will be cleaned up.
module._private = {
private_sections: new WeakMap(),
instances: []
};
function private(action, instance) {
switch (action) {
case "create":
if (module._private.private_sections.has(instance)) throw new Error('Cannot create private store twice on the same instance! check calls to create.')
module._private.instances.push(instance);
module._private.private_sections.set(instance, {});
break;
case "delete":
const index = module._private.instances.indexOf(instance);
if (index == -1) throw new Error('Invalid state');
module._private.instances.slice(index, 1);
return module._private.private_sections.delete(instance);
break;
case "get":
return module._private.private_sections.get(instance);
break;
default:
throw new Error('Invalid action');
break;
}
}
dModule.call(module.context, private);
ModuleLoader.modulesLoaded++;
}
ModuleLoader.prototype.remove = function(moduleName) {
if (!moduleName in (ModuleLoader.modules)) return;
/*
Clean up as best we can.
*/
const module = ModuleLoader.modules[moduleName];
module.context.__moduleName = null;
module.context.exports = null;
module.cotext = null;
module._private.instances.forEach(function(instance) { module._private.private_sections.delete(instance) });
for (let i = 0; i < module._private.instances.length; i++) {
module._private.instances[i] = undefined;
}
module._private.instances = undefined;
module._private = null;
delete ModuleLoader.modules[moduleName];
ModuleLoader.modulesLoaded -= 1;
}
ModuleLoader.prototype.require = function(moduleName) {
if (!(moduleName in ModuleLoader.modules)) throw new Error('Module does not exist');
return ModuleLoader.modules[moduleName].context.exports;
}
return new ModuleLoader();
})();
loader.define('MyModule', function(private_store) {
function MyClass() {
//Creates the private storage facility. Called once in constructor.
private_store("create", this);
//Retrieve the private storage object from the storage facility.
private_store("get", this).no = 1;
}
MyClass.prototype.incrementPrivateVar = function() {
private_store("get", this).no += 1;
}
MyClass.prototype.getPrivateVar = function() {
return private_store("get", this).no;
}
this.exports = MyClass;
})
//Get whatever is exported from MyModule
const MyClass = loader.require('MyModule');
//Create a new instance of `MyClass`
const myClass = new MyClass();
//Create another instance of `MyClass`
const myClass2 = new MyClass();
//print out current private vars
console.log('pVar = ' + myClass.getPrivateVar())
console.log('pVar2 = ' + myClass2.getPrivateVar())
//Increment it
myClass.incrementPrivateVar()
//Print out to see if one affected the other or shared
console.log('pVar after increment = ' + myClass.getPrivateVar())
console.log('pVar after increment on other class = ' + myClass2.getPrivateVar())
//Clean up.
loader.remove('MyModule')