JavaScript: Can (a==1 && a==2 && a==3) evaluate to true ?
Bài đăng này đã không được cập nhật trong 6 năm
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.
All rights reserved