Kiểu symbol

Các symbol là các giá trị cơ sở được tạo thông qua hàm Symbol():

const mySymbol = Symbol('mySymbol');

Tham số là không bắt buộc và cung cấp mô tả cho symbol chỉ được dùng khi gỡ lỗi.

Mặt khác, symbol lại giống đối tượng vì được tạo qua Symbol(), là duy nhất và không được so sánh bởi giá trị:

> Symbol() === Symbol()
false

Chúng lại có những đặc tính của giá trị cơ sở, typeof coi chúng có kiểu 'symbol' (không phải đối tượng) và một symbol có thể dùng làm key của thuộc tính trong đối tượng:

const obj = {
  [sym]: 123,
};

Các trường hợp sử dụng symbol

Hai trường hợp chính mà symbol được sử dụng là:

  • Làm giá trị của một hằng.
  • Làm key của một thuộc tính duy nhất.

Làm giá trị của một hằng

Giả sử bạn muốn tạo các hằng biểu diễn màu đỏ, cam, vàng, xanh lá, xanh dương và tím. Một cách đơn giản là sử dụng chuỗi:

const COLOR_BLUE = 'Blue';

Điểm cộng là khi log các hằng này ra ta nhận được thông tin hữu ích. Điểm trừ là dễ bị gõ nhầm. Hai hằng đáng ra khác nhau lại trở thành bằng nhau:

const MOOD_BLUE = 'Blue';
assert.equal(COLOR_BLUE, MOOD_BLUE);

Chúng ta có thể sửa lại bằng symbol:

const COLOR_BLUE = Symbol('Blue');
const MOOD_BLUE = Symbol('Blue');

assert.notEqual(COLOR_BLUE, MOOD_BLUE);

Ta sẽ sử dụng các hằng mang giá trị symbol để thực thi một hàm:

const COLOR_RED    = Symbol('Red');
const COLOR_ORANGE = Symbol('Orange');
const COLOR_YELLOW = Symbol('Yellow');
const COLOR_GREEN  = Symbol('Green');
const COLOR_BLUE   = Symbol('Blue');
const COLOR_VIOLET = Symbol('Violet');

function getComplement(color) {
  switch (color) {
    case COLOR_RED:
      return COLOR_GREEN;
    case COLOR_ORANGE:
      return COLOR_BLUE;
    case COLOR_YELLOW:
      return COLOR_VIOLET;
    case COLOR_GREEN:
      return COLOR_RED;
    case COLOR_BLUE:
      return COLOR_ORANGE;
    case COLOR_VIOLET:
      return COLOR_YELLOW;
    default:
      throw new Exception('Unknown color: '+color);
  }
}
assert.equal(getComplement(COLOR_YELLOW), COLOR_VIOLET);

Làm key của một thuộc tính

Key của một thuộc tính (trường) trong các đối tượng nó được dùng với hai mức độ:

  • Chỉ dùng trong chương trình hiện tại. Mức độ này gọi là base level.
  • Dùng bởi JavaScript hoặc dùng trong nhiều chương trình khác. Mức độ này gọi là meta-level.

Đoạn mã sau mô tả hai mục đích này:

const pt = {
  x: 7,
  y: 4,
  toString() {
    return `(${this.x}, ${this.y})`;
  },
};
assert.equal(String(pt), '(7, 4)');

Thuộc tính .x.y chỉ được dùng trong chương trình. Trong khi phương thức .toString() được dùng bởi JavaScript hoặc các chương trình khác có sử dụng đối tượng pt để chuyển đổi đối tượng này thành chuỗi.

Ở mức meta-level tên thuộc tính không được trùng với các thuộc tính ở mức base level khác. Điều đó có nghĩa là key của thuộc tính meta-level không được phép trùng lặp với các thuộc tính base level.

Lúc này symbol được sử dụng làm tên thuộc tính: Mỗi symbol luôn là duy nhất nên tên thuộc tính không bao giờ xung đột với các thuộc tính khác.

Publicly known symbols

Các symbol đóng vai trò đặc biệt trong ECMAScript được gọi là publicly known symbols (các symbol công khai). Ví dụ:

  • Symbol.iterator: tạo một đối tượng iterable. Nó là tên của một phương thức trả về một iterator.
  • Symbol.hasInstance: tùy chỉnh cách instanceof làm việc. Nếu một đối tượng thực thi phương thức có tên này, nó có thể dùng ở vế phải của toán tử instanceof. Ví dụ:
const PrimitiveNull = {
  [Symbol.hasInstance](x) {
    return x === null;
  }
};
assert.equal(null instanceof PrimitiveNull, true);
  • Symbol.toStringTag: ảnh hướng đến phương thức .toString() mặc định.
> String({})
'[object Object]'
> String({ [Symbol.toStringTag]: 'is no money' })
'[object is no money]'