patternjavascriptMinor
Attempt at type-safe enums in JavaScript
Viewed 0 times
javascriptattempttypesafeenums
Problem
I wrote this tiny library yesterday. The goal was to implement enums in JavaScript with type-safety. I modeled the implementation similar to enums in Java, since that is what I am most familiar with.
It seems to work like you would expect, but I wanted to make sure that what I did makes sense and that there isn't some glaring hole in my implementation, and that enum semantics are appropriately conveyed. Suggestions for improvement are appreciated also.
```
/**
* enum.js - Type-safe enums in JavaScript. Modeled after Java enums.
* Version 1.0.0
* Written by Vivin Paliath (http://vivin.net)
* License: BSD License
* Copyright (C) 2015
*/
var Enum = (function () {
/**
* Function to define an enum
* @param typeName - The name of the enum.
* @param constants - The constants on the enum. Can be an array of strings, or an object where each key is an enum
* constant, and the values are objects that describe attributes that can be attached to the associated constant.
*/
function define(typeName, constants) {
/ Check Arguments /
if (typeof typeName === "undefined") {
throw new TypeError("A name is required.");
}
if (!(constants instanceof Array) && (Object.getPrototypeOf(constants) !== Object.prototype)) {
throw new TypeError("The constants parameter must either be an array or an object.");
} else if ((constants instanceof Array) && constants.length === 0) {
throw new TypeError("Need to provide at least one constant.");
} else if ((constants instanceof Array) && !constants.reduce(function (isString, element) {
return isString && (typeof element === "string");
}, true)) {
throw new TypeError("One or more elements in the constant array is not a string.");
} else if (Object.getPrototypeOf(constants) === Object.prototype && !Object.keys(constants).reduce(function (isObject, constant) {
It seems to work like you would expect, but I wanted to make sure that what I did makes sense and that there isn't some glaring hole in my implementation, and that enum semantics are appropriately conveyed. Suggestions for improvement are appreciated also.
```
/**
* enum.js - Type-safe enums in JavaScript. Modeled after Java enums.
* Version 1.0.0
* Written by Vivin Paliath (http://vivin.net)
* License: BSD License
* Copyright (C) 2015
*/
var Enum = (function () {
/**
* Function to define an enum
* @param typeName - The name of the enum.
* @param constants - The constants on the enum. Can be an array of strings, or an object where each key is an enum
* constant, and the values are objects that describe attributes that can be attached to the associated constant.
*/
function define(typeName, constants) {
/ Check Arguments /
if (typeof typeName === "undefined") {
throw new TypeError("A name is required.");
}
if (!(constants instanceof Array) && (Object.getPrototypeOf(constants) !== Object.prototype)) {
throw new TypeError("The constants parameter must either be an array or an object.");
} else if ((constants instanceof Array) && constants.length === 0) {
throw new TypeError("Need to provide at least one constant.");
} else if ((constants instanceof Array) && !constants.reduce(function (isString, element) {
return isString && (typeof element === "string");
}, true)) {
throw new TypeError("One or more elements in the constant array is not a string.");
} else if (Object.getPrototypeOf(constants) === Object.prototype && !Object.keys(constants).reduce(function (isObject, constant) {
Solution
Why use getters like
you might use these even when they do nothing more than return a private
property so as to future-proof your code: if, one day, you want to add some
logic to count the number of times an Enum name is fetched, or otherwise do
more than only return a value, then you can do this without having to modify
every bit of source that had referenced a property and must now call a
function. But there's no need for this kind of future-proofing in JavaScript:
Why should
feature, or just something that fell out of your code?
Why freeze everything?
following code, I just wonder what the point is.
Do enums have a name for any reason other than to show up in their string representation? Again, easy to add back to the following if you really want it.
Mainly, this strikes me as an incredible amount of work for what it does.
Usage is retained, with one exception:
EDIT: I didn't even notice the protection against new instances of the Enum constructor. This seems to me to be an unlikely error... but this kind of protection is easily added with a check against a closed-over lexical variable:
name() and ordinal() instead of properties? In Javayou might use these even when they do nothing more than return a private
property so as to future-proof your code: if, one day, you want to add some
logic to count the number of times an Enum name is fetched, or otherwise do
more than only return a value, then you can do this without having to modify
every bit of source that had referenced a property and must now call a
function. But there's no need for this kind of future-proofing in JavaScript:
var a = { get x() { return "got x" } }
a.x // 'got x'Why should
Days.Friday.fromName('Saturday') work? Is this a real, intendedfeature, or just something that fell out of your code?
Why freeze everything?
Object.freeze() calls are easy to add to thefollowing code, I just wonder what the point is.
Do enums have a name for any reason other than to show up in their string representation? Again, easy to add back to the following if you really want it.
Mainly, this strikes me as an incredible amount of work for what it does.
var Enum = {
define: function() {
var type = function() {},
length = arguments.length,
enums = [],
string = "Enum { "
type.prototype.valueOf = type.prototype.toString = function() { return this.name }
type.fromName = function(name) { return type[name] }
type.values = function() { return enums }
type.toString = function() { return string }
for (var i = 0; i 0) string += ", "
string += o.name
}
string += " }"
return type
}
}Usage is retained, with one exception:
var Days = Enum.define("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday")
console.log(true, Days.Monday instanceof Days)
console.log('Friday', Days.Friday.name)
console.log(4, Days.Friday.ordinal)
console.log(true, Days.Sunday === Days.Sunday)
console.log(false, Days.Sunday === Days.Friday)
console.log('Sunday', Days.Sunday.toString())
console.log('Enum { Monday, Tuesday, ... }', Days.toString())
console.log('["Monday", ...]', Days.values().map(function(e) { return e.name }))
console.log('Friday', Days.values()[4].name)
console.log(true, Days.fromName("Thursday") === Days.Thursday)
console.log('Wednesday', Days.fromName("Wednesday").name)
// console.log('Saturday', Days.Friday.fromName('Saturday').name)EDIT: I didn't even notice the protection against new instances of the Enum constructor. This seems to me to be an unlikely error... but this kind of protection is easily added with a check against a closed-over lexical variable:
var Terminable = (function() {
var terminated = false,
constructor = function() {
if (terminated)
throw new TypeError("Cannot instantiate a new instance of a terminated constructor")
}
constructor.terminate = function() { terminated = true }
return constructor
})()
var a = new Terminable
console.log(true, a instanceof Terminable) // true true
Terminable.terminate()
var b = new Terminable // TypeErrorCode Snippets
var a = { get x() { return "got x" } }
a.x // 'got x'var Enum = {
define: function() {
var type = function() {},
length = arguments.length,
enums = [],
string = "Enum { "
type.prototype.valueOf = type.prototype.toString = function() { return this.name }
type.fromName = function(name) { return type[name] }
type.values = function() { return enums }
type.toString = function() { return string }
for (var i = 0; i < length; i++) {
var o = new type
type[arguments[i]] = o
o.name = arguments[i]
o.ordinal = i
enums.push(o)
if (i > 0) string += ", "
string += o.name
}
string += " }"
return type
}
}var Days = Enum.define("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday")
console.log(true, Days.Monday instanceof Days)
console.log('Friday', Days.Friday.name)
console.log(4, Days.Friday.ordinal)
console.log(true, Days.Sunday === Days.Sunday)
console.log(false, Days.Sunday === Days.Friday)
console.log('Sunday', Days.Sunday.toString())
console.log('Enum { Monday, Tuesday, ... }', Days.toString())
console.log('["Monday", ...]', Days.values().map(function(e) { return e.name }))
console.log('Friday', Days.values()[4].name)
console.log(true, Days.fromName("Thursday") === Days.Thursday)
console.log('Wednesday', Days.fromName("Wednesday").name)
// console.log('Saturday', Days.Friday.fromName('Saturday').name)var Terminable = (function() {
var terminated = false,
constructor = function() {
if (terminated)
throw new TypeError("Cannot instantiate a new instance of a terminated constructor")
}
constructor.terminate = function() { terminated = true }
return constructor
})()
var a = new Terminable
console.log(true, a instanceof Terminable) // true true
Terminable.terminate()
var b = new Terminable // TypeErrorContext
StackExchange Code Review Q#105032, answer score: 3
Revisions (0)
No revisions yet.