Symbol trong JavaScript

Symbol trong JavaScript

Giới thiệu

Symbol là một kiểu dữ liệu nguyên thủy của JavaScript, cùng với string, number, boolean, null và undefined. Nó được giới thiệu lần đầu tiên tại ECMAScript 2015 (ES6) một vài năm về trước. Nó có một kiểu dữ liệu rất đặc biệt. Khi bạn tạo một Symbol trong JavaScript, giá trị của nó được giữ kín và chỉ để sử dụng nội bộ.


Tại sao lại có kiểu dữ liệu Symbol?

Có 3 lý do chính:

1. Thêm một core-feature mới với khả năng tương thích ngược

Đôi khi chúng ta cần thêm một thuộc tính mới vào đối tượng hiện tại mà không muốn gây ảnh hưởng tới vòng lặp for in hay hàm Object.keys.

Ví dụ: Cho một đối tượng: var myObject = {firstName:'raja', lastName:'rao'}. Khi gọi hàm Object.keys(myObject), kết quả trả về là [firstName, lastName]. Giờ nếu thêm một thuộc tính mới có tên là newProperty vào myObject và mong muốn kết quả trả về của hàm Object.keys(myObject) vẫn phải là [firstName, lastName], chứ không phải [firstName, lastName, newProperty] thì làm như thế nào?

Nếu newProperty là một symbol thì Object.keys(myObject) sẽ bỏ qua thuộc tính này và kết quả trả về vẫn là [firstName, lastName] .

2. Tránh xung đột về tên gọi

Symbol đảm bảo rằng các thuộc tính mới được thêm vào một object là duy nhất.

Ví dụ: Khi thêm một phương thức tùy biến toUpperCase vào Array.prototype. Sau đó, nếu load một thư viện mới (hoặc trong ES2019 sắp tới) và nó có một phiên bản khác của Array.prototype.toUpperCase thì phương thức tùy biến có thể hoạt động không chính xác vì xung đột tên.

Symbol trong JavaScript

Symbol trong JavaScript sẽ giải quyết vấn đề này bằng cách tạo ngầm một giá trị duy nhất khi thêm những thuộc tính mới.

3. Tạo hook đối với những phương thức core thông qua các symbol “phổ biến”

Giả sử chúng ta muốn String.prototype.search gọi đến phương thức search tùy biến bên trong một đối tượng myObject‘somestring’.search(myObject); và truyền ‘somestring’ như một tham số.

Điều này có thể được thực hiện thông qua các symbol toàn cục có tên là symbol phổ biến. Chúng cho phép tạo lời gọi đến các hàm bất kỳ bên trong các hàm core.


Tạo Symbol

Một symbol có thể được tạo bằng cách sử dụng từ khóa Symbol. Cách khởi tạo này sẽ trả về một giá trị có kiểu dữ liệu là symbol.

Symbol trong JavaScript 2

Lưu ý: dù cách khởi tạo như một đối tượng nhưng thực chất symbol là một giá trị nguyên thủy. Chúng bất biến và có tính duy nhất.

Symbol không thể tạo bằng từ khóa new

Bởi symbol không phải là object nên không thể sử dụng từ khóa new để trả về một giá trị kiểu symbol.

var mySymbol = new Symbol(); //throws error

Symbol có phần mô tả cho việc logging

//mySymbol variable now holds a "symbol" unique value
//its description is "some text"
const mySymbol = Symbol('some text');

Symbol có tính duy nhất

const mySymbol1 = Symbol('some text');
const mySymbol2 = Symbol('some text');
mySymbol1 == mySymbol2 // false

Symbol có thể được coi là một singleton nếu sử dụng phương thức Symbol.for

Thay vì khởi tạo một symbol thông qua Symbol(), một symbol có thể tạo thông qua Symbol.for(<key>). Trong đó, key là một string bất kỳ. Nếu một symbol có key đã tồn tại thì việc sử dụng phương thức trên sẽ chỉ trả về giá trị cũ.

var mySymbol1 = Symbol.for('some key'); //creates a new symbol
var mySymbol2 = Symbol.for('some key'); // **returns the same symbol
mySymbol1 == mySymbol2 //true

Mục đính thực sự của việc sử dụng .for là để tạo Symbol ở một nơi và truy cập ở một nơi khác.

Lưu ý: Symbol.for sẽ khiến symbol không còn tính duy nhất. Dó đó tránh việc sử dụng các key giống nhau.

Mô tả của symbol khác với key của symbol

Mô tả của symbol vẫn đảm bảo tính duy nhất. Còn Symbol.for thì không.

Symbol trong JavaScript 3

Symbol có thể là tên thuộc tính của một đối tượng

Chính vì tính duy nhất nên một symbol có thể được gán như tên của một thuộc tính bên trong một đối tượng. Những thuộc tính này còn được gọi là keyed properties

Symbol trong JavaScript 4

Truy cập vào thuộc tính có tên sử dụng symbol

