Vắc xin
Tôi không khuyên bạn nên cố gắng xác định hoặc sử dụng hàm tính toán xem có giá trị nào trên toàn thế giới không. "Thực sự" có nghĩa là gì? Nếu tôi có let human = { name: 'bob', stomach: 'empty' }
, nên isEmpty(human)
trở về true
? Nếu tôi có let reg = new RegExp('');
, nên isEmpty(reg)
trở về true
? Thế còn isEmpty([ null, null, null, null ])
- danh sách này chỉ chứa sự trống rỗng, vậy bản thân danh sách có trống không? Tôi muốn đưa ra ở đây một số lưu ý về "khoảng trống" (một từ có chủ ý tối nghĩa, để tránh các liên kết có sẵn) trong javascript - và tôi muốn tranh luận rằng "khoảng trống" trong các giá trị javascript không bao giờ nên được xử lý một cách khái quát.
Sự thật / giả dối
Để quyết định làm thế nào để xác định "độ trống" của các giá trị, chúng ta cần phải bổ sung sẵn nội dung của javascript, ý thức vốn có về việc các giá trị là "trung thực" hay "giả mạo". Đương nhiên, null
và undefined
cả hai đều là "giả". Ít tự nhiên hơn, số 0
(và không có số nào khác ngoại trừ NaN
) cũng là "giả". Ít nhất là tự nhiên: ''
là giả, nhưng []
và {}
(và new Set()
, và new Map()
) là sự thật - mặc dù tất cả đều có vẻ trống rỗng như nhau!
Null vs Không xác định
Ngoài ra còn có một số cuộc thảo luận liên quan đến null
vs undefined
- chúng ta có thực sự cần cả hai để thể hiện sự trống rỗng trong các chương trình của mình không? Cá nhân tôi tránh bao giờ có các chữ cái u, n, d, e, f, i, n, e, d xuất hiện trong mã của tôi theo thứ tự đó. Tôi luôn luôn sử dụng null
để biểu thị "sự trống rỗng". Mặc dù vậy, một lần nữa, chúng ta cần thể hiện ý thức vốn có của javascript về cách thức null
và sự undefined
khác biệt:
- Cố gắng truy cập vào một tài sản không tồn tại cho
undefined
- Bỏ qua một tham số khi gọi một hàm dẫn đến việc nhận tham số đó
undefined
:
let f = a => a;
console.log(f('hi'));
console.log(f());
- Các tham số có giá trị mặc định chỉ nhận mặc định khi được cung cấp
undefined
, không null
:
let f = (v='hello') => v;
console.log(f(null));
console.log(f(undefined));
Vắc xin không chung chung
Tôi tin rằng sự trống rỗng không bao giờ nên được xử lý theo cách chung chung. Thay vào đó, chúng ta nên luôn luôn có sự nghiêm ngặt để có thêm thông tin về dữ liệu của mình trước khi xác định xem nó có bị bỏ trống không - tôi chủ yếu làm điều này bằng cách kiểm tra loại dữ liệu nào tôi đang xử lý:
let isType = (value, Cls) => {
try {
return Object.getPrototypeOf(value).constructor === Cls;
} catch(err) {
return false;
}
};
Lưu ý rằng hàm này bỏ qua tính đa hình - nó dự kiến value
là một thể hiện trực tiếp Cls
và không phải là một thể hiện của một lớp con của Cls
. Tôi tránh instanceof
vì hai lý do chính:
([] instanceof Object) === true
("Một mảng là một đối tượng")
('' instanceof String) === false
("Chuỗi không phải là Chuỗi")
Lưu ý rằng Object.getPrototypeOf
được sử dụng để tránh trường hợp như thế let v = { constructor: String };
Các isType
chức năng vẫn trả về đúng cho isType(v, String)
(false), và isType(v, Object)
(true).
Nhìn chung, tôi khuyên bạn nên sử dụng isType
chức năng này cùng với các mẹo sau:
- Giảm thiểu số lượng giá trị xử lý mã của loại không xác định. Ví dụ, cho
let v = JSON.parse(someRawValue);
, v
biến của chúng tôi bây giờ là loại không xác định. Càng sớm càng tốt, chúng ta nên hạn chế khả năng của mình. Cách tốt nhất để làm điều này có thể là bằng cách yêu cầu một loại cụ thể: ví dụ: if (!isType(v, Array)) throw new Error('Expected Array');
đây là cách thực sự nhanh chóng và biểu cảm để loại bỏ bản chất chung của v
và đảm bảo nó luôn luôn là một Array
. Đôi khi, mặc dù, chúng ta cần phải cho phép v
có nhiều loại. Trong những trường hợp đó, chúng ta nên tạo các khối mã v
không còn chung chung, càng sớm càng tốt:
if (isType(v, String)) {
/* v isn't generic in this block - It's a String! */
} else if (isType(v, Number)) {
/* v isn't generic in this block - It's a Number! */
} else if (isType(v, Array)) {
/* v isn't generic in this block - it's an Array! */
} else {
throw new Error('Expected String, Number, or Array');
}
- Luôn sử dụng "danh sách trắng" để xác nhận. Nếu bạn yêu cầu một giá trị là, ví dụ: Chuỗi, Số hoặc Mảng, hãy kiểm tra 3 khả năng "trắng" đó và ném Lỗi nếu không có gì trong số 3 được thỏa mãn. Chúng ta nên có thể thấy rằng việc kiểm tra đối với khả năng "đen" không phải là rất hữu ích: Nói chúng tôi viết
if (v === null) throw new Error('Null value rejected');
- điều này là rất tốt cho việc đảm bảo rằng null
giá trị này không làm cho nó qua, nhưng nếu một giá trị không làm cho nó qua, chúng tôi vẫn biết khó bất cứ thứ gì về nó. Một giá trị v
vượt qua kiểm tra null này vẫn còn RẤT chung chung - đó là bất cứ điều gì nhưngnull
! Danh sách đen hầu như không xua tan sự chung chung.
Trừ khi một giá trị là null
, không bao giờ coi "một giá trị trống". Thay vào đó, hãy xem xét "một chữ X trống". Về cơ bản, không bao giờ xem xét làm bất cứ điều gì như if (isEmpty(val)) { /* ... */ }
- cho dù isEmpty
chức năng đó được thực hiện như thế nào (tôi không muốn biết ...), điều đó không có ý nghĩa! Và đó là cách quá chung chung! Độ trống chỉ nên được tính toán với kiến thức về val
loại. Kiểm tra phòng ngừa nên như thế này:
- "Một chuỗi, không có ký tự":
if (isType(val, String) && val.length === 0) ...
- "Một đối tượng, với 0 đạo cụ":
if (isType(val, Object) && Object.entries(val).length === 0) ...
- "Một số, bằng hoặc nhỏ hơn 0":
if (isType(val, Number) && val <= 0) ...
"Một mảng, không có mục nào": if (isType(val, Array) && val.length === 0) ...
Ngoại lệ duy nhất là khi null
được sử dụng để biểu thị chức năng nhất định. Trong trường hợp này, thật ý nghĩa khi nói: "Một giá trị trống":if (val === null) ...