NỘI DUNG BÀI VIẾT
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 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
.
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 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
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.
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.
Tương tự với các phương thức Object.keys
và Object.getOwnPropertyNames
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ự.
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.match
, Symbol.replace
, Symbol.search
, Symbol.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.
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)
- Phân tích
‘rajarao’.search(‘rao’);
- Chuyển “rajarao” thành một đối tượng String
new String(“rajarao”)
- Chuyển “rao” thành một đối tượng RegExp
new Regexp(“rao”)
- Gọi hàm
search
của String objectrajarao
- Hàm
search
gọi tới phương thứcSymbol.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")
"rao"[Symbol.search]("rajarao")
trả về vị trí (index) là 4 cho hàmsearch
và cuối cùng hàmsearch
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àmsearch
của đối tượng String, không chỉ có mỗi RegExp. Kết quả của hàmsearch
sẽ phụ thuộc vào việc định nghĩa phương thứcSymbol.search
của đối tượng được truyền vào.
Tùy biến lại hàm String.search theo một hàm bất kỳ
- Phân tích
‘barsoap’.search(soapObj);
- Chuyển “barsoap” thành một đối tượng String
new String(“barsoap”)
- 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 - Gọi hàm
search
của đối tượng String “barsoap” - Hàm
search
gọi tới phương thứcSymbol.search
của đối tượngsoapObj
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")
- 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.