Thuộc tính sử dụng symbol chỉ có thể sử dụng thông qua ngoặc vuông và không thể sử dụng dấu chấm.

Symbol trong JavaScript 5

Nhìn lại 3 lý do chính cho việc sử dụng Symbol

1. Symbol sẽ bị bỏ qua đối với các vòng lặp và một số hàm đọc thuộc tính

Trong ví dụ dưới đây, khi duyệt qua các thuộc tính của obj thì các thuộc tính prop3 và prop4 sẽ bị bỏ qua vì chúng có kiểu dữ liệu symbol.

Symbol trong JavaScript 6

Tương tự với các phương thức Object.keys và Object.getOwnPropertyNames

Symbol trong JavaScript 7

2. Symbol có tính duy nhất

Giả sử chúng ta muốn thêm một tính năng mới vào đối tượng Array có tên là includes. Thay vì đặt một tên gọi kiểu như custom_includes thì làm như nào để có thể thêm trực tiếp phương thức này mà tránh được việc xung đột với Array.prototype.includes đã có sẵn?

Đầu tiên, tạo một biến có tên chính xác là includes và gán một symbol cho nó. Sau đố thêm biến này vào đối tượng Array sử dụng giống ngoặc vuông và gán bất kỳ hàm nào mong muốn.

Cuối cùng sử dụng hàm trên bằng dấu ngoặc vuông. Nhưng chú ý là phải sử dụng đúng tên biến bên trong dấu ngoặc vuông arr[includes](), chứ không phải một chuỗi ký tự.

Symbol trong JavaScript 8

3. Symbol “phổ biến” (symbol “toàn cục”)

Javascript đã có sẵn một số biến symbol và gán chúng vào đối tượng toàn cục Symbol. Ví dụ: Đối với object String trong ES2015: Symbol.matchSymbol.replaceSymbol.searchSymbol.iterator và Symbol.split.


Một ví dụ về Symbol trong JavaScript: Symbol.search

Phương thức pubic String.prototype.search được dùng để tìm kiếm trong chuỗi ký tự dựa vào từ khóa hoặc một biểu thức chính quy và trả về vị trí tìm thấy.

Symbol trong JavaScript 9

Trong ES2015, phương thức này sẽ kiểm tra xem có phương thức Symbol.search đã được định nghĩa bên trong đối tượng RegExp. Nếu có thì sẽ gọi đến hàm này và ủy quyền cho nó. Do đó, thực tế thì trong phép tìm kiếm đầu tiên ở ví dụ trên, phương thức có symbol là Symbol.search của đối tượng RegExp sẽ có nhiệm vụ chính trong việc tìm kiếm.

Cách thức hoạt động bên trong của Symbol.search (Mặc định)

  1. Phân tích ‘rajarao’.search(‘rao’);
  2. Chuyển “rajarao” thành một đối tượng String new String(“rajarao”)
  3. Chuyển “rao” thành một đối tượng RegExp new Regexp(“rao”)
  4. Gọi hàm search của String object rajarao
  5. Hàm search gọi tới phương thức Symbol.search của đối tượng “rao” và gán việc tìm kiếm cho đối tượng “rao” với tham số truyền vào là “rajarao”. Giống như sau: "rao"[Symbol.search]("rajarao")
  6. "rao"[Symbol.search]("rajarao") trả về vị trí (index) là 4 cho hàm search và cuối cùng hàm search lại trả về giá trị 4 cho đoạn code hiện tại. Nhưng điều hay ho ở đây là chúng ta có thể truyền vào bất cứ đối tượng nào vào hàm search của đối tượng String, không chỉ có mỗi RegExp. Kết quả của hàm search sẽ phụ thuộc vào việc định nghĩa phương thức Symbol.search của đối tượng được truyền vào.
Symbol trong JavaScript 10

Tùy biến lại hàm String.search theo một hàm bất kỳ

Symbol trong JavaScript 11
  1. Phân tích ‘barsoap’.search(soapObj);
  2. Chuyển “barsoap” thành một đối tượng String new String(“barsoap”)
  3. Vì soapObj đã là một đối tượng rồi nên không cần bất kỳ phép chuyển đổi nào khác
  4. Gọi hàm search của đối tượng String “barsoap”
  5. Hàm search gọi tới phương thức Symbol.search của đối tượng soapObj và gán việc tìm kiếm cho đối tượng ‘soapObj’ với tham số truyền vào là “barsoap”. Giống như sau: soapObj[Symbol.search]("barsoap")
  6. Kết quả trả về là FOUND

Tham khảo:

https://viblo.asia/p/symbols-iterators-trong-javascript-YWOZrgOYlQ0

https://medium.freecodecamp.org/some-of-javascripts-most-useful-features-can-be-tricky-let-me-explain-them-4003d7bbed32

Các bạn có thể tham khảo các bài viết hay về JavaScript tại đây.


Hãy tham gia nhóm Học lập trình để thảo luận thêm về các vấn đề cùng quan tâm.

Bình luận