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, nullvà undefinedcả 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 nullvs 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 nullvà sự undefinedkhá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 valuelà một thể hiện trực tiếp Clsvà không phải là một thể hiện của một lớp con của Cls. Tôi tránh instanceofvì 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 isTypechứ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 isTypechứ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);, vbiế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 vvà đả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 vcó nhiều loại. Trong những trường hợp đó, chúng ta nên tạo các khối mã vkhô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 nullgiá 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ị vvượ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ù isEmptychứ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ề valloạ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) ...