Empty arrays seem to equal true and false at the same time
Asked Answered
C

10

237

Empty arrays are true but they're also equal to 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??");

I guess this is due to the implicit conversion operated by the equality operator.

Can anyone explain what's going on behind the scenes?

Corrianne answered 30/3, 2011 at 19:59 Comment(8)
Here's a similar thread that should shed some light on the issue : #4226601Hosmer
Note, that arr == true does not evaluate to true ;-)Microcurie
@Rionmonster thanks, I wasn't able to find that thread (or maybe I didn't search hard enough) :)Corrianne
Wow... just when you thought you had all this down.Minicam
@DjebbZ both ([] == []) and ([] === []) should always evaluate to false! Arrays are objects and compared by reference equality; two array variables can hold the same contents but still differ.Mesenchyme
What about testing for arr.length ? How does that compare to testing for [] ?Jester
You forgot if (arr == !arr) console.log('There is no spoon');Feed
If you want to test the emptiness of an array DO NOT use arr === [], as that will ALWAYS return false, since the right side is instantiating a new array, and the variable on the left cannot refer to something you just created. Testing emptiness should be done by looking up arr.length === 0.Supersensual
P
307

You're testing different things here.

if (arr) called on object (Array is instance of Object in JS) will check if the object is present, and returns true/false.

When you call if (arr == false) you compare values of this object and the primitive false value. Internally, arr.toString() is called, which returns an empty string "".

This is because toString called on Array returns Array.join(), and empty string is one of falsy values in JavaScript.

Phycomycete answered 30/3, 2011 at 20:10 Comment(4)
Can you explain whyBoolean([]) returns true?Pigfish
that's by convention, in JS if objects are coerced to Boolean, they are always coerced to TRUE. look at the "Boolean context" table at: javascript.info/tutorial/object-conversionAllochthonous
@Pigfish all objects in JavaScript are truthy, so the convert any objects to Boolean is true. See 2ality.com/2013/08/objects-truthy.htmlJeannettejeannie
"When you call if (arr == false) you compare values of this object and the primitive false value. Internally, arr.toString() is called, which returns an empty string "". This is because toString called on Array returns Array.join(), and empty string is one of falsy values in JavaScript." That makes it sound like the reason the comparison hods up is because empty string is falsy. Yet. 1. the falsyness is not relevant and 2. "" == false is not the comparison that runs. The comparison will make them the same type [] == false -> [] == 0 -> "" == 0 -> 0 == 0Dioptase
E
68

Regarding the line:

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

Maybe these will help:

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

What I believe is happening is that the boolean false is coerced to 0 for comparison with an object (the left-hand side). The object is coerced to a string (the empty string). Then, the empty string is coerced into a number, as well, namely zero. And so the final comparison is 0 == 0, which is true.

Edit: See this section of the spec for details on exactly how this works.

Here's what's happening, starting at rule #1:

1. If Type(x) is different from Type(y), go to step 14.

The next rule that applies is #19:

19. If Type(y) is Boolean, return the result of the comparison x == ToNumber(y).

The result of ToNumber(false) is 0, so we now have:

[] == 0

Again, rule #1 tells us to jump to step #14, but the next step that actually applies is #21:

21. If Type(x) is Object and Type(y) is either String or Number, return the result of the comparison ToPrimitive(x)== y.

The result of ToPrimitive([]) is the empty string, so we now have:

"" == 0

Again, rule #1 tells us to jump to step #14, but the next step that actually applies is #17:

17. If Type(x) is String and Type(y) is Number, return the result of the comparison ToNumber(x)== y.

The result of ToNumber("") is 0, which leaves us with:

0 == 0

Now, both values have the same type, so the steps continue from #1 until #7, which says:

7. If x is the same number value as y, return true.

So, we return true.

In brief:

ToNumber(ToPrimitive([])) == ToNumber(false)
Elevenses answered 30/3, 2011 at 20:11 Comment(2)
Great reference! To avoid confusion, it might be helpful to mention that the reason "the next rule that applies is #19" even though rule #1 says "go to step 14", is because steps 14-18 don't match the types of the values being compared.Taegu
Nice explanation. It is puzzling to me that empty arrays are considered to be truthy, 0 is falsey, and yet [] == 0 is true. I get how this happens based on your explanation of the spec, but it seems like odd language behavior from a logical standpoint.Hautbois
P
10

To supplement Wayne's answer and to try to explain why ToPrimitive([]) returns "", it's worth considering two possible types of answers to the 'why' question. The first type of answer is: "because the specification says this is how JavaScript will behave." In the ES5 spec, section 9.1, which describes the result of ToPrimitive as a default value for an Object:

The default value of an object is retrieved by calling the [[DefaultValue]] internal method of the object, passing the optional hint PreferredType.

