Próżnia
Nie polecam próbować definiować ani używać funkcji, która oblicza, czy jakaś wartość na całym świecie jest pusta. Co to tak naprawdę znaczy być „pustym”? Jeśli tak let human = { name: 'bob', stomach: 'empty' }
, czy powinienem isEmpty(human)
wrócić true
? Jeśli tak let reg = new RegExp('');
, czy powinienem isEmpty(reg)
wrócić true
? Co z isEmpty([ null, null, null, null ])
- ta lista zawiera tylko pustkę, więc czy sama lista jest pusta? Chcę przedstawić tutaj kilka uwag na temat „próżności” (słowo celowo niejasne, aby uniknąć wcześniejszych skojarzeń) w javascript - i chcę argumentować, że „próżności” w wartościach javascript nigdy nie należy traktować ogólnie.
Prawdomówność / fałsz
Aby zdecydować, w jaki sposób określić „próżność” wartości, musimy uwzględnić wbudowane, javascriptowe nieodłączne poczucie, czy wartości są „prawdą”, czy „fałszem”. Oczywiście null
i undefined
oba są „falsy”. Mniej naturalnie liczba 0
(i żadna inna liczba oprócz NaN
) jest również „fałszem”. ''
Przynajmniej naturalnie: jest fałszem, ale []
i {}
(i new Set()
, i new Map()
) są prawdomówne - chociaż wszystkie wydają się równie próżne!
Null vs Undefined
Trwa także dyskusja na temat null
vs undefined
- czy naprawdę potrzebujemy obu, aby wyrazić pustkę w naszych programach? Osobiście unikam, aby litery u, n, d, e, f, i, n, e, d pojawiały się w moim kodzie w tej kolejności. Zawsze używam null
do oznaczenia „próżności”. Ponownie, choć musimy pomieścić Javascript w przyrodzone poczucie jak null
i undefined
różnią się:
- Próba uzyskania dostępu do nieistniejącej właściwości daje
undefined
- Pominięcie parametru podczas wywoływania funkcji powoduje otrzymanie tego parametru
undefined
:
let f = a => a;
console.log(f('hi'));
console.log(f());
- Parametry z wartościami domyślnymi otrzymują wartość domyślną tylko wtedy
undefined
, gdy są podane , a nie null
:
let f = (v='hello') => v;
console.log(f(null));
console.log(f(undefined));
Nieogólna próżnia
Uważam, że pustki nigdy nie powinny być traktowane w sposób ogólny. Zamiast tego powinniśmy zawsze dokładać starań, aby uzyskać więcej informacji o naszych danych przed ustaleniem, czy są one puste - robię to głównie poprzez sprawdzenie, z jakim typem danych mam do czynienia:
let isType = (value, Cls) => {
try {
return Object.getPrototypeOf(value).constructor === Cls;
} catch(err) {
return false;
}
};
Zauważ, że ta funkcja ignoruje polimorfizm - oczekuje value
się, że będzie to bezpośrednia instancja Cls
, a nie instancja podklasy Cls
. Unikam instanceof
z dwóch głównych powodów:
([] instanceof Object) === true
(„Tablica to obiekt”)
('' instanceof String) === false
(„Ciąg nie jest ciągiem”)
Zauważ, że Object.getPrototypeOf
jest używany, aby uniknąć przypadku takiego jak let v = { constructor: String };
. isType
Funkcja nadal zwraca poprawnie dla isType(v, String)
(fałsz) iisType(v, Object)
(prawda).
Ogólnie polecam korzystanie z tej isType
funkcji wraz z następującymi wskazówkami:
- Zminimalizuj ilość wartości przetwarzania kodu nieznanego typu. Np.
let v = JSON.parse(someRawValue);
Nasza v
zmienna jest teraz nieznanego typu. Jak najwcześniej powinniśmy ograniczyć nasze możliwości. Najlepszym sposobem na to może być wymaganie określonego typu: np. if (!isType(v, Array)) throw new Error('Expected Array');
- jest to naprawdę szybki i ekspresyjny sposób na usunięcie ogólnej natury v
i zapewnienie, że zawsze będzie Array
. Czasami jednak musimy pozwolić v
na bycie wielu typów. W takich przypadkach v
jak najwcześniej powinniśmy utworzyć bloki kodu, które nie są już ogólne.
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');
}
- Zawsze używaj „białych list” do walidacji. Jeśli potrzebujesz, aby wartością była np. Ciąg, Liczba lub Tablica, sprawdź te 3 „białe” możliwości i wyrzuć Błąd, jeśli żadna z 3 nie jest spełniona. Powinniśmy być w stanie zobaczyć, że sprawdzanie dla „czarnych” możliwości nie jest bardzo przydatne: Say piszemy
if (v === null) throw new Error('Null value rejected');
- to jest wielki dla zapewnienia, że null
wartości nie zrobić to przez, ale jeśli to jest wartość nie zrobić to przez, wciąż wiemy mało cokolwiek na ten temat. Wartość, v
która przejdzie kontrolę zerową, jest nadal BARDZO ogólna - to nic innegonull
! Czarne listy prawie nie rozpraszają ogólności.
O ile wartość nie jest null
, nigdy nie uważaj „wartości pustej”. Zamiast tego rozważ „X, który jest pusty”. Zasadniczo nigdy nie rozważaj robienia czegoś podobnego if (isEmpty(val)) { /* ... */ }
- bez względu na to, jak ta isEmpty
funkcja jest zaimplementowana (nie chcę wiedzieć ...), to nie ma znaczenia! I to jest zbyt ogólne! Wakat należy obliczać tylko przy znajomości val
rodzaju. Kontrole próżni powinny wyglądać następująco:
- „Ciąg bez znaków”:
if (isType(val, String) && val.length === 0) ...
- „Obiekt z 0 rekwizytami”:
if (isType(val, Object) && Object.entries(val).length === 0) ...
- „Liczba równa lub mniejsza niż zero”:
if (isType(val, Number) && val <= 0) ...
„Tablica bez przedmiotów”: if (isType(val, Array) && val.length === 0) ...
Jedynym wyjątkiem jest sytuacja, gdy null
jest używany do oznaczenia określonej funkcjonalności. W tym przypadku sensowne jest powiedzenie: „Pusta wartość”:if (val === null) ...