Puste tablice wydają się być równe prawdzie i fałszowi w tym samym czasie


201

Puste tablice są prawdziwe, ale są również równe false.

var arr = [];
console.log('Array:', arr);
if (arr) console.log("It's true!");
if (arr == false) console.log("It's false!");
if (arr && arr == false) console.log("...what??");

Wydaje mi się, że wynika to z niejawnej konwersji obsługiwanej przez operatora równości.

Czy ktoś może wyjaśnić, co dzieje się za kulisami?


1
Oto podobny wątek, który powinien rzucić nieco światła na ten problem: stackoverflow.com/questions/4226101/…
Rion Williams

2
Zauważ, że arr == trueto nie zgadza się z prawdą ;-)
Michael Krelin - haker

5
Wow ... właśnie wtedy, gdy myślałeś, że już to wszystko straciłeś.
harpo

3
Aby uniknąć przymusu typu JavaScript WTF, użyj opcji ścisłej równości ===. Jeśli chcesz przetestować pustość tablicy, użyjarr === []
DjebbZ

17
Jeśli chcesz przetestować pustość tablicy NIE NALEŻY jej używać arr === [], ponieważ ZAWSZE zwróci ona wartość false, ponieważ prawa strona tworzy nową tablicę, a zmienna po lewej nie może odnosić się do czegoś, co właśnie utworzyłeś. Testowanie pustki należy wykonać, podnosząc wzrok arr.length === 0.
Kyle Baker

Odpowiedzi:


274

Testujesz tutaj różne rzeczy.

if (arr) wywołany na obiekcie (Array jest instancją Object w JS) sprawdzi, czy obiekt jest obecny i zwróci true / false.

Podczas wywoływania if (arr == false)porównujesz wartości tego obiektu z pierwotną falsewartością. arr.toString()Nazywa się wewnętrznie, co zwraca pusty ciąg "".

Wynika to z tego, że toStringwywołana tablica zwraca Array.join(), a pusty ciąg znaków jest jedną z wartości fałszowania w JavaScript.


2
Czy możesz wyjaśnić, dlaczego Boolean([])powraca true?
Devy

11
w konwencji JS, jeśli obiekty są wymuszone na wartość logiczną, zawsze są wymuszone na wartość PRAWDA. spójrz na tabelę „Boolean context” pod adresem: javascript.info/tutorial/object-conversion
Niki

2
@Devy wszystkie obiekty w JavaScript są zgodne z prawdą, więc konwersja dowolnych obiektów na Boolean jest prawdziwa. Zobacz 2ality.com/2013/08/objects-truthy.html
Thomson

62

Jeśli chodzi o linię:

if (arr == false) console.log("It's false!");

Może te pomogą:

console.log(0 == false) // true
console.log([] == 0) // true
console.log([] == "") // true

Moim zdaniem dzieje się tak, że wartość logiczna falsejest zmuszana do 0porównania z obiektem (lewa strona). Obiekt jest przymuszany do łańcucha (pusty łańcuch). Następnie pusty ciąg jest również przymuszany do liczby, a mianowicie do zera. Tak więc końcowe porównanie to 0== 0, czyli true.

Edycja: zapoznaj się z tą sekcją specyfikacji, aby uzyskać szczegółowe informacje na temat tego, jak to działa.

Oto, co się dzieje, zaczynając od zasady nr 1:

1. Jeśli typ (x) jest inny niż typ (y), przejdź do kroku 14.

Następna zasada, która ma zastosowanie, to # 19:

19. Jeśli typ (y) jest wartością logiczną, zwróć wynik porównania x == ToNumber (y).

Rezultatem ToNumber(false)jest 0, więc teraz mamy:

[] == 0

Ponownie reguła nr 1 mówi nam, aby przejść do kroku nr 14, ale następnym krokiem, który faktycznie ma zastosowanie, jest nr 21:

21. Jeśli Type (x) to Object, a Type (y) to String lub Number, zwróć wynik porównania ToPrimitive (x) == y.

Wynikiem ToPrimitive([])jest pusty ciąg, więc teraz mamy:

"" == 0

