Ép kiểu (Type coercion)

Hãy tưởng tượng bạn là một đầu bếp trong một gian bếp hiện đại. Ở đây, mỗi nguyên liệu như rau củ, thịt, gia vị, tương ứng với các kiểu dữ liệu trong JavaScript như số, chuỗi, boolean. Khi nấu ăn, bạn phải kết hợp chúng một cách khéo léo để tạo ra một món ăn hoàn chỉnh. Tương tự, trong JavaScript, “ép kiểu” là quá trình bạn biến đổi giá trị từ một kiểu dữ liệu này sang một kiểu khác để đạt được kết quả mong muốn trong mã lệnh.

Ví dụ ép kiểu trong JavaScript
Trong JavaScript, ép kiểu là quá trình biến đổi giá trị từ một kiểu dữ liệu này sang một kiểu dữ liệu khác.

1. Ép kiểu là gì?

Ép kiểu (Type coercion) trong JavaScript là quá trình chuyển đổi giá trị từ một kiểu dữ liệu này sang một kiểu khác. Điều này thường xảy ra tự động, chẳng hạn khi sử dụng toán tử so sánh == hoặc các toán tử số học như +, -, v.v.

Ví dụ:

"3" + 2; // "32"

Ở đây, JavaScript đã tự động chuyển số 2 thành chuỗi và kết hợp nó với chuỗi “3”, tạo ra kết quả là “32”, không phải là 5.

2. Ép kiểu rõ ràng và ép kiểu ngầm

2.2 Ép kiểu rõ ràng

Ép kiểu rõ ràng là khi bạn chủ động chuyển đổi kiểu dữ liệu bằng cách sử dụng các hàm như Number(), String(), Boolean(), v.v.

Ví dụ:

Number("123"); // 123

Trong JavaScript, mỗi kiểu dữ liệu cơ bản đều có các hàm chuyển đổi tương ứng để thực hiện chuyển đổi giữa các kiểu.

  • Kiểu số (number): Có hàm Number() để chuyển đổi giá trị sang kiểu số.
  • Kiểu chuỗi (string): Có hàm String() để chuyển đổi giá trị sang kiểu chuỗi.
  • Kiểu boolean (boolean): Có hàm Boolean() để chuyển đổi giá trị sang kiểu boolean.
  • Đối tượng (object): Có hàm Object() để tạo hoặc chuyển đổi giá trị thành đối tượng.
  • Mảng (array): Có hàm Array() để tạo mảng mới hoặc chuyển đổi giá trị thànnh mảng.
  • Hàm (function): Có hàm Function để tạo hàm mới.
  • Null và undefined: Không có hàm chuyển đổi dữ liêụ tương ứng.

2.3 Ép kiểu ngầm

Ép kiểu ngầm xảy ra khi JavaScript tự động quyết định kiểu dữ liệu, thường là trong các phép toán và so sánh.

Ví dụ:

5 + ""; // "5"

Hiểu rõ về ép kiểu trong JavaScript rất quan trọng để tránh những lỗi không mong muốn trong quá trình lập trình. Đặc biệt, nên chú ý đến những tình huống ép kiểu ngầm, vì chúng có thể dẫn đến hiểu nhầm.

3. Các trường hợp ép kiểu phổ biến

3.1 Ép kiểu sang chuỗi

Bất kỳ kiểu dữ liệu nào trong JavaScript cũng có thể được chuyển đổi thành chuỗi. Dưới đây là 3 phương pháp phổ biến và ví dụ minh họa:

Cách 1: Sử dụng hàm String().

String(123); // "123"

Cách 2: Sử dụng phương thức toString().

(123).toString(); // "123"
// Lưu ý: không áp dụng được cho `null` và `undefined`

Cách 3: Nối chuỗi hoặc Template literals.

`${123}`; // "123"
// hoặc
123 + ""; // "123"

✅ BẢNG SO SÁNH CÁC CÁCH ÉP KIỂU SANG CHUỖI

Bảng dưới đây cung cấp cái nhìn trực quan về điểm giống và khác nhau cho các cách ép kiểu sang chuỗi như String(), .toString(), toán tử + và Template literals.

Phương thứcMô tảLưu ýVí dụ
String(value)Chuyển đổi value sang chuỗiHoạt động với mọi kiểu dữ liệuString(123)-> "123"
value.toString()Chuyển đổi value sang chuỗiKhông hoạt động với nullundefined(123).toString() -> "123"
Template literals hoặc nối chuỗiChuyển đổi value sang chuỗi. Ví dụ:
${value} hoặc value + "".
Hoạt động với mọi kiểu dữ liệu123 + "" -> "123"

3.2 Ép kiểu sang số

Dưới đây là một số cách chuyển đổi giá trị sang kiểu số trong JavaScript, cùng với ví dụ minh hoạ:

Cách 1: sử dụng hàm Number().

Number("456") // 456
Number(true); // 1
Number(null); // 0

Cách 2: Sử dụng toán tử +.

+"456"; // 456
+true; // 1
+null; // 0

