Created
November 15, 2020 16:25
-
-
Save colindecarlo/24ac7a80c71a3abb9946f8c92d004ebc to your computer and use it in GitHub Desktop.
My implementation of private class attributes in JavaScript and Babel's implementation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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 | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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