Ponownie reguła nr 1 mówi nam, aby przejść do kroku nr 14, ale następnym krokiem, który faktycznie ma zastosowanie, jest nr 17:

17. Jeśli Type (x) to String, a Type (y) to Number, zwróć wynik porównania ToNumber (x) == y.

Rezultatem ToNumber("")jest 0, co pozostawia nam:

0 == 0

Teraz obie wartości mają ten sam typ, więc kroki są kontynuowane od nr 1 do nr 7, co oznacza:

7. Jeśli x jest tą samą liczbą, co y, zwróć true.

Więc wracamy true.

W skrócie:

ToNumber(ToPrimitive([])) == ToNumber(false)

2
Świetne referencje! Aby uniknąć nieporozumień, warto wspomnieć, że przyczyną „następnej reguły, która ma zastosowanie, jest nr 19”, chociaż reguła nr 1 mówi „przejdź do kroku 14”, ponieważ kroki 14–18 nie pasują do typów porównywane wartości.
Sean the Bean

2
Ładne wyjaśnienie. Zastanawiam mnie, że puste tablice są uważane za prawdziwe, 0 to falsey, a jednak [] == 0jest prawdą. Rozumiem, jak to się dzieje na podstawie twojego wyjaśnienia specyfikacji, ale z logicznego punktu widzenia wygląda to na dziwne zachowanie językowe.
bigh_29,

7

Aby uzupełnić odpowiedź Wayne'a i spróbować wyjaśnić, dlaczego ToPrimitive([])powraca "", warto rozważyć dwa możliwe typy odpowiedzi na pytanie „dlaczego”. Pierwszy typ odpowiedzi brzmi: „ponieważ specyfikacja mówi, że tak będzie działał JavaScript”. W specyfikacji ES5, sekcja 9.1 , która opisuje wynik ToPrimitive jako wartość domyślną dla obiektu:

Domyślna wartość obiektu jest pobierana poprzez wywołanie wewnętrznej metody [[DefaultValue]] obiektu, przekazując opcjonalną wskazówkę PreferredType.

Sekcja 8.12.8 opisuje tę [[DefaultValue]]metodę. Ta metoda przyjmuje „podpowiedź” jako argument, a podpowiedź może być ciągiem lub liczbą. Aby uprościć sprawę, rezygnując z niektórych szczegółów, jeśli podpowiedź to String, to [[DefaultValue]]zwraca wartość toString()if, jeśli istnieje i zwraca pierwotną wartość, a w przeciwnym razie zwraca wartość valueOf(). Jeśli wskazówką jest Liczba, priorytety toString()i valueOf()są odwrócone, tak więc valueOf()wywoływana jest pierwsza, a jej wartość zwracana, jeśli jest prymitywna. Tak więc, czy [[DefaultValue]]zwraca wynik toString()lub valueOf()zależy od określonego PreferredType dla obiektu i czy te funkcje zwracają wartości prymitywne.

Domyślna valueOf()metoda Object po prostu zwraca sam obiekt, co oznacza, że ​​jeśli klasa nie zastąpi metody domyślnej, valueOf()po prostu zwraca sam Obiekt. Tak jest w przypadku Array. [].valueOf()zwraca []sam obiekt . Ponieważ Arrayobiekt nie jest prymitywny, [[DefaultValue]]podpowiedź nie ma znaczenia: wartością zwracaną dla tablicy będzie wartość toString().

Cytując JavaScript Davida Flanagana : The Definitive Guide , który, nawiasem mówiąc, jest świetną książką, która powinna być pierwszym miejscem, w którym każdy powinien uzyskać odpowiedzi na tego rodzaju pytania:

Szczegóły tej konwersji obiekt-liczba wyjaśniają, dlaczego pusta tablica konwertuje na liczbę 0 i dlaczego tablica z pojedynczym elementem może również konwertować na liczbę. Tablice dziedziczą domyślną metodę valueOf (), która zwraca obiekt, a nie pierwotną wartość, więc konwersja tablic na liczby zależy od metody toString (). Puste tablice są konwertowane na pusty ciąg. A pusty łańcuch konwertuje na liczbę 0. Tablica z pojedynczym elementem konwertuje na ten sam ciąg, co ten jeden element. Jeśli tablica zawiera pojedynczy numer, liczba ta jest konwertowana na ciąg znaków, a następnie z powrotem na liczbę.

