惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

G
Google Developers Blog
S
Schneier on Security
The Hacker News
The Hacker News
P
Proofpoint News Feed
Spread Privacy
Spread Privacy
L
LINUX DO - 热门话题
L
Lohrmann on Cybersecurity
I
Intezer
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
www.infosecurity-magazine.com
www.infosecurity-magazine.com
Schneier on Security
Schneier on Security
Security Latest
Security Latest
AWS News Blog
AWS News Blog
B
Blog RSS Feed
Microsoft Security Blog
Microsoft Security Blog
有赞技术团队
有赞技术团队
博客园 - 叶小钗
The Last Watchdog
The Last Watchdog
O
OpenAI News
月光博客
月光博客
Hacker News: Ask HN
Hacker News: Ask HN
阮一峰的网络日志
阮一峰的网络日志
S
Security @ Cisco Blogs
Google Online Security Blog
Google Online Security Blog
云风的 BLOG
云风的 BLOG
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Latest news
Latest news
P
Palo Alto Networks Blog
Last Week in AI
Last Week in AI
M
MIT News - Artificial intelligence
Google DeepMind News
Google DeepMind News
P
Proofpoint News Feed
C
CERT Recently Published Vulnerability Notes
Apple Machine Learning Research
Apple Machine Learning Research
U
Unit 42
PCI Perspectives
PCI Perspectives
博客园 - 聂微东
SecWiki News
SecWiki News
宝玉的分享
宝玉的分享
Forbes - Security
Forbes - Security
H
Heimdal Security Blog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Hugging Face - Blog
Hugging Face - Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
T
Troy Hunt's Blog
博客园 - 三生石上(FineUI控件)
Application and Cybersecurity Blog
Application and Cybersecurity Blog
罗磊的独立博客
WordPress大学
WordPress大学
D
Darknet – Hacking Tools, Hacker News & Cyber Security

Dmitri Pavlutin Blog

Pure Functions in JavaScript: A Beginner's Guide Record Type in TypeScript: A Quick Intro How to Write Comments in React: The Good, the Bad and the Ugly React forwardRef(): How to Pass Refs to Child Components TypeScript Function Types: A Beginner's Guide How to Use v-model to Access Input Values in Vue Mastering Vue refs: From Zero to Hero Environment Variables in JavaScript: process.env 5 Must-Know Differences Between ref() and reactive() in Vue How to Destructure Props in Vue (Composition API) Triangulation in Test-Driven Development How to Use nextTick() in Vue Programming to Interface Vs to Implementation A Smarter JavaScript Mapper: array.flatMap() Array Grouping in JavaScript: Object.groupBy() How to Access ES Module Metadata using import.meta JSON Modules in JavaScript How to Trim Strings in JavaScript TypeScript Function Overloading How to Debounce and Throttle Callbacks in Vue How to Show/Hide Elements in Vue Sparse vs Dense Arrays in JavaScript How to Fill an Array with Initial Values in JavaScript Covariance and Contravariance in TypeScript What are Higher-Order Functions in JavaScript? How to Use TypeScript with React Components Index Signatures in TypeScript How to Use React useReducer() Hook unknown vs any in TypeScript A Guide to React Context and useContext() Hook How to Use Promise.any() 2 Ways to Remove a Property from an Object in JavaScript 'return await promise' vs 'return promise' in JavaScript How to Use Promise.allSettled() How to Use fetch() with JSON JavaScript Promises: then(f,f) vs then(f).catch(f) What is a Promise in JavaScript? How to Use Promise.all() A Simple Guide to Component Props in React Don't Stop Me Now: How to Use React useTransition() hook A Simple Explanation of JavaScript Variables: const, let, var ES Modules Dynamic Import How to Memoize with React.useMemo() How to Cleanup Async Effects in React Why Math.max() Without Arguments Returns -Infinity How to Debounce and Throttle Callbacks in React Don't Confuse Function Expressions and Function Declarations in JavaScript How to Use ES Modules in Node.js Solving a Mystery Behavior of parseInt() in JavaScript How to Use Array Reduce Method in JavaScript 3 Ways to Merge Arrays in JavaScript A Guide to Jotai: the Minimalist React State Management Library The Difference Between Values and References in JavaScript How to Implement a Queue in JavaScript A Helpful Algorithm to Determine "this" value in JavaScript React useRef() Hook Explained in 3 Steps 7 Interview Questions on "this" keyword in JavaScript. Can You Answer Them? How to Greatly Enhance fetch() with the Decorator Pattern 7 Interview Questions on JavaScript Closures. Can You Answer Them? What's a Method in JavaScript? array.sort() Does Not Simply Sort Numbers in JavaScript How to Solve the Infinite Loop of React.useEffect() The New Array Method You'll Enjoy: array.at(index) What's the Difference between DOM Node and Element? Why Promises Are Faster Than setTimeout()? Everything About Callback Functions in JavaScript How React Updates State 5 Mistakes to Avoid When Using React Hooks 5 Best Practices to Write Quality JavaScript Variables Type checking in JavaScript: typeof and instanceof operators 3 Ways to Check if a Variable is Defined in JavaScript React Forms Tutorial: Access Input Values, Validate, Submit Forms Prototypal Inheritance in JavaScript How to Timeout a fetch() Request How to Learn JavaScript If You're a Beginner A Simple Explanation of React.useEffect() A Simple Explanation of JavaScript Iterators How to Use React Controlled Inputs Everything about null in JavaScript How to Use Fetch with async/await Getting Started with Arrow Functions in JavaScript An Interesting Explanation of async/await in JavaScript Front-end Architecture: Stable and Volatile Dependencies Is it Safe to Compare JavaScript Strings? How to Access Object's Keys, Values, and Entries in JavaScript What Actually is a String in JavaScript? 3 Ways to Shallow Clone Objects in JavaScript (w/ bonuses) Checking if an Array Contains a Value in JavaScript JavaScript Event Delegation: A Beginner's Guide How to Parse URL in JavaScript: hostname, pathname, query, hash 3 Ways to Detect an Array in JavaScript How to Get the Screen, Window, and Web Page Sizes in JavaScript 3 Ways to Check If an Object Has a Property/Key in JavaScript How to Compare Objects in JavaScript Object.is() vs Strict Equality Operator in JavaScript Own and Inherited Properties in JavaScript 5 Differences Between Arrow and Regular Functions How to Use Object Destructuring in JavaScript Your Guide to React.useCallback() 5 JavaScript Scope Gotchas
4 Ways to Create an Enum in JavaScript
Dmitri Pavlutin · 2023-04-14 · via Dmitri Pavlutin Blog

Strings and numbers have an infinite set of values, while others types like booleans are restricted to a finite set.

The days of the week (Monday, Tuesday, ..., Sunday), seasons of the year (winter, spring, summer, autumn), and cardinal directions (north, east, south, west) are examples of sets with finite values.

Using an enum is convenient when a variable has a value from a finite set of predefined constants. The enum saves you from using magic numbers and strings (which is considered an antipattern).

Let's see the 4 good ways to create enums in JavaScript (with their pros and cons).

Table of Contents

  • 1. Enum based on a plain object
  • 2. Enum value types
  • 3. Enum based on Object.freeze()
  • 4. Enum based on a proxy
  • 5. Enum based on a class
  • 6. Conclusion

1. Enum based on a plain object

An enum is a data structure that defines a finite set of named constants. Each constant can be accessed by its name.

Let's consider the sizes of a T-shirt: Small, Medium, and Large.

A simple way (though not the most optimal, see the approaches below) to create an enum in JavaScript is to use a plain JavaScript object.


const Sizes = {

Small: 'small',

Medium: 'medium',

Large: 'large',

}

const mySize = Sizes.Medium

console.log(mySize === Sizes.Medium) // logs true


Sizes is an enum based on a plain JavaScript object which has 3 named constants: Sizes.Small, Sizes.Medium, and Sizes.Large.

Sizes is also a string enum because the values of the named constants are strings: 'small', 'medium', and 'large'.

Sizes Enum

To access the named constant value use the property accessor. For example the value of Sizes.Medium is 'medium'.

The enum is more readable, more explicit, and removes the use of magic strings or numbers.

Pros and cons

The plain object enum is attractive because of its simplicity: just define an object with keys and values, and the enum is ready.

But in a large codebase someone might accidentally modify the enum object and this will affect the runtime of the application.


const Sizes = {

Small: 'small',

Medium: 'medium',

Large: 'large',

}

const size1 = Sizes.Medium

const size2 = Sizes.Medium = 'foo' // Changed!

console.log(size1 === Sizes.Medium) // logs false


Open the demo.

Sizes.Medium enum value was accidentally changed.

size1, while being initialized with Sizes.Medium, no longer equals Sizes.Medium!

The plain object implementation is not protected from such accidental changes.

Let's take a closer look at string and symbol enums. And then how to freeze the enum object to avoid the accidental change problem.

2. Enum value types

Besides the string type, the value of an enum can be a number:


const Sizes = {

Small: 0,

Medium: 1,

Large: 2

}

const mySize = Sizes.Medium

console.log(mySize === Sizes.Medium) // logs true


Open the demo.

Sizes enum in the above example is an numeric enum because the values are numbers: 0, 1, 2.

You can also create a symbol enum:


const Sizes = {

Small: Symbol('small'),

Medium: Symbol('medium'),

Large: Symbol('large')

}

const mySize = Sizes.Medium

console.log(mySize === Sizes.Medium) // logs true


Open the demo.

The benefit of using a symbol is that each symbol is unique. This means that you will always have to compare enums by using the enum itself:


const Sizes = {

Small: Symbol('small'),

Medium: Symbol('medium'),

Large: Symbol('large')

}

const mySize = Sizes.Medium

console.log(mySize === Sizes.Medium) // logs true

console.log(mySize === Symbol('medium')) // logs false


Open the demo.

The downside of using symbol enum is that JSON.stringify() stringifies symbols to either null, undefined, or skips the property having a symbol as a value:


const Sizes = {

Small: Symbol('small'),

Medium: Symbol('medium'),

Large: Symbol('large')

}

const str1 = JSON.stringify(Sizes.Small)

console.log(str1) // logs undefined

const str2 = JSON.stringify([Sizes.Small])

console.log(str2) // logs '[null]'

const str3 = JSON.stringify({ size: Sizes.Small })

console.log(str3) // logs '{}'


Open the demo.

In the following examples, I will use string enums. But you are free to use whatever value type you need.

If you are free to choose the enum value type, just go with the strings. Strings are easier to debug than numbers and symbols.

3. Enum based on Object.freeze()

A good way to protect the enum object from modifications is to freeze it. When an object is frozen, you cannot modify or add new properties to the object. In other words, the object becomes read-only.

In JavaScript, Object.freeze() utility function freezes an object. Let's freeze the sizes enum:


const Sizes = Object.freeze({

Small: 'small',

Medium: 'medium',

Large: 'large',

})

const mySize = Sizes.Medium

console.log(mySize === Sizes.Medium) // logs true


Open the demo.

const Sizes = Object.freeze({ ... }) creates a frozen object. Even being frozen, you can freely access the enum values: const mySize = Sizes.Medium.

Pros and cons

If an enum property has been accidentally changed, JavaScript throws an error (in strict mode):


const Sizes = Object.freeze({

Small: 'Small',

Medium: 'Medium',

Large: 'Large',

})

const size1 = Sizes.Medium

const size2 = Sizes.Medium = 'foo' // throws TypeError


Open the demo.

The statement const size2 = Sizes.Medium = 'foo' makes an accidental assignment to Sizes.Medium property.

Because Sizes is a frozen object, JavaScript (in strict mode) throws the error:


TypeError: Cannot assign to read only property 'Medium' of object <Object>


The frozen object enum is protected from accidental changes.

Still, there's another issue. If you accidentally misspell the enum constant, then the result will be undefined:


const Sizes = Object.freeze({

Small: 'small',

Medium: 'medium',

Large: 'large',

})

console.log(Sizes.Med1um) // logs undefined


Open the demo.

Sizes.Med1um expression (Med1um is a misspelled version of Medium) evaluates to undefined rather than throwing an error about the non-existent enum constant.

Let's see how a proxy-based enum can solve even this problem.

4. Enum based on a proxy

An interesting, and my favourite implementation, are enums based on proxies.

A proxy is a special object that wraps an object to modify the behaviour of operations on the original object. The proxy doesn't change the structure of the original object.

The enum proxy intercepts the read and write operations on an enum object and:

  • Throws an error when a non-existent enum value is accessed
  • Throws an error when an enum object property is changed

Here's an implementation of a factory function that accepts a plain enum object, and returns a proxied object:


// enum.js

export function Enum(baseEnum) {

return new Proxy(baseEnum, {

get(target, name) {

if (!baseEnum.hasOwnProperty(name)) {

throw new Error(`"${name}" value does not exist in the enum`)

}

return baseEnum[name]

},

set(target, name, value) {

throw new Error('Cannot add a new value to the enum')

}

})

}


get() method of the proxy intercepts the read operations and throws an error if the property name doesn't exist.

set() method intercepts the write operations and just throws an error. It's designed to protect the enum object from write operations.

Let's wrap the sizes object enum into a proxy:


import { Enum } from './enum'

const Sizes = Enum({

Small: 'small',

Medium: 'medium',

Large: 'large',

})

const mySize = Sizes.Medium

console.log(mySize === Sizes.Medium) // logs true


Open the demo.

The proxied enum works exactly like the plain object enum.

