I need to emulate enum type in Javascript and approach seems pretty straight forward:
var MyEnum = {Left = 1; Right = 2; Top = 4; Bottom = 8}
N
You just have to use the bitwise operators:
var myEnum = {
left: 1,
right: 2,
top: 4,
bottom: 8
}
var myConfig = myEnum.left | myEnum.right;
if (myConfig & myEnum.right) {
// right flag is set
}
More info:
Yes, bitwise arithmetic works in Javascript. You have to be careful with it because Javascript only has the Number
data type, which is implemented as a floating-point type. But, values are converted to signed 32-bit values for bitwise operations. So as long as you don't try to use more than 31 bits, you'll be fine.
In javascript you should be able to combine them as:
var left_right = MyEnum.Left | MyEnum.Right;
Then testing would be exactly as it is in your example of
if ( (left_right & MyEnum.Left) == MyEnum.Left) {...}
I've tried to create an example that demonstrates a common use case where one may want to use bit mask enums to control logging verbosity. It demonstrates the us of JavaScript bit-wise operations: See it on JSFiddle
/*
* Demonstration of how a Flags enum can be simulated in JavaScript and
* Used to control what gets logged based on user passed value
*/
// A Flags Enum (sort-of)
var LogLevels = {
NONE: 0,
INFO: 1,
TRACE: 2,
DEBUG: 4,
WARN: 8,
ERROR: 16,
FATAL: 32
};
// Initialize
var currLogLevel = LogLevels.NONE;
// User Sets a log level
var logLevel = LogLevels.WARN;
// Convert the configured logLvel to a bit-masked enum value
switch (logLevel) {
case LogLevels.INFO:
currLogLevel = LogLevels.INFO | LogLevels.TRACE | LogLevels.DEBUG | LogLevels.WARN | LogLevels.ERROR | LogLevels.FATAL;
break;
case LogLevels.TRACE:
currLogLevel = LogLevels.TRACE | LogLevels.DEBUG | LogLevels.WARN | LogLevels.ERROR | LogLevels.FATAL;
break;
case LogLevels.DEBUG:
currLogLevel = LogLevels.DEBUG | LogLevels.WARN | LogLevels.ERROR | LogLevels.FATAL;
break;
case LogLevels.WARN:
currLogLevel = LogLevels.WARN | LogLevels.ERROR | LogLevels.FATAL;
break;
case LogLevels.ERROR:
case LogLevels.FATAL:
default:
currLogLevel = LogLevels.ERROR | LogLevels.FATAL;
}
// Example: log verbosity set to WARN, so this would NOT be logged
if ((currLogLevel & LogLevels.DEBUG) == LogLevels.DEBUG) {
console.log("log DEBUG");
}
Flag Enumerations in JavaScript
Flag enums: Values must increment by powers of 2
var SEASONS = {
Spring : 1,
Summer : 2,
Fall : 4,
Winter : 8
};
Cannot use 0 in a bitwise &
operation to test for a flag b/c it will
always result in zero. However, it can be used for logical comparisons.
Usage examples (contrived)
var getSeasonsSelected = function( seasons ) {
var selected = [];
// The perens are needed around the bitwise operation due to the
// greater operator precedence of `===`
if ( (seasons & SEASONS.Spring) === SEASONS.Spring ) selected.push('Spring');
if ( (seasons & SEASONS.Summer) === SEASONS.Summer ) selected.push('Summer');
if ( (seasons & SEASONS.Fall) === SEASONS.Fall ) selected.push('Fall');
if ( (seasons & SEASONS.Winter) === SEASONS.Winter ) selected.push('Winter');
return selected;
};
var s1 = getSeasonsSelected( SEASONS.Spring | SEASONS.Fall );
console.log(s1);
//=> ["Spring", "Fall"]
There is my implementation in typescript:
export class FlagEnumService {
constructor(private value: number = 0) {
}
public get() {
return this.value;
}
public set(value: number): this {
this.value = value;
return this;
}
public has(key: number): boolean {
return !!(this.value & key);
}
public add(key: number): this {
this.value |= key;
return this;
}
public delete(key: number): this {
this.value &= ~key;
return this;
}
public toggle(key: number): this {
this.has(key) ? this.delete(key) : this.add(key);
return this;
}
}
And tests for clarification
import { FlagEnumService } from './flag-enum.service';
enum Test {
None = 0,
First = 1,
Second = 2,
Third = 4,
Four = 8
}
describe('FlagEnumService', () => {
let service: FlagEnumService;
beforeEach(() => service = new FlagEnumService());
it('should create with initial value', () => {
service = new FlagEnumService(Test.First);
expect(service.get()).toBe(Test.First);
});
it('should return true if has flag', () => {
service = new FlagEnumService(Test.First);
expect(service.has(Test.First)).toBe(true);
});
it('should return false if doesn\'t have flag', () => {
service = new FlagEnumService(Test.First);
expect(service.has(Test.Second)).toBe(false);
});
it('should add', () => {
expect(service.add(Test.First).add(Test.Second).get()).toBe(Test.First + Test.Second);
});
it('should not add the same value twice', () => {
expect(service.add(Test.First).add(Test.First).get()).toBe(Test.First);
});
it('should remove', () => {
expect(
service
.add(Test.First)
.add(Test.Second)
.delete(Test.Second)
.get()
)
.toBe(Test.First);
});
it('should return 0 when add then remove the same value', () => {
expect(service.add(Test.First).delete(Test.First).get()).toBe(0);
});
it('should not remove not added values', () => {
expect(service.add(Test.First).delete(Test.Second).get()).toBe(Test.First);
});
});