Skip to content

Instantly share code, notes, and snippets.

@colindecarlo
Created November 15, 2020 16:25
Show Gist options
  • Save colindecarlo/24ac7a80c71a3abb9946f8c92d004ebc to your computer and use it in GitHub Desktop.
Save colindecarlo/24ac7a80c71a3abb9946f8c92d004ebc to your computer and use it in GitHub Desktop.
My implementation of private class attributes in JavaScript and Babel's implementation
/**
* The good people at the Babel project opted to use global-ish variables (`WeakMap`s) and functions
* to track the private attributes of class instances.
*
* Looking at the implementation, I think the functions used to access the private attributes of the
* classes allow you to have private getters and setters (not sure why you might want that, perhaps
* for internally accessible computed properties) but it sure is neat.
*/
"use strict";
function _classPrivateFieldGet(receiver, privateMap) {
var descriptor = privateMap.get(receiver);
if (!descriptor) {
throw new TypeError("attempted to get private field on non-instance");
}
if (descriptor.get) {
return descriptor.get.call(receiver);
}
return descriptor.value;
}
function _classPrivateFieldSet(receiver, privateMap, value) {
var descriptor = privateMap.get(receiver);
if (!descriptor) {
throw new TypeError("attempted to set private field on non-instance");
}
if (descriptor.set) {
descriptor.set.call(receiver, value);
} else {
if (!descriptor.writable) {
throw new TypeError("attempted to set read only private field");
}
descriptor.value = value;
}
return value;
}
var _name = new WeakMap();
class BabelPerson {
constructor(name, age) {
_name.set(this, {
writable: true,
value: void 0
});
_classPrivateFieldSet(this, _name, name);
}
get name() {
return _classPrivateFieldGet(this, _name);
}
set name(newName) {
if (newName.length === 0) {
throw new Error('invalid name');
}
_classPrivateFieldSet(this, _name, newName);
}
}
const me = new BabelPerson('Colin');
const you = new BabelPerson('Tyler')
console.log({
myName: me.name,
yourName: you.name
});
try {
me.name = '';
} catch (err) {
console.log(err.message);
}
try {
you.name = '';
} catch (err) {
console.log(err.message);
}
me.name = 'Robert Paulson'
console.log({
myName: me.name,
yourName: you.name
});
I think overall, though there is significant difference, both implementations are fine and likely performant (if that's your bag).
I feel that a pro of my implementation is that the data is kept with the instance as opposed to having a separate entity track
the private data of multiple instances. I do like how, in the babel implementation, you can have private getters / setters;
I think I'll update my implementation to support the same.
/**
* I decided to add a `name` property to the object in the constructor with get/set attributes.
* This closed over the `name` parameter which means it won't get garbage collected
* when the constructor finishes executing. The getter and setter of the `name` property provide
* access to the `name` parameter that is passed into the constructor when it's invoked.
*/
"use strict";
class Person {
constructor(name) {
Object.defineProperty(this, 'name', {
get() {
return name;
},
set(newName) {
if (newName.length === 0) {
throw new Error('invalid name')
}
name = newName;
}
})
}
}
const me = new Person('Colin');
const you = new Person('Tyler')
console.log({
myName: me.name,
yourName: you.name
});
try {
me.name = '';
} catch (err) {
console.log(err.message);
}
try {
you.name = '';
} catch (err) {
console.log(err.message);
}
me.name = 'Robert Paulson'
console.log({
myName: me.name,
yourName: you.name
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment