4 cách để tạo Enum trong JavaScript

Tìm hiểu 4 cách khác nhau để tạo enum trong JavaScript, bao gồm sử dụng từ khóa enum, đối tượng đơn giản hoặc ký hiệu. Bài viết này sẽ giúp bạn chọn cách tốt nhất để tạo enum cho dự án của bạn.

Chuỗi và số có tập hợp giá trị vô hạn, trong khi các loại khác như booleans bị giới hạn ở tập hợp hữu hạn.

Các ngày trong tuần (Thứ Hai, Thứ Ba, ..., Chủ Nhật), các mùa trong năm (đông, xuân, hạ, thu) và các hướng chính (bắc, đông, nam, tây) là ví dụ về các tập hợp có giá trị hữu hạn .

Việc sử dụng enum rất thuận tiện khi một biến có giá trị từ một tập hữu hạn các hằng số được xác định trước. Enum giúp bạn tránh khỏi việc sử dụng các số và chuỗi ma thuật (được coi là phản mẫu ).

Hãy xem 4 cách hay để tạo enum trong JavaScript (cùng với ưu và nhược điểm của chúng).

Mục lục

  • 1. Enum dựa trên một đối tượng đơn giản
  • 2. Các loại giá trị enum
  • 3. Enum dựa trên Object.freeze()
  • 4. Enum dựa trên proxy
  • 5. Enum dựa trên một lớp
  • 6. Kết luận

1. Enum dựa trên một đối tượng đơn giản

Enum là cấu trúc dữ liệu xác định một tập hợp hữu hạn các hằng số được đặt tên. Mỗi hằng số có thể được truy cập bằng tên của nó.

Hãy xem xét kích thước của một chiếc áo phông: Small, Medium, và Large.

Một cách đơn giản (mặc dù không phải là tối ưu nhất, hãy xem các cách tiếp cận bên dưới) để tạo enum trong JavaScript là sử dụng một đối tượng JavaScript đơn giản .

const Sizes = {
  Small: 'small',
  Medium: 'medium',
  Large: 'large',
}

const mySize = Sizes.Medium

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

Sizeslà một enum dựa trên một đối tượng JavaScript đơn giản có 3 hằng số được đặt tên: Sizes.Small, Sizes.MediumSizes.Large.

Sizescũng là một chuỗi enum vì giá trị của các hằng số được đặt tên là các chuỗi: 'small', 'medium', và 'large'.

Kích thước Enum

Để truy cập giá trị không đổi được đặt tên, hãy sử dụng trình truy cập thuộc tính. Ví dụ giá trị của Sizes.Medium'medium'.

Enum dễ đọc hơn, rõ ràng hơn và loại bỏ việc sử dụng các chuỗi hoặc số ma thuật.

Ưu và nhược điểm

Đối tượng đơn giản enum rất hấp dẫn vì tính đơn giản của nó: chỉ cần xác định một đối tượng bằng khóa và giá trị, thế là enum đã sẵn sàng.

Nhưng trong một cơ sở mã lớn, ai đó có thể vô tình sửa đổi đối tượng enum và điều này sẽ ảnh hưởng đến thời gian chạy của ứng dụng.

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

Mở bản demo.

Sizes.Mediumgiá trị enum vô tình bị thay đổi.

size1, trong khi được khởi tạo bằng Sizes.Medium, không còn bằng Sizes.Medium!

Việc triển khai đối tượng đơn giản không được bảo vệ khỏi những thay đổi ngẫu nhiên như vậy.

Chúng ta hãy xem xét kỹ hơn về enum chuỗi và ký hiệu. Và sau đó làm thế nào để đóng băng đối tượng enum để tránh sự cố thay đổi ngẫu nhiên.

2. Các loại giá trị enum

Ngoài kiểu chuỗi, giá trị của enum có thể là một số:

const Sizes = {
  Small: 0,
  Medium: 1,
  Large: 2
}

const mySize = Sizes.Medium

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

Mở bản demo.

Sizesenum trong ví dụ trên là enum số vì các giá trị là số: 0, 1, 2.

Bạn cũng có thể tạo một biểu tượng enum:

const Sizes = {
  Small: Symbol('small'),
  Medium: Symbol('medium'),
  Large: Symbol('large')
}

const mySize = Sizes.Medium

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

Mở bản demo.

Lợi ích của việc sử dụng biểu tượng là mỗi biểu tượng là duy nhất. Điều này có nghĩa là bạn sẽ luôn phải so sánh các enum bằng cách sử dụng chính enum đó:

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

Mở bản demo.

Nhược điểm của việc sử dụng ký hiệu enum là JSON.stringify()xâu chuỗi các ký hiệu thành null, undefinedhoặc bỏ qua thuộc tính có ký hiệu làm giá trị:

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 '{}'

Mở bản demo.

Trong các ví dụ sau, tôi sẽ sử dụng enum chuỗi. Nhưng bạn có thể tự do sử dụng bất kỳ loại giá trị nào bạn cần.

Nếu bạn được tự do chọn loại giá trị enum, chỉ cần chọn chuỗi. Chuỗi dễ gỡ lỗi hơn số và ký hiệu.

3. Enum dựa trên Object.freeze()

Một cách tốt để bảo vệ đối tượng enum khỏi bị sửa đổi là đóng băng nó. Khi một đối tượng bị đóng băng, bạn không thể sửa đổi hoặc thêm thuộc tính mới vào đối tượng đó. Nói cách khác, đối tượng trở thành chỉ đọc.

Trong JavaScript, hàm tiện ích Object.freeze() đóng băng một đối tượng. Hãy đóng băng kích thước enum:

const Sizes = Object.freeze({
  Small: 'small',
  Medium: 'medium',
  Large: 'large',
})

const mySize = Sizes.Medium

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

Mở bản demo.

const Sizes = Object.freeze({ ... })tạo ra một vật thể đông cứng. Ngay cả khi bị đóng băng, bạn vẫn có thể thoải mái truy cập các giá trị enum: const mySize = Sizes.Medium.

Ưu và nhược điểm

Nếu một thuộc tính enum vô tình bị thay đổi, JavaScript sẽ báo lỗi (ở chế độ nghiêm ngặt ):

const Sizes = Object.freeze({
  Small: 'Small',
  Medium: 'Medium',
  Large: 'Large',
})

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

Mở bản demo.

Tuyên bố const size2 = Sizes.Medium = 'foo'thực hiện một sự chuyển nhượng ngẫu nhiên cho Sizes.Mediumtài sản.

Sizeslà một đối tượng bị đóng băng nên JavaScript (ở chế độ nghiêm ngặt ) sẽ đưa ra lỗi:

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

Đối tượng cố định enum được bảo vệ khỏi những thay đổi ngẫu nhiên.

Tuy nhiên, vẫn còn một vấn đề khác. Nếu bạn vô tình viết sai chính tả hằng enum thì kết quả sẽ là undefined:

const Sizes = Object.freeze({
  Small: 'small',
  Medium: 'medium',
  Large: 'large',
})

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

Mở bản demo.

Sizes.Med1umbiểu thức ( Med1umlà phiên bản sai chính tả của Medium) đánh giá undefinedthay vì đưa ra lỗi về hằng số enum không tồn tại.

Hãy xem enum dựa trên proxy có thể giải quyết vấn đề này như thế nào.

4. Enum dựa trên proxy

Một cách triển khai thú vị và được tôi yêu thích nhất là enum dựa trên proxy .

Proxy là một đối tượng đặc biệt bao bọc một đối tượng để sửa đổi hành vi hoạt động trên đối tượng ban đầu. Proxy không thay đổi cấu trúc của đối tượng ban đầu.

Proxy enum chặn các hoạt động đọc và ghi trên đối tượng enum và:

  • Đưa ra lỗi khi truy cập giá trị enum không tồn tại
  • Đưa ra lỗi khi thuộc tính đối tượng enum bị thay đổi

Đây là cách triển khai hàm xuất xưởng chấp nhận một đối tượng enum đơn giản và trả về một đối tượng được ủy quyền:

// 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()phương thức proxy chặn các hoạt động đọc và đưa ra lỗi nếu tên thuộc tính không tồn tại.

set()phương thức chặn các thao tác ghi và chỉ đưa ra một lỗi. Nó được thiết kế để bảo vệ đối tượng enum khỏi các thao tác ghi.

Hãy gói kích thước đối tượng enum vào một 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

Mở bản demo.

Enum proxy hoạt động chính xác như enum đối tượng đơn giản.

Ưu và nhược điểm

Tuy nhiên, enum được ủy quyền được bảo vệ khỏi việc ghi đè ngẫu nhiên hoặc truy cập vào các hằng số enum không tồn tại:

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

Mở bản demo.

Sizes.Med1umđưa ra lỗi vì Med1umtên constat không tồn tại trên enum.

Sizes.Medium = 'foo'đưa ra lỗi vì thuộc tính enum đã bị thay đổi.

Nhược điểm của proxy enum là bạn luôn phải nhập Enumhàm xuất xưởng và bọc các đối tượng enum của mình vào đó.

5. Enum dựa trên một lớp

Một cách thú vị khác để tạo enum là sử dụng lớp JavaScript.

Một enum dựa trên lớp chứa một tập hợp các trường tĩnh, trong đó mỗi trường tĩnh đại diện cho một enum có tên là hằng số. Giá trị của mỗi hằng số enum tự nó là một thể hiện của lớp.

Hãy triển khai enum kích thước bằng cách sử dụng một lớp 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

Mở bản demo.

Sizeslà một lớp đại diện cho enum. Các hằng số enum là các trường tĩnh trên lớp, ví dụ: static Small = new Sizes('small').

Mỗi phiên bản của Sizeslớp cũng có một trường riêng #value, đại diện cho giá trị thô của enum.

Một ưu điểm thú vị của enum dựa trên lớp là khả năng xác định trong thời gian chạy xem giá trị có phải là instanceofthao tác sử dụng enum hay không. Ví dụ: mySize instanceof Sizesđánh giá là true, vì mySizelà giá trị enum.

So sánh enum dựa trên lớp là dựa trên cá thể (so sánh khá nguyên thủy trong trường hợp enum đơn giản, cố định hoặc proxy):

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

Mở bản demo.

mySize(có Sizes.Small) không bằng new Sizes('small').

Sizes.Smallnew Sizes('small'), thậm chí có cùng #value, là các thể hiện đối tượng khác nhau. 

Ưu và nhược điểm

Các enum dựa trên lớp không được bảo vệ khỏi việc ghi đè hoặc truy cập vào một enum không tồn tại có tên là hằng số.

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

Mở bản demo.

Nhưng bạn có thể kiểm soát việc tạo các phiên bản mới, chẳng hạn bằng cách đếm xem có bao nhiêu phiên bản đã được tạo bên trong hàm tạo. Sau đó đưa ra lỗi nếu có hơn 3 phiên bản được tạo.

Tất nhiên, tốt hơn hết là bạn nên thực hiện enum đơn giản nhất có thể. Enums có nghĩa là cấu trúc dữ liệu đơn giản.

6. Kết luận

Có 4 cách hay để tạo enum trong JavaScript.

Cách đơn giản nhất là sử dụng một đối tượng JavaScript đơn giản:

const MyEnum = {
  Option1: 'option1',
  Option2: 'option2',
  Option3: 'option3'
}

Đối tượng đơn giản enum phù hợp với các dự án nhỏ hoặc các bản demo nhanh.

Tùy chọn thứ hai, nếu bạn muốn bảo vệ đối tượng enum khỏi việc vô tình ghi đè, là một đối tượng đơn giản bị đóng băng:

const MyEnum = Object.freeze({
  Option1: 'option1',
  Option2: 'option2',
  Option3: 'option3'
})

Đối tượng cố định enum rất phù hợp cho các dự án vừa hoặc lớn mà bạn muốn chắc chắn rằng enum sẽ không bị thay đổi một cách vô tình.

Tùy chọn thứ ba là cách tiếp cận proxy:

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'
})

Enum được ủy quyền hoạt động cho các dự án vừa hoặc lớn để bảo vệ tốt hơn nữa các enum của bạn khỏi bị ghi đè hoặc truy cập vào các hằng số được đặt tên không tồn tại.

Enum proxy là sở thích cá nhân của tôi.

Tùy chọn thứ tư là sử dụng enum dựa trên lớp, trong đó mỗi hằng số được đặt tên là một thể hiện của lớp và được lưu trữ dưới dạng thuộc tính tĩnh của lớp:

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
  }
}

Enum dựa trên lớp hoạt động nếu bạn thích các lớp. Tuy nhiên, enum dựa trên lớp ít được bảo vệ hơn enum đông lạnh hoặc proxy.

 

#javascript

1.40 GEEK