Pros and cons

However, the proxied enum is protected from accidental overwriting or accessing non-existent enum constants:


import { Enum } from './enum'

const Sizes = Enum({

Small: 'small',

Medium: 'medium',

Large: 'large',

})

const size1 = Sizes.Med1um // throws Error: non-existing constant

const size2 = Sizes.Medium = 'foo' // throws Error: changing the enum


Open the demo.

Sizes.Med1um throws an error because Med1um constat name does not exist on the enum.

Sizes.Medium = 'foo' throws an error because the enum property has been changed.

The downside of proxy enum is that you always have to import the Enum factory function and wrap your enum objects into it.

5. Enum based on a class

Another interesting way to create an enum is using a JavaScript class.

A class-based enum contains a set of static fields, where each static field represents an enum named constant. The value of each enum constant is itself an instance of the class.

Let's implement the sizes enum using a class Sizes:


class Sizes {

static Small = new Sizes('small')

static Medium = new Sizes('medium')

static Large = new Sizes('large')

#value

constructor(value) {

this.#value = value

}

toString() {

return this.#value

}

}

const mySize = Sizes.Small

console.log(mySize === Sizes.Small) // logs true

console.log(mySize instanceof Sizes) // logs true


Open the demo.

Sizes is a class that represents the enum. The enum constants are static fields on the class, e.g. static Small = new Sizes('small').

Each instance of the Sizes class also has a private field #value, which represents the raw value of the enum.

A nice advantage of the class-based enum is the ability to determine at runtime whether the value is an enum using instanceof operation. For example, mySize instanceof Sizes evaluates to true, since mySize is an enum value.

The class-based enum comparison is instance-based (rather primitive comparison in the case of plain, frozen, or proxied enums):


class Sizes {

static Small = new Sizes('small')

static Medium = new Sizes('medium')

static Large = new Sizes('large')

#value

constructor(value) {

this.#value = value

}

toString() {

return this.#value

}

}

const mySize = Sizes.Small

console.log(mySize === new Sizes('small')) // logs false


Open the demo.

mySize (that has Sizes.Small) isn't equal to new Sizes('small').

Sizes.Small and new Sizes('small'), even having the same #value, are different object instances. See more about referential equality.

Pros and cons

Class-based enums are not protected from overwriting or accessing a non-existent enum named constant.


class Sizes {

static Small = new Sizes('small')

static Medium = new Sizes('medium')

static Large = new Sizes('large')

#value

constructor(value) {

this.#value = value

}

toString() {

return this.#value

}

}

const size1 = Sizes.medium // a non-existing enum value can be accessed

const size2 = Sizes.Medium = 'foo' // enum value can be overwritten accidentally


Open the demo.

But you can control the creation of new instances, for example, by counting how many instances have been created inside the constructor. Then throw an error if more than 3 instances were created.

Of course, it's better to keep your enum implementation as simple as possible. Enums are meant to be plain data structures.

6. Conclusion

There are 4 good ways to create enums in JavaScript.

The simplest way is to use a plain JavaScript object:


const MyEnum = {

Option1: 'option1',

Option2: 'option2',

Option3: 'option3'

}


The plain object enum fits small projects or quick demos.

The second option, if you want to protect the enum object from accidental overwriting, is a frozen plain object:


const MyEnum = Object.freeze({

Option1: 'option1',

Option2: 'option2',

Option3: 'option3'

})


The frozen object enum is good for medium or large projects where you want to be sure the enum won't be changed accidentally.

The third option is the proxy approach:


export function Enum(baseEnum) {

return new Proxy(baseEnum, {

get(target, name) {

if (!baseEnum.hasOwnProperty(name)) {

throw new Error(`"${name}" value does not exist in the enum`)

}

return baseEnum[name]

},

set(target, name, value) {

throw new Error('Cannot add a new value to the enum')

}

})

}



import { Enum } from './enum'

const MyEnum = Enum({

Option1: 'option1',

Option2: 'option2',

Option3: 'option3'

})


The proxied enum works for medium or large projects to protect even better your enums from overwriting or accessing non-existent named constants.

The proxied enum is my personal preference.

The fourth option is to use the class-based enum, where each named constant is an instance of the class and is stored as a static property of the class:


class MyEnum {

static Option1 = new MyEnum('option1')

static Option2 = new MyEnum('option2')

static Option3 = new MyEnum('option3')

#value

constructor(value) {

this.#value = value

}

toString() {

return this.#value

}

}


The class-based enum works if you like classes. However, the class-based enum is less protected than frozen or proxied enums.

What other ways to create enums in JavaScript do you know?