Lưu ý: Các giá trị "" (chuỗi rỗng), "0" (chuỗi chứa số 0), "000", 0, -0, null, false, []new Date(0) khi ép kiểu sang Number sẽ trả về 0.

Các giá trị chuỗi không phải là số ("abc"), undefined, và NaN không chuyển đổi thành 0 khi sử dụng Number() hoặc +. Thay vào đó, chúng trả về NaN (Not-a-Number).

Ví dụ:

Number("abc"); // NaN
Number(undefined); // NaN

Cách 3: Sử dụng parseInt()parseFloat().

parseInt("123", 10); // 123
parseFloat("123.45"); // 123.45

Lưu ý: Cách hoạt động của parseInt()parseFloat() khác với cách ép kiểu sang Number ở trên. Cụ thể, nó hoạt động như sau:

  • Khi đầu vào là một chuỗi: parseInt()parseFloat() sẽ phân tích chuỗi đó để tìm số đầu tiên và chuyển số đó sang kiểu Number. Nếu không tìm thấy số nào, kết quả là NaN.
  • Khi đầu vào không phải là chuỗi: parseInt()parseFloat() trước tiên sẽ chuyển giá trị đó thành chuỗi. Sau đó, thực hiện quá trình phân tích như trên.

✅ BẢNG SO SÁNH CÁCH ÉP KIỂU SANG SỐ

Bảng này cung cấp một cái nhìn nhanh về sự khác biệt và giống nhau giữa Number(value), +value, và parseFloat/parseInt(value) khi chuyển đổi giá trị khác nhau sang số trong JavaScript.

Tính năng/Phương thứcNumber(value) / +valueparseFloat(value) / parseInt(value)
Xử lý chuỗiChặt chẽLinh hoạt
VD chuỗi phức tạp"123abd" -> NaNparseFloat ("123abc") -> 123,

parseInt ("123abc") -> 123

Xử lý số thựcChính xácparseFloat giữ nguyên, parseInt làm tròn xuống
Xử lý booleantrue -> 1, false -> 0NaN
Xử lý null0NaN
Xử lý UndefinedNaNNaN

3.3 Ép kiểu sang Boolean

Có hai cách để chuyển đổi giá trị sang kiểu boolean:

Cách 1: Sử dụng Boolean().

Boolean(1); // true
Boolean(0); // false
Boolean("hello"); // true
Boolean(""); // false

Cách 2: Sử dụng toán tử NOT hai lần !!

!!0; // false
!!1; // true

Chỉ có 6 giá trị sau đây khi ép kiểu sang boolean sẽ trả về false: false, 0,"" (chuỗi rỗng), null, undefined, và NaN. Mọi giá trị khác khi ép sang boolean đều trở thành true.

3.4 Tại sao ít khi chuyển kiểu dữ liệu khác sang Object?

Trong JavaScript, việc chuyển đổi từ các kiểu dữ liệu đơn giản sang Object không phổ biến do cấu trúc dữ liệu và mức độ phức tạp khác nhau. Ví dụ, chuyển một số hoặc chuỗi thành Object có thể không mang lại giá trị thực tế và làm tăng độ phức tạp của code.

4. Quy tắc ép kiểu ngầm

4.1 Ép kiểu ngầm với toán tử so sánh ==

Trong JavaScript, khi so sánh hai giá trị xy với nhau bằng cách sử dụng x == y, kết quả sẽ trả về true hoặc false. Quá trình so sánh này được thực hiện như sau:

  • Nếu xy cùng kiểu dữ liệu:
    • Nếu cả hai là undefined hoặc null, trả về true.
    • Nếu là số:
      • Nếu một trong hai là NaN, trả về false.
      • Nếu cả hai số giống hệt nhau, trả về true.
    • Nếu là chuỗi, so sánh từng ký tự (cùng độ dài và giống nhau từng ký tự theo vị trí) để xác định chúng có giống hệt nhau không.
    • Nếu là boolean, trả về true nếu cả hai cùng true hoặc cùng false.
  • Nếu xnullyundefined, hoặc ngược lại, trả về true.
  • Nếu một trong hai là số và cái kia là chuỗi, chuyển đổi chuỗi thành số rồi so sánh.
  • Nếu một trong hai là boolean, chuyển đổi boolean thành số rồi so sánh.
  • Nếu một trong hai là đối tượng và cái kia là chuỗi hoặc số, chuyển đổi đối tượng thành giá trị nguyên thủy rồi so sánh.
  • Nếu không thỏa mãn các trường hợp trên, trả về false.

Trong trường hợp “Nếu xnullyundefined, hoặc ngược lại, trả về true,” không có sự ép kiểu ngầm định giữa nullundefined. Đây là một quy tắc đặc biệt trong JavaScript khi sử dụng toán tử so sánh bằng lỏng lẻo (==). Theo quy tắc này, nullundefined được coi là bằng nhau mà không cần chuyển đổi kiểu dữ liệu của chúng. Điều này có nghĩa là không có sự chuyển đổi kiểu dữ liệu nào xảy ra; JavaScript đơn giản chỉ coi chúng là giống nhau trong trường hợp cụ thể này (so sánh ==).

4.2 Ép kiểu ngầm với toán tử số học

