Phân tích mã nguồn lodash: NaN không bằng NaN

Bài viết này là phần thứ năm trong chuỗi phân tích mã nguồn lodash. Các bài viết tiếp theo sẽ được cập nhật vào kho lưu trữ này, mời bạn sao chép: pocket-lodash.

Tài liệu GitBook cũng sẽ được đồng bộ với các cập nhật từ kho lưu trữ, truy cập tại: pocket-lodash.

Bài viết này sẽ phân tích hàm eq.

Mục đích và Cách sử dụng

Hàm eq được sử dụng để so sánh hai giá trị xem chúng có bằng nhau hay không. Nó tuân thủ quy tắc SameValueZero.

var obj1 = {test: 1};
var obj2 = {test: 1};
var obj3 = obj1;
_.eq(1,1); // true
_.eq(+0, -0); // true
_.eq(obj1, obj3); // true
_.eq(obj1, obj2); // false
_.eq(NaN, NaN); // false

Các quy tắc so sánh

SameValueNonNumber

Quy tắc này áp dụng khi cả hai giá trị xy không phải là kiểu Number. Cụ thể:

  1. Kiểu của x không phải là Number.
  2. Kiểu của y giống như kiểu của x.
  3. Nếu kiểu của xUndefined, trả về true.
  4. Nếu kiểu của xNull, trả về true.
  5. Nếu kiểu của xString và cả hai chuỗi có độ dài và mã hóa giống nhau, trả về true; ngược lại trả về false.
  6. Nếu kiểu của xBoolean và cả hai đều là true hoặc cả hai đều là false, trả về true; ngược lại trả về false.
  7. Nếu kiểu của xSymbol và cả hai có cùng giá trị Symbol, trả về true; ngược lại trả về false.
  8. Nếu cả hai trỏ đến cùng một đối tượng, trả về true; ngược lại trả về false.

Strict Equality Comparison

Quy tắc này tương ứng với toán tử so sánh nghiêm ngặt (===) trong JavaScript:

  1. Nếu kiểu của xy khác nhau, trả về false.
  2. Nếu kiểu của xNumber:
    • Nếu xNaN, trả về false.
    • Nếu yNaN, trả về false.
    • Nếu giá trị số của xy giống nhau, trả về true.
    • Nếu x+0y-0, trả về true.
    • Nếu x-0y+0, trả về true.
    • Trả về false.
  3. Theo kết quả của quy tắc SameValueNonNumber.

SameValue

Quy tắc này định nghĩa như sau:

  1. Nếu kiểu của xy khác nhau, trả về false.
  2. Nếu kiểu của xNumber:
    • Nếu cả xy đều là NaN, trả về true.
    • Nếu x+0y-0, trả về false.
    • Nếu x-0y+0, trả về false.
    • Nếu giá trị số của xy giống nhau, trả về true.
    • Trả về false.
  3. Theo kết quả của quy tắc SameValueNonNumber.

SameValueZero

Đây là quy tắc mà hàm eq tuân thủ:

  1. Nếu kiểu của xy khác nhau, trả về false.
  2. Nếu kiểu của xNumber:
    • Nếu cả xy đều là NaN, trả về true.
    • Nếu x+0y-0, trả về true.
    • Nếu x-0y+0, trả về true.
    • Nếu giá trị số của xy giống nhau, trả về true.
    • Trả về false.
  3. Theo kết quả của quy tắc SameValueNonNumber.

Phân tích mã nguồn

Xem xét mã nguồn của hàm eq:

function eq(giaTri, khacGiaTri) {
  return giaTri === khacGiaTri || (giaTri !== giaTri && khacGiaTri !== khacGiaTri);
}

Hàm này chỉ có một dòng duy nhất.

eq tuân thủ quy tắc SameValueZero, ta sẽ phân tích từng phần của nó.

Phần đầu tiên:

giaTri === khacGiaTri

Đoạn mã này tuân thủ quy tắc Strict Equality Comparison. So sánh giữa Strict Equality ComparisonSameValueZero, sự khác biệt nằm ở cách xử lý NaN.

Theo quy tắc Strict Equality Comparison, nếu cả xy đều là NaN, kết quả sẽ là false. Trong khi đó, theo quy tắc SameValueZero, kết quả sẽ là true.

Phần tiếp theo xử lý trường hợp NaN:

(giaTri !== giaTri && khacGiaTri !== khacGiaTri)

Trong JavaScript, chỉ có NaN mới không bằng chính nó. Khi cả hai giá trị cần so sánh đều không bằng chính chúng, điều đó cho thấy cả hai đều là NaN, do đó hàm sẽ trả về true.

Có thể sử dụng Object.is() không?

Object.is(NaN, NaN) trả về true, nên hàm eq cũng có thể được viết lại như sau:

function eq(giaTri, khacGiaTri) {
  return giaTri === khacGiaTri || Object.is(giaTri, khacGiaTri);
}

Tuy nhiên, Object.is tuân thủ quy tắc SameValue, vì vậy Object.is(+0, -0) sẽ trả về false. Do đó, không thể thay thế trực tiếp Object.is cho eq.

Có thể sử dụng isNaN() không?

Có phương thức toàn cục isNaN dùng để kiểm tra xem một giá trị có phải là NaN hay không. Ví dụ, isNaN(NaN) sẽ trả về true. Tuy nhiên, hàm eq không thể được viết lại như sau:

function eq(giaTri, khacGiaTri) {
  return giaTri === khacGiaTri || (isNaN(giaTri) && isNaN(khacGiaTri));
}

Lý do là isNaN có hành vi kỳ lạ: nếu tham số đầu vào không phải là kiểu Number, nó sẽ cố gắng chuyển đổi thành kiểu Number trước khi kiểm tra. Điều này dẫn đến kết quả không mong muốn, ví dụ isNaN('notNaN') sẽ trả về true vì chuỗi 'notNaN' bị chuyển đổi thành NaN.

Có thể sử dụng Number.isNaN() không?

Để khắc phục hạn chế của isNaN, ES6 đã mở rộng phương thức isNaN trên đối tượng Number, chỉ trả về true khi giá trị là NaN. Do đó, việc sử dụng Number.isNaN để kiểm tra là an toàn và hàm eq cũng có thể được viết lại như sau:

function eq(giaTri, khacGiaTri) {
  return giaTri === khacGiaTri || (Number.isNaN(giaTri) && Number.isNaN(khacGiaTri));
}

Thẻ: lodash JavaScript samevaluezero nan so-sanh

Đăng vào ngày 30 tháng 6 lúc 22:42