Điều gì xảy ra khi các đối tượng được thêm: obj1 + obj2, trừ: obj1 - obj2hoặ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).

  1. Tất cả các đối tượng là truetrong một bối cảnh boolean. Chỉ có chuyển đổi số và chuỗi.
  2. 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 - date2là sự khác biệt về thời gian giữa hai ngày.
  3. Đố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ự.

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 truetrong 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:

  1. Gọi obj[Symbol.toPrimitive](hint)– phương thức có từ khoáSymbol.toPrimitive(system symbol)
  2. Nếu không, nếu gợi ý là "string"
    • Nó cố gắng obj.toString()obj.valueOf()
  3. Mặt khác nếu gợi ý là "number"hoặc"default"
    • Nó cố gắng obj.valueOf()obj.toString()

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.toPrimitivenê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 userthự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 toStringvalueOfđế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.toPrimitivethì 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 toStringhoặc valueOftrả 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 toStringvalueOfphươ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 alerthoặ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 userthực hiện tương tự như trên bằng cách sử dụng kết hợp toStringvalueOfthay 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.toPrimitivevalueOf, toStringsẽ 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ù toStringtrả 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 toStringhoặc valueOftrả 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:

  1. Đố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).
  2. 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
  1. Phép nhân obj * 2trước tiên chuyển đổi đối tượng thành nguyên thủy (đó là một chuỗi "2").
  2. Sau đó "2" * 2trở thành 2 * 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"(cho alertvà 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à:

  1. Gọi obj[Symbol.toPrimitive](hint)nếu phương thức tồn tại,
  2. Nếu không, nếu gợi ý là "string"
    • Nó cố gắng obj.toString()obj.valueOf()
  3. Mặt khác nếu gợi ý là "number"hoặc"default"
    • Nó cố gắng obj.valueOf()obj.toString()

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!

Đăng ký kênh youtube để ủng hộ Cafedev nha các bạn, Thanks you!