4.2.1 Toán tử cộng (+)

Trong JavaScript, toán tử cộng (+) có thể thực hiện cả phép cộng số học và nối chuỗi. Cách hoạt động của nó được xác định như sau:

  • Xem xét giá trị của biểu thức bên trái và bên phải của toán tử.
  • Chuyển đổi giá trị này sang kiểu nguyên thủy (ToPrimitive).
  • Nếu một trong hai giá trị sau khi chuyển đổi là chuỗi, thực hiện nối chuỗi.
  • Nếu không, chuyển đổi cả hai giá trị sang số (ToNumber) và thực hiện phép cộng.

Ví dụ:

  • "5" + 2 sẽ có kết quả là "52" (nối chuỗi).
  • 5 + "2" cũng sẽ trở thành "52".
  • 5 + 2 sẽ có kết quả là 7 (phép cộng số).

4.2.2 Toán tử trừ (-)

Toán tử trừ (-) chỉ thực hiện phép trừ số học. Trong quá trình này, toán tử trừ chuyển đổi cả hai giá trị sang số rồi mới thực hiện phép trừ.

  • 5 - "2" sẽ trả về 3 (chuyển "2" thành số).
  • "10" - "2” sẽ trả về 8 (chuyển "10""2" thành số).

Tham khảo thêm: https://262.ecma-international.org/5.1/#sec-11.9.3

5. Bảng chuyển đổi kiểu dữ liệu

Bảng dưới đây cung cấp một cái nhìn tổng quan về cách các giá trị khác nhau trong JavaScript được chuyển đổi sang các kiểu dữ liệu Number, StringBoolean.

Giá trị gốcChuyển thành sốChuyển thành chuỗiChuyển thành boolean
false0“false”false
true1“true”true
00“0”false
11“1”true
“0”0“0”true
“000”0“000”true
“1”1“1”true
NaNNaN“NaN”false
InfinityInfinity“Infinity”true
-Infinity-Infinity“-Infinity”true
“”0“”false
“20”20“20”true
“twenty”NaN“twenty”true
[]0“”true
[20]20“20”true
[10, 20]NaN“10,20”true
[“twenty”]NaN“twenty”true
[“ten”, “twenty”]NaN“ten,twenty”true
function sum() {}NaN“function sum() {}”true
{}NaN“[object Object]”true
null0“null”false
undefinedNaN“undefined”false

Giá trị màu đỏ chỉ các giá trị mà một số lập trình viên có thể không mong đợi.

Tham khảo thêm: https://www.w3schools.com/js/js_type_conversion.asp

6. Lưu ý quan trọng

  • Nên sử dụng toán tử so sánh nghiêm ngặt: Để tránh những hành vi không mong muốn từ ép kiểu ngầm, nên sử dụng toán tử ===. Toán tử này chỉ trả về true khi cả hai giá trị có cùng kiểu dữ liệu và giá trị.
  • Tư duy nghiêm ngặt với kiểu dữ liệu: Khi lập trình, hãy chú ý đến việc sử dụng cùng kiểu dữ liệu trong so sánh và các phép tính toán. Điều này giúp tránh những lỗi logic không mong muốn.

Ví dụ:

3 === "3"; // false, vì một là số và một là chuỗi
3 === 3;   // true, cùng kiểu và giá trị

Nắm vững các quy tắc và kỹ thuật ép kiểu trong JavaScript sẽ giúp bạn viết code chính xác hơn, tránh được những lỗi phổ biến, và hiểu rõ hơn về cách ngôn ngữ này xử lý dữ liệu.

7. Tóm tắt

  • Định nghĩa ép kiểu: Ép kiểu trong JavaScript là quá trình chuyển đổi giá trị từ một kiểu dữ liệu này sang một kiểu khác, thường xảy ra tự động trong các toán tử so sánh hoặc số học.
  • Ép kiểu rõ ràng và ngầm:
    • Rõ ràng: Chủ động sử dụng các hàm như Number(), String(), Boolean(), v.v để chuyển đổi.
    • Ngầm: Tự động xảy ra, ví dụ khi sử dụng toán tử + hoặc ==.
  • Các trường hợp ép kiểu phổ biến:
    • Sang chuỗi: Dùng String(), .toString(), nối chuỗi, hoặc Template literals.
    • Sang số: Dùng Number(), toán tử +, parseInt(), parseFloat().
    • Sang boolean: Dùng Boolean() hoặc !!.
  • Quy tắc ép kiểu ngầm:
    • Với toán tử ==So sánh giá trị sau khi chuyển đổi kiểu.
    • Với toán tử +: Nếu một trong hai giá trị là chuỗi, thực hiện nối chuỗi; nếu không, thực hiện cộng số.
    • Với toán tử -: Chuyển đổi cả hai giá trị sang số rồi thực hiện trừ.
  • Lưu ý khi sử dụng ép kiểu:
    • Sử dụng toán tử so sánh nghiêm ngặt === để tránh hành vi không mong muốn từ ép kiểu ngầm.
    • Chú ý sử dụng cùng kiểu dữ liệu trong so sánh và tính toán.