Drugi rodzaj odpowiedzi na pytanie „dlaczego”, inny niż „ponieważ specyfikacja mówi”, daje pewne wyjaśnienie, dlaczego zachowanie ma sens z punktu widzenia projektu. W tej kwestii mogę tylko spekulować. Po pierwsze, jak przekonwertować tablicę na liczbę? Jedyną sensowną możliwością, jaką mogę wymyślić, byłoby przekonwertowanie pustej tablicy na 0, a dowolnej niepustej tablicy na 1. Ale jak ujawniła odpowiedź Wayne'a, pusta tablica zostanie przekonwertowana na 0 dla wielu typów porównań. Poza tym trudno jest wymyślić sensowną prymitywną wartość zwracaną dla Array.valueOf (). Można więc argumentować, że sensowniej jest mieć Array.valueOf()wartość domyślną i zwrócić samą tablicę, co prowadzi toString()do wyniku używanego przez ToPrimitive. Bardziej sensowne jest przekonwertowanie tablicy na ciąg znaków niż na liczbę.

Ponadto, jak sugeruje cytat Flanagana, ta decyzja projektowa umożliwia pewne rodzaje korzystnych zachowań. Na przykład:

var a = [17], b = 17, c=1;
console.log(a==b);      // <= true
console.log(a==c);      // <= false

To zachowanie pozwala porównać tablicę jednoelementową z liczbami i uzyskać oczekiwany wynik.


Dzięki za odpowiedź, to dość szczegółowe wyjaśnienie, którego brakowało w pytaniu.
Estus Flask

3
console.log('-- types: undefined, boolean, number, string, object --');
console.log(typeof undefined);  // undefined
console.log(typeof null);       // object
console.log(typeof NaN);        // number
console.log(typeof false);      // boolean
console.log(typeof 0);          // number
console.log(typeof "");         // string
console.log(typeof []);         // object
console.log(typeof {});         // object

console.log('-- Different values: NotExist, Falsy, NaN, [], {} --');
console.log('-- 1. NotExist values: undefined, null have same value --');
console.log(undefined == null); // true

console.log('-- 2. Falsy values: false, 0, "" have same value --');
console.log(false == 0);        // true
console.log(false == "");       // true
console.log(0 == "");           // true

console.log('-- 3. !NotExist, !Falsy, and !NaN return true --');
console.log(!undefined);        // true
console.log(!null);             // true

console.log(!false);            // true
console.log(!"");               // true
console.log(!0);                // true

console.log(!NaN);              // true

console.log('-- 4. [] is not falsy, but [] == false because [].toString() returns "" --');
console.log(false == []);       // true
console.log([].toString());     // ""

console.log(![]);               // false

console.log('-- 5. {} is not falsy, and {} != false, because {}.toString() returns "[object Object]" --');
console.log(false == {});       // false
console.log({}.toString());     // [object Object]

console.log(!{});               // false

console.log('-- Comparing --');
console.log('-- 1. string will be converted to number or NaN when comparing with a number, and "" will be converted to 0 --');
console.log(12 < "2");          // false
console.log("12" < "2");        // true
console.log("" < 2);            // true

console.log('-- 2. NaN can not be compared with any value, even if NaN itself, always return false --');
console.log(NaN == NaN);        // false

console.log(NaN == null);       // false
console.log(NaN == undefined);  // false
console.log(0 <= NaN);          // false
console.log(0 >= NaN);          // false
console.log(undefined <= NaN);  // false
console.log(undefined >= NaN);  // false
console.log(null <= NaN);       // false
console.log(null >= NaN);       // false

console.log(2 <= "2a");         // false, since "2a" is converted to NaN
console.log(2 >= "2a");         // false, since "2a" is converted to NaN

console.log('-- 3. undefined can only == null and == undefined, and can not do any other comparing even if <= undefined --');
console.log(undefined == null);         // true
console.log(undefined == undefined);    // true

console.log(undefined == "");           // false
console.log(undefined == false);        // false
console.log(undefined <= undefined);    // false
console.log(undefined <= null);         // false
console.log(undefined >= null);         // false
console.log(0 <= undefined);            // false
console.log(0 >= undefined);            // false

console.log('-- 4. null will be converted to "" when <, >, <=, >= comparing --');
console.log(12 <= null);        // false
console.log(12 >= null);        // true
console.log("12" <= null);      // false
console.log("12" >= null);      // true

console.log(0 == null);         // false
console.log("" == null);        // false

console.log('-- 5. object, including {}, [], will be call toString() when comparing --');
console.log(12 < {});           // false, since {}.toString() is "[object Object]", and then converted to NaN
console.log(12 > {});           // false, since {}.toString() is "[object Object]", and then converted to NaN
console.log("[a" < {});         // true, since {}.toString() is "[object Object]"
console.log("[a" > {});         // false, since {}.toString() is "[object Object]"
console.log(12 < []);           // false, since {}.toString() is "", and then converted to 0
console.log(12 > []);           // true, since {}.toString() is "", and then converted to 0
console.log("[a" < []);         // false, since {}.toString() is ""
console.log("[a" > []);         // true, since {}.toString() is ""

console.log('-- 6. According to 4 and 5, we can get below weird result: --');
console.log(null < []);         // false
console.log(null > []);         // false
console.log(null == []);        // false
console.log(null <= []);        // true
console.log(null >= []);        // true

2

W if (arr) zawsze jest oceniane (ToBoolean) na true, jeśli arr jest obiektem, ponieważ wszystkie obiekty w JavaScript są prawdziwe . (null nie jest przedmiotem!)

[] == falsejest oceniany w podejściu iteracyjnym. Na początku, jeśli jedna strona ==jest prymitywna, a druga to obiekt, najpierw konwertuje obiekt na prymityw, a następnie konwertuje obie strony na Liczba, jeśli obie strony nie są string(porównanie ciągów jest stosowane, jeśli obie strony są ciągami). Porównanie jest więc powtarzane jak: [] == false-> '' == false-> 0 == 0-> true.


2

Przykład:

const array = []
const boolValueOfArray = !!array // true

Dzieje się tak, ponieważ

ToNumber(ToPrimitive([])) == ToNumber(false)  
  1. []jest pusty Arrayobiekt → ToPrimitive([])→ „” → ToNumber("")0
  2. ToNumber(false) → 0
  3. 0 == 0 → prawda

1

Tablica z elementami (niezależnie od tego, czy 0, fałsz, czy inna pusta tablica) zawsze decyduje się na trueużycie Abstrakcyjnego porównania równości ==.

1. [] == false; // true, because an empty array has nothing to be truthy about
2. [2] == false; // false because it has at least 1 item
3. [false] == false; // also false because false is still an item
4. [[]] == false; // false, empty array is still an item

Ale używając porównania ścisłej równości ===, próbujesz ocenić zawartość zmiennej, a także jej typ danych, dlatego:

1. [] === false; // false, because an array (regardless of empty or not) is not strictly comparable to boolean `false`
2. [] === true; // false, same as above, cannot strictly compare [] to boolean `true`
3. [[]] === false; // true, because see #1

-1

Możesz opróżnić tablicę JavaScript, odwołując się do nowej tablicy, używając list = []lub usuwając elementy aktualnie przywoływanej tablicy list.length = 0.

Źródło: JavaScript Empty Array


-2

Żadne z powyższych nie pomogło mi, gdy próbowałem użyć wtyczki mapowania knockout.js, być może ponieważ „pusta tablica” nie jest tak naprawdę pusta.

Skończyło się na tym: data-bind="if: arr().length"co zrobiło.

Jest to specyficzne dla nokautu, a nie pytania OP, ale może pomoże komuś innemu przeglądać tutaj w podobnej sytuacji.


Ta odpowiedź nie jest powiązana
fauverism

tylko stycznie, zgodnie z zastrzeżeniem :)
domoarigato,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.