Điều gì xảy ra khi các đối tượng được thêm: obj1 + obj2
, trừ: obj1 - obj2
hoặc in bằng cách sử dụng alert(obj)
?
Trong trường hợp đó, các đối tượng được tự động chuyển đổi thành kiểu nguyên thủy, và sau đó thao tác được thực hiện.
Trong chương Chuyển đổi kiểu, chúng ta đã thấy các quy tắc để chuyển đổi số, chuỗi và boolean của kiểu nguyên thủy. Nhưng chúng ta chưa nói về các đối tượng. Bây giờ, như chúng ta đã biết về các phương thức và biểu tượng(symbols).
- Tất cả các đối tượng là
true
trong một bối cảnh boolean. Chỉ có chuyển đổi số và chuỗi. - Việc chuyển đổi số xảy ra khi chúng ta trừ các đối tượng hoặc áp dụng các hàm toán học. Chẳng hạn, các đối tượng
Date
(sẽ được đề cập trong chương Ngày và thời gian) có thể được trừ đi và kết quảdate1 - date2
là sự khác biệt về thời gian giữa hai ngày. - Đối với chuyển đổi chuỗi – nó thường xảy ra khi chúng ta xuất một đối tượng như
alert(obj)
và trong các bối cảnh tương tự.
Nội dung chính
1. Chuyển kiểu nguyên thuỷ
Chúng ta có thể tinh chỉnh chuyển đổi chuỗi và số, sử dụng các phương thức đối tượng đặc biệt.
Có ba biến thể của chuyển đổi kiểu, được gọi là “hints”, được mô tả trong mô tả ở đây
"string"
Đối với chuyển đổi đối tượng thành chuỗi, khi chúng ta thực hiện thao tác trên một đối tượng mong đợi nó thành một chuỗi, như alert
:
// output
alert(obj);
// using object as a property key
anotherObj[obj] = 123;
"number"
Đối với chuyển đổi đối tượng sang số, như khi chúng ta đang làm toán:
/*
Cafedev.vn - Kênh thông tin IT hàng đầu Việt Nam
@author cafedevn
Contact: cafedevn@gmail.com
Fanpage: https://www.facebook.com/cafedevn
Instagram: https://instagram.com/cafedevn
Twitter: https://twitter.com/CafedeVn
Linkedin: https://www.linkedin.com/in/cafe-dev-407054199/
*/
// explicit conversion
let num = Number(obj);
// maths (except binary plus)
let n = +obj; // unary plus
let delta = date1 - date2;
// less/greater comparison
let greater = user1 > user2;
"default"
Xảy ra trong những trường hợp hiếm hoi khi toán tử không chắc chắn kiểu dữ liệu nào nó mong đợi.
Chẳng hạn,cộng nhị phân +
có thể hoạt động cả với chuỗi (nối chúng) và số (thêm chúng), vì vậy cả chuỗi và số sẽ làm được. Vì vậy, nếu một cộng nhị phân lấy một đối tượng làm đối số, nó sử dụng gợi ý "default"
để chuyển đổi nó.
Ngoài ra, nếu một đối tượng được so sánh bằng cách sử dụng ==
với một chuỗi, số hoặc biểu tượng, thì cũng không rõ nên thực hiện chuyển đổi nào, vì vậy gợi ý "default"
được sử dụng.
// binary plus uses the "default" hint
let total = obj1 + obj2;
// obj == number uses the "default" hint
if (user == 1) { ... };
Các toán tử so sánh lớn hơn và ít hơn, chẳng hạn như <
>
, cũng có thể hoạt động với cả chuỗi và số. Tuy nhiên, họ sử dụng gợi ý "number"
, không dùng"default"
. Đó là vì lý do lịch sử.
Tuy nhiên, trên thực tế, chúng ta không cần phải nhớ các chi tiết đặc biệt này, bởi vì tất cả các đối tượng tích hợp ngoại trừ một trường hợp (đối tượng Date
, chúng ta sẽ tìm hiểu sau) thực hiện "default"
chuyển đổi theo cách tương tự "number"
. Và chúng ta có thể làm như vậy.
Không có gợi ý "boolean"
Xin lưu ý – chỉ có ba gợi ý.
Không có gợi ý về boolean (tất cả các đối tượng true
trong bối cảnh boolean) hoặc bất cứ điều gì khác.
Để thực hiện chuyển đổi, JavaScript cố gắng tìm và gọi ba phương thức đối tượng này:
- Gọi
obj[Symbol.toPrimitive](hint)
– phương thức có từ khoáSymbol.toPrimitive
(system symbol) - Nếu không, nếu gợi ý là
"string"
- Nó cố gắng
obj.toString()
vàobj.valueOf()
- Nó cố gắng
- Mặt khác nếu gợi ý là
"number"
hoặc"default"
- Nó cố gắng
obj.valueOf()
vàobj.toString()
- Nó cố gắng
2. Symbol.toPrimitive
Hãy bắt đầu từ phương pháp đầu tiên. Có một biểu tượng tích hợp có tên Symbol.toPrimitive
nên được sử dụng để đặt tên cho phương thức chuyển đổi, như sau:
obj[Symbol.toPrimitive] = function(hint) {
// must return a primitive value
// hint = one of "string", "number", "default"
};
Ví dụ, ở đây đối tượng user
thực hiện nó:
let user = {
name: "John",
money: 1000,
[Symbol.toPrimitive](hint) {
alert(`hint: ${hint}`);
return hint == "string" ? `{name: "${this.name}"}` : this.money;
}
};
// conversions demo:
alert(user); // hint: string -> {name: "John"}
alert(+user); // hint: number -> 1000
alert(user + 500); // hint: default -> 1500
Như chúng ta có thể thấy từ code, user
tự trở thành một chuỗi hoặc một số tùy thuộc vào chuyển đổi. Phương pháp duy nhất user[Symbol.toPrimitive]
xử lý tất cả các trường hợp chuyển đổi.
3. toString / valueOf
Phương pháp toString
và valueOf
đến từ thời cổ đại. Chúng không phải là các biểu tượng (các biểu tượng không tồn tại từ lâu), mà là các phương thức được đặt tên theo chuỗi thông thường. Họ cung cấp một cách khác thay thế theo kiểu cũ để thực hiện chuyển đổi.
Nếu không có Symbol.toPrimitive
thì JavaScript cố gắng tìm chúng và thử theo thứ tự:
toString -> valueOf
cho gợi ý chuỗi.valueOf -> toString
ngược lại.
Các phương thức này phải trả về một giá trị nguyên thủy. Nếu toString
hoặc valueOf
trả về một đối tượng, thì nó bị bỏ qua (giống như không có phương thức nào).
Theo mặc định, một đối tượng đơn giản có các phương thức toString
và valueOf
phương thức sau:
- Các phương thức
toString
trả về một chuỗi"[object Object]"
. - Các phương thức
valueOf
trả về đối tượng riêng của mình.
Đây là bản demo:
let user = {name: "David"};
alert(user); // [object Object]
alert(user.valueOf() === user); // true
Vì vậy, nếu chúng ta cố gắng sử dụng một đối tượng như một chuỗi, như trong một alert
hoặc như vậy, thì theo mặc định chúng ta thấy [object Object]
.
Và mặc định valueOf
được đề cập ở đây chỉ vì mục đích hoàn thành bài này, để tránh mọi sự nhầm lẫn. Như bạn có thể thấy, nó trả về chính đối tượng và do đó bị bỏ qua. Đừng hỏi tôi tại sao, đó là vì lý do lịch sử. Vì vậy, chúng ta có thể cho rằng nó không tồn tại.
Hãy thực hiện các phương pháp này.
Chẳng hạn, ở đây user
thực hiện tương tự như trên bằng cách sử dụng kết hợp toString
và valueOf
thay vì Symbol.toPrimitive
:
let user = {
name: "David",
money: 1000,
// for hint="string"
toString() {
return `{name: "${this.name}"}`;
},
// for hint="number" or "default"
valueOf() {
return this.money;
}
};
alert(user); // toString -> {name: "John"}
alert(+user); // valueOf -> 1000
alert(user + 500); // valueOf -> 1500
Như chúng ta có thể thấy, hành vi giống như ví dụ trước với Symbol.toPrimitive
.
Thông thường, chúng ta muốn có một địa điểm bắt tất cả các điểm khác để xử lý tất cả các chuyển đổi nguyên thủy. Trong trường hợp này, chúng ta chỉ có thể thực hiện toString
, như thế này:
let user = {
name: "David",
toString() {
return this.name;
}
};
alert(user); // toString -> John
alert(user + 500); // toString -> John500
Trong trường hợp không Symbol.toPrimitive
và valueOf
, toString
sẽ xử lý tất cả các chuyển đổi nguyên thủy.
4. Trả về các kiểu
Điều quan trọng cần biết về tất cả các phương thức chuyển đổi nguyên thủy là chúng không nhất thiết phải trả về nguyên thủy của gợi ý.
Không có kiểm soát cho dù toString
trả về chính xác một chuỗi, hoặc liệu phương thứcSymbol.toPrimitive
trả về một số cho một gợi ý "number"
.
Điều bắt buộc duy nhất: các phương thức này phải trả về một nguyên thủy, không phải là một đối tượng.
Ghi chú lịch sử
Vì các lý do lịch sử, nếu toString
hoặc valueOf
trả về một đối tượng, không có lỗi, nhưng giá trị đó bị bỏ qua (như nếu phương thức không tồn tại). Đó là bởi vì trong thời cổ đại, không có khái niệm lỗi nào trong JavaScript.
Ngược lại, Symbol.toPrimitive
phải trả về một kiểu nguyên thủy, nếu không sẽ có lỗi.
5. Một số chuyển đổi khác
Như chúng ta đã biết, nhiều toán tử và hàm thực hiện chuyển đổi kiểu, ví dụ phép nhân *
chuyển đổi toán hạng thành số.
Nếu chúng ta truyền một đối tượng làm đối số, thì có hai giai đoạn:
- Đối tượng được chuyển đổi thành kiểu nguyên thủy (sử dụng các quy tắc được mô tả ở trên).
- Nếu kết quả kiểu nguyên thủy không đúng kiểu, nó sẽ được chuyển đổi tiếp cho phù hợp.
Ví dụ:
let obj = {
// toString handles all conversions in the absence of other methods
toString() {
return "2";
}
};
alert(obj * 2); // 4, object converted to primitive "2", then multiplication made it a number
- Phép nhân
obj * 2
trước tiên chuyển đổi đối tượng thành nguyên thủy (đó là một chuỗi"2"
). - Sau đó
"2" * 2
trở thành2 * 2
(chuỗi được chuyển đổi thành số).
Cộng nhị phân sẽ nối các chuỗi trong cùng một tình huống, vì nó sẵn sàng chấp nhận một chuỗi:
let obj = {
toString() {
return "2";
}
};
alert(obj + 2); // 22 ("2" + 2), conversion to primitive returned a string => concatenation
6. Tóm lược
Chuyển đổi từ đối tượng nguyên thủy được gọi tự động bởi nhiều hàm và toán tử có sẵn mong đợi một giá trị nguyên thủy.
Có 3 loại (gợi ý) của nó:
"string"
(choalert
và các hoạt động khác cần một chuỗi)"number"
(đối với môn toán)"default"
(vài toán tử khác)
Đặc tả mô tả rõ ràng toán tử nào sử dụng gợi ý nào. Có rất ít toán tử mà không biết nên mong đợi điều gì và sử dụng gợi ý "default"
này. Thông thường đối với các đối tượng tích hợp, "default"
gợi ý được xử lý theo cùng một cách "number"
, vì vậy trong thực tế, hai đối tượng cuối cùng thường được hợp nhất với nhau.
Thuật toán chuyển đổi là:
- Gọi
obj[Symbol.toPrimitive](hint)
nếu phương thức tồn tại, - Nếu không, nếu gợi ý là
"string"
- Nó cố gắng
obj.toString()
vàobj.valueOf()
- Nó cố gắng
- Mặt khác nếu gợi ý là
"number"
hoặc"default"
- Nó cố gắng
obj.valueOf()
vàobj.toString()
- Nó cố gắng
Trong thực tế, nó thường thực hiện obj.toString()
như một phương thức bắt tất cả các kiểu chuyển đổi trả về một đại diện cho một đối tượng mà con người có thể đọc được, cho mục đích ghi log hoặc gỡ lỗi.
Full series tự học Javascript từ cơ bản tới nâng cao tại đây nha.
Nếu bạn thấy hay và hữu ích, bạn có thể tham gia các kênh sau của cafedev để nhận được nhiều hơn nữa:
Chào thân ái và quyết thắng!