Section 8.12.8 describes the [[DefaultValue]] method. This method takes a "hint" as an argument, and the hint can be either String or Number. To simplify the matter by dispensing with some details, if the hint is String, then [[DefaultValue]] returns the value of toString() if it exists and returns a primitive value and otherwise returns the value of valueOf(). If the hint is Number, the priorities of toString() and valueOf() are reversed so that valueOf() is called first and its value returned if it's a primitive. Thus, whether [[DefaultValue]] returns the result of toString() or valueOf() depends on the specified PreferredType for the object and whether or not these functions return primitive values.

The default valueOf() Object method just returns the object itself, which means that unless a class overrides the default method, valueOf() just returns the Object itself. This is the case for Array. [].valueOf() returns the object [] itself. Since an Array object is not a primitive, the [[DefaultValue]] hint is irrelevant: the return value for an array will be the value of toString().

To quote David Flanagan's JavaScript: The Definitive Guide, which, by the way, is a superb book that should be everyone's first place to get answers to these types of questions:

The details of this object-to-number conversion explain why an empty array converts to the number 0 and why an array with a single element may also convert to a number. Arrays inherit the default valueOf() method that returns an object rather than a primitive value, so array-to-number conversion relies on the toString() method. Empty arrays convert to the empty string. And the empty string converts to the number 0. An array with a single element converts to the same string that that one element does. If an array contains a single number, that number is converted to a string, and then back to a number.

The second type of answer to the "why" question, other than "because the spec says", gives some explanation for why the behavior makes sense from the design perspective. On this issue I can only speculate. First, how would one convert an array to a number? The only sensible possibility I can think of would be to convert an empty array to 0 and any non-empty array to 1. But as Wayne's answer revealed, an empty array will get converted to 0 for many types of comparisons anyway. Beyond this, it's hard to think of a sensible primitive return value for Array.valueOf(). So one could argue that it just makes more sense to have Array.valueOf() be the default and return the Array itself, leading toString() to be the result used by ToPrimitive. It just makes more sense to convert an Array to a string, rather than a number.

Moreover, as hinted by the Flanagan quote, this design decision does enable certain types of beneficial behaviors. For instance:

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

This behavior allows you to compare a single-element array to numbers and get the expected result.

Piceous answered 11/5, 2017 at 1:28 Comment(0)
H
4
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
Hypotenuse answered 18/12, 2018 at 9:46 Comment(0)
J
3

In if (arr), it is always evaluated (ToBoolean) to true if arr is an object because all objects in JavaScript are truthy. (null is not an object!)

[] == false is evaluated in iterative approach. At first, if one side of == is primitive and the other is object, it converts object to primitive at first, then converts both sides to Number if both sides are not string (string comparison is used if both sides are strings). So the comparison is iterated like, [] == false -> '' == false -> 0 == 0 -> true.

Jeannettejeannie answered 17/10, 2016 at 21:50 Comment(0)
P
2

Example:

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

It happens because

ToNumber(ToPrimitive([])) == ToNumber(false)  
  1. [] is empty Array object → ToPrimitive([]) → "" → ToNumber("")0
  2. ToNumber(false) → 0
  3. 0 == 0 → true
Propertied answered 4/5, 2018 at 8:47 Comment(0)
W
1

An array with elements (regardless if 0, false or another empty array), always resolves to true using Abstract Equality Comparison ==.

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

But using a Strict Equality Comparison ===, you are attempting to evaluate the variable's content as well as its data type that is why:

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
Woebegone answered 10/5, 2020 at 8:9 Comment(1)
3. [[]] === false is false , not true.Soapbark
A
0

It seems no one is talking about why (arr && arr == false) is true. Just for people who are not sure about the operator precedence, accourding to MDN, == has higher precedence than &&, so it is actually (arr && (arr == false)). After wildcard's answer , it's true && true, so it's true.

var arr = [];
if (arr && arr == false) console.log("...what??");
Arguelles answered 8/7, 2022 at 0:55 Comment(0)
N
-2

You can empty a JavaScript Array by referencing it to a new array, using list = [] or deleting the elements of the currently referenced array list.length = 0.

Source: JavaScript Empty Array

Nmr answered 10/8, 2018 at 6:58 Comment(0)
J
-3

None of the above helped me, when trying to use the knockout.js mapping plugin, perhaps since an "empty array" isn't really empty.

I ended up using: data-bind="if: arr().length" which did the trick.

This is specific to knockout, not the OP's question, but maybe it will help someone else browsing here in a similar situation.

Jennette answered 25/12, 2014 at 10:37 Comment(2)
This answer is not relatedSleight
only tangentially, as disclaimed :)Jennette

© 2022 - 2024 — McMap. All rights reserved.