+2

JavaScript: Can (a==1 && a==2 && a==3) evaluate to true ?

Mở đầu

Đúng như tiêu đề bài viết, có khi nào biểu thức so sánh (a == 1 && a == 2 && a == 3) lại trả về giá trị là true không? Thoạt nhìn thì điều đấy có vẻ hơi vô lý, nhưng câu trả lời lại là có.

Tình cờ người viết đọc được một topic nói về vấn đề này do được anh cùng cty chia sẻ, cá nhân mình thấy thật sự nó khá thú vị, nên mạn phép mượn nó về đây để mọi người cùng đọc 😄

Bắt đầu tiến hành

Trước tiên thì chúng ta cùng mở console của browser và xem đoạn code này:

var a = {
  value: 0,
  valueOf: function() {
    return this.value += 1;
  }
};
var equality = (a==1 && a==2 && a==3);

Sau đó check value của equality và thấy rằng kết quả trả về là true. Vậy trick ở đây là ? Có 2 thứ cần chú ý:

  • Biểu thức so sánh mà chúng ta đang đề cập dùng loose equality
  • Function valueOf()

Khi chúng ta dùng loose equality với 2 biến khác kiểu, thì js sẽ tìm cách đưa 1 trong 2 biến so sánh về cùng 1 kiểu với bên còn lại (type coercion).

Cụ thể ở đây khi ta dùng loose equality để so sánh 1 biến có kiểu Object với 1 biến không có kiểu Object (ở trường hợp này là Number), thì mặc định trong js giá trị của Object sẽ trả về giá trị của function valueOf().

Trong javaScript function valueOf() sẽ trả về giá trị primitive (nguyên bản) của object đó. Nếu valueOf() không được định nghĩa, hoặc nó trả về giá trị không phải là Primitive, thì giá trị của Object sẽ là giá trị trả về từ function toString(). Nếu toString() cũng trả về một giá trị không phải là Primitive thì sẽ xảy ra lỗi TypeError. Nếu chưa clear lắm thì các bạn có thể tham khảo thêm ở đây.

Điều thú vị là chúng ta hoàn toàn có thể override lại function valueOf() này ở bên trong constant a, thay vì trả về chính object đó thì giờ đây chúng ta sẽ khiến nó trả về bất cứ giá trị nào chúng ta muốn. Vd như chúng ta có 1 object thế này:

var a = {
  value: 0
}

Gọi a.valueOf() kết quả trả về chính là obj này {value: 0}

Sau đó tiến hành override lại function valueOf():

a.valueOf = function() {
  return this.value;
}

Rồi thử gọi lại function valueOf(), kết quả trả về bây giờ đã là 0, tương đương với:

a == 0 // true

Ok, việc bây giờ chúng ta cần làm để biến giá trị của biểu thức so sánh ban đầu thành true là tăng giá trị của a.value lên 1 mỗi lần gọi đến nó. Để làm được điều đó thì trong js chúng ta sẽ dùng đến toán tử +=:

a.valueOf = function() {
  return this.value += 1;
}

Và áp dụng vào biểu thức điều kiện ban đầu:

(a==1 && a==2 && a==3); // true

Cơ bản là đã xong, với 3 điều kiện so sánh con tương ứng với 3 lần gọi đến function valueOf() thì mỗi lần giá trị của a.value lại tăng thêm 1 và cũng bằng với giá trị của biến so sánh còn lại => thỏa mãn với điều kiện của biểu thức so sánh.

Một cách tổng quát các bước thực hiện so sánh như sau:

a                     == 1   -> 
a.valueOf()           == 1   -> 
a.value += 1          == 1   -> 
0     += 1            == 1   ->
1                     == 1   -> true
a                     == 2   -> 
a.valueOf()           == 2   -> 
a.value += 1          == 2   -> 
1     += 1            == 2   ->
2                     == 2   -> true
a                     == 3   -> 
a.valueOf()           == 3   -> 
a.value += 1          == 3   -> 
2     += 1            == 3   ->
3                     == 3   -> true

Vậy là (a==1 && a==2 && a==3) evaluated to true 😄

Quan điểm cá nhân

Với cá nhân mình thì rất ít khi dùng đến Loose Equality vì nó sẽ ép kiểu của 2 biến so sánh về cùng 1 kiểu giá trị để so sánh, nếu không cẩn thận thì rất dễ sai sót sinh bug.

Nếu dùng Strict Equality === thì mọi chuyện đã khác, nó sẽ so sánh cả kiểu giá trị của 2 bên, nếu kiểu giá trị khác nhau thì sẽ là false, rất minh bạch và ít gây hiểu lầm như Loose Equality ==. Cho nên dùng === an toàn hơn.

Tất nhiên việc dùng Strict Equality hay Loose Equality còn tùy thuộc vào từng trường hợp và mục đích khác nhau. Nhưng chỉ nên dùng Loose Equality khi đã nắm được rõ các quy tắc ép kiểu của nó:

  • Nếu cả 2 vế đều là null, hoặc undefined => true
  • Nếu một vế là giá trị kiểu Number, vế còn lại là giá trị kiểu String, String sẽ được convert sang kiểu Number và so sánh giá trị
  • Nếu một vế là kiểu Boolean, một vế là kiểu Number, Boolean sẽ được chuyển sang kiểu Number và so sánh giá trị
  • Nếu một vế là kiểu Boolean, một vế là kiểu String, cả 2 vế sẽ được chuyển về kiểu Number và so sánh giá trị
  • Nếu một vế là kiểu Number, một vế là kiểu Object tham chiếu, vế Object tham chiếu sẽ được chuyển sang Number và so sánh giá trị
  • Nếu một vế là kiểu String, một vế là kiểu Object tham chiếu, vế Object tham chiếu sẽ được chuyển sang kiểu String và so sánh nội dung
  • Ngoài những trường hợp trên, tất cả đều là false

Giả sử như 1 ngày nào đó xuất hiện bài báo (a===1 && a===2 && a===3) evaluate to true? thì đúng là ...tận thế.

Cảm ơn mọi người đã dành thời gian đọc bài viết.

Nguồn.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí