Equality comparisons in Javascript
Bài đăng này đã không được cập nhật trong 3 năm
Bạn đã bao giờ băn khoăn giữa việc sử dụng ==
và ===
khi muốn thực hiện một phép so sánh bằng hay chưa? Có ý kiến cho rằng: ***" ==
chỉ so sánh về mặt giá trị, còn ===
thì so sánh về cả giá trị và kiểu dữ liệu"***. Nghe có vẻ hợp lý và dễ nhớ nhưng lại chưa chính xác. Phát biểu đúng phải là: ***“ ==
cho phép ép kiểu (coercion hay chuyển kiểu – type conversion) còn ===
thì không.”***. Vậy lời giải thích thứ 2 khác gì so với lời giải thích ban đầu về sự khác nhau giữa ==
và ===
? Nội dung bài viết này sẽ trả lời cho câu hỏi trên. Nhưng trước tiên, chúng ta hay cùng nhau tìm hiểu về các kiểu so sánh bằng trong Javascript.
Trong ES2015, có 4 thuật toán so sánh bằng:
- So sánh bằng trừu tượng (
==
) - So sánh bằng chính xác (
===
) - SameValueZero
- SameValue
Javascript cung cấp 3 toán tử/hàm so sánh:
==
sử dụng trong so sánh bằng trừu tượng (so sánh 2 bằng)===
sử dụng trong so sánh bằng chính xác (so sánh 3 bằng)Object.is
(có trong ES2015)
So sánh bằng chính xác (===
)
So sánh bằng về mặt giá trị. Không có toán hạng nào được ép kiểu ngầm trước khi được so sánh. Do đó, nếu 2 toán hạng khác nhau về kiểu dữ liệu, thì chúng được coi là không bằng nhau. Trường hợp ngoại lệ:
NaN !== NaN
: Đối với giá trịNaN (Not a Number)
, ví dụ như căn bậc 2 của một số âm, thì phép so sánh===
trả vềfalse
ngay cả khi so sánh với chính nó.+0 === 0
: Giá trị0
được coi là bằng nhau bất kể có dấu hay không.
So sánh bằng trừu tượng (==
)
So sánh bằng cho phép ép/chuyển kiểu. Nếu 2 toán hạng khác nhau về kiểu dữ liệu thì phép so sánh được thực hiện sau khi đã chuyển đổi 2 giá trị về cùng một kiểu dữ liệu.
Trường hợp ngoại lệ ở phép so sánh ==
cũng tương tự phép so sánh ===
NaN != NaN
+0 == -0
So sánh: string
với number
Xem xét ví dụ sau:
var a = 42;
var b = "42";
a === b; // false
a == b; // true
Ở phép so sánh đầu tiên, kết quả là đương nhiên vì 2 toán hạng này có kiểu dữ liệu khác nhau.
Ở phép so sánh thứ 2, một trong 2 hoặc cả 2 toán hạng đã được chuyển kiểu ngầm trước khi so sánh. Vậy giá trị 42
của a được chuyển kiểu về string
hay “42”
của b
được chuyển kiểu về number
?
Trong đặc tả của ES5, mệnh đề 11.9.3.4-5 phát biểu rằng:
- If Type(x) is Number and Type(y) is String, return the result of the comparison x == ToNumber(y).
- If Type(x) is String and Type(y) is Number, return the result of the comparison ToNumber(x) == y.
Tức nếu so sánh string
với number
thì giá trị kiểu string
sẽ được chuyển về kiểu number
.
Do đó, câu trả lời đúng là giá trị “42”
sẽ được chuyển kiểu về dạng number
.
So sánh: kiểu boolean
Một trong những vấn đề lớn nhất gặp phải khi ép kiểu ngầm trong so sánh ==
. Xem xét ví dụ sau:
var a = "42";
// bad (will fail!):
if (a == true) {
// ..
}
// good enough (works implicitly):
if (a) {
// ..
}
Rõ ràng 42
là giá trị đúng. Tuy nhiên câu điều kiện đầu trả về false
. Lí giải cho điều này là mệnh đề 11.9.3.6-7 trong đặc tả ES5:
If Type(x) is Boolean, return the result of the comparison ToNumber(x) == y. If Type(y) is Boolean, return the result of the comparison x == ToNumber(y).
Theo đó, khi so sánh giá trị kiểu boolean
thì giá trị này sẽ được chuyển kiểu về dạng number
.
Quay trở lại phép so sánh “42” == true
ở ví dụ trên. true
được ép kiểu thành 1
. Phép so sánh trở thành “42” == 1
. Kiểu dữ liệu của 2 toán hạng vẫn khác nhau. “42”
lại được ép kiểu về dạng number
là 42
. Phép so sánh cuối cùng là 42 == 1
. Rõ ràng kết quả của phép so sánh này là false
.
Tương tự, phép so sánh “42” == false
cũng trả về false
khi false
được ép kiểu về 0
.
Vậy “42”
không == true
và cũng không == false
. Một giá trị không đúng cũng không không sai. Có vẻ lạ nhỉ?
Thực tế thì “42”
là giá trị đúng. ToBoolean(“42”) = true
. Tính đúng đắn của nó không liên quan tới phép so sánh == true
hay == false
. Bởi trong các phép so sánh này, không hề có ép kiểu về dạng boolean
nào được thực hiện cả. Do đó, ta nên tránh sử dụng phép so sánh bằng (==
và ===
) với giá trị kiểu boolean
.
Khi so sánh giá trị kiểu boolean
trong phép so sánh ==
, giá trị boolean
luôn được ép kiểu về dạng number
trước tiên.
So sánh: null
và undefined
Hai giá trị này là bằng nhau trong phép so sánh ==
và không bằng bất cứ giá trị nào khác.
Chính vì vậy ta có thể dùng câu điều kiện a == null
để kiểm tra a
có bằng null
hay undefined
hay không thay vì a == null || a == undefined
.
Với trường hợp ta chỉ muốn kiểm tra a
có phải là undefined
hay không (a có thể bằng null
) thì hàm typeof
sẽ giải quyết vấn đề này. typeof a == “undefined”
.
So sánh: object
và non-object
Giá trị kiểu object
được ép kiểu về dạng nguyên thủy thông qua hàm trừu tượng ToPrimitive
(valueOf()
, toString()
) khi so sánh với giá trị không phải dạng object
.
Xét ví dụ sau:
var a = "abc";
var b = Object( a ); // same as `new String( a )`
a === b; // false
a == b; // true
b
là giá trị kiểu object
tương đương với new String( a )
. Thông qua hàm toString()
, giá trị trả về sau khi ép kiểu là “abc”
bằng với giá trị củaa
.
Bảng so sánh ==
với các kiểu dữ liệu khác nhau:
Lưu ý khi sử dụng so sánh ==
:
- Tránh sử dụng trong các phép so sánh có các giá trị
true
hoặcfalse
- Xem xét cẩn thận trong các phép so sánh có các giá trị
[]
,“”
hoặc0
- Sử dụng hàm
typeof
để kiểm tra kiểu dữ liệu trước khi so sánh.
Đến đây ta có thể thấy so sánh ===
an toàn hơn với so sánh ==
khi không cho phép ép kiểu. Đồng thời cũng lý giải được sự nhầm lẫn về 2 kiểu so sánh này như phần đầu của bài viết đã đề cập. “So sánh ==
chỉ so sánh về mặt giá trị” là chưa đúng. Cả 2 kiểu so sánh ==
và ===
đều so sánh kiểu dữ liệu trước tiên. Sau đó mới so sánh về mặt giá trị.
- Nếu kiểu dữ liệu trùng nhau: 2 kiểu so sánh này là như nhau.
- Nếu kiểu dữ liệu không trùng nhau:
+ So sánh
===
trả về false và kết thúc việc so sánh. + So sánh==
thực hiện chuyển đổi 2 toán hạng về cùng một kiểu dữ liệu rồi tiếp tục so sánh về mặt giá trị.
Dễ thấy, so sánh ==
thực hiện nhiều công việc hơn khi 2 toán hạng không cùng một kiểu dữ liệu. Do đó, hiệu năng so sánh thấp hơn với so sánh ===
. Tuy nhiên sự chênh lệch này là không đáng kể. Việc quyết định sử dụng kiểu so sánh nào chỉ nên dựa vào việc bạn có muốn chuyển kiểu được thực hiện hay không mà thôi. Không nên lạm dụng so sánh ===
trong mọi trường hợp.
Ví dụ dưới đây cho thấy việc sử dụng ==
làm code trở nên dễ đọc hơn, thậm chí có phần nhanh hơn:
var a = doSomething();
if (a == null) {
// ..
}
var a = doSomething();
if (a === undefined || a === null) { //unnecessary and ugly
// ..
}
Ngoài 2 kiểu so sánh trên, Javascript còn 2 thuật toán so sánh được sử dụng chủ yếu bên trong phần core của ngôn ngữ:
So sánh SameValue
- Thuật toán so sánh này tương tự với so sánh
===
nhưng coi+0
và-0
là không bằng nhauNaN
bằng với chính nó - Hàm tương ứng:
Object.is
(có trong ES2015)Object.is(+0, -0) = false
Object.is(NaN, NaN) = false
So sánh SameValueZero
- Thuật toán này tương tự thuật toán SameValue ngoại trừ việc coi
+0
và-0
là bằng nhau - Thuật toán này chưa có toán tử hay hàm tương ứng.
Bảng dưới đây tổng kết 4 kiểu so sánh bằng trong Javascript.
Nguồn tham khảo:
- Kyle Simpson, You Don’t Know JS, Chapter 4: Coercion, https://github.com/getify/You-Dont-Know-JS/blob/master/types %26 grammar/ch4.md#loose-equals-vs-strict-equals
- MDN Web Docs, Equality comparisons and sameness, https://developer.mozilla.org/en/docs/Web/JavaScript/Equality_comparisons_and_sameness
All rights reserved