This question already has answers here:
Why doesn't equality check work with arrays [duplicate]
(6 answers)
Closed 2 years ago.
I was playing around with arrays in Javascript today and noticed this little gem:
alert([1, 2, 3] == [1, 2, 3]); //alerts false
It strikes me as rather odd that the array is not equal to itself.
But then I noticed this, which was even weirder:
alert([1, 2, 3] == "1,2,3"); //alerts true
?!?!?!?!!!?
Why in the world is [1, 2, 3] not == to itself but is == to the string?
I realize that == is not the same as ===. Even so, what evilness could cause Mr. Javascript do such weird things?
Ok, so first you need to understand how javascript treats values in your program. All of your variables that you create are going to merely be references to a location in memory where that object is stored. Therefore, when you do this:
alert( [1,2,3] == [1,2,3] );
...it does three things:
Place an array ([1,2,3]) onto the heap
Place another array ([1,2,3]) onto the heap (notice it will have a different memory location)
Compare the two references. They point to different objects in different locations in memory, thus it is considered not equal.
You can check for some sane behavior by running this code:
var a = [1,2,3];
var b = a;
alert (a == b) // Result is true. Both point to the same object.
Now for your question about the string
When you use the == operator tries to convert the two operands to the same type (evil behavior...I know...)
When it does this, it decides to convert both to a string before it does the compare (thus the result is really "1,2,3" === "1,2,3", which evaluates to true.
I can't give you a complete picture, as there are few people who understand every nuance of the madness that is JavaScript, but hopefully this clears some of the fog.
== operator
[..] If either operand is a string, the other operand is converted to a string if possible. [..] If both operands are objects, then JavaScript compares internal references which are equal when operands refer to the same object in memory.
https://developer.mozilla.org/en/JavaScript/Reference/Operators/Comparison_Operators
That is to say, [1, 2, 3] converted to a string equals "1,2,3". One array object does not equal another array object though.
For the first part, you are creating two distinct objects as arrays are just objects and since you created two of them, they are both unique.
Because == only coerces if it has to to get the same type (e.g, only when the types of the operands are different). When doing
alert([1, 2, 3] == [1, 2, 3]); //alerts false
...no coercion is required; both are objects. They're not the same object, so it's false. People think of == as the "coercing" equality operator, and it is, but the key is that it only coerces if it has to.
But doing
alert([1, 2, 3] == "1,2,3"); //alerts true
...involves operands of different types: string and object. So coercion is done. In this case, the object is coerced to string as though with String(obj), which invokes its default toString behavior, which for arrays is .join(). join defaults to "," as the separator, so the resulting string matches "1,2,3". (You can find the full logic of why the object gets coerced to string in the specification.)
The first comparison fails because a basic comparison of two objects will check to see if they are literally the same referenced object, not if the two objects have the same values. If you wish to compare two arrays, you'll have to loop over the values.
function arrayCompare(arr1, arr2) {
if (arr1.length !== arr2.length) return false;
for (var i = 0, len = arr1.length; i < len; i++) {
if (arr1[i] !== arr2[i]) return false;
}
return true;
}
Just remember that this is not a recursive comparison, so it will only work on an array of primitive values.
The second comparison works because == will attempt to coerce the types of the arguments, and when you convert an array to a string, that is the result.
[1,2,3].toString() === '1,2,3'
The two Array objects are distinct, and thus not equal when using the == comparison. To compare them, you'd need to loop, checking the index in both are identical (and being recursive if the elements are Arrays too or Objects).
The second is because the Array had its toString() implicitly called which returned '1,2,3' (try it).
This is because by the rules of a == (non strict) comparison in ECMAScript, the left hand operator was coerced to a String (== does type conversion).
Related
This question already has answers here:
Why doesn't equality check work with arrays [duplicate]
(6 answers)
Closed 7 years ago.
I am storing (x,y) coordinates as 2-element arrays.
var coordinateA = [0,3];
var coordinateB = [1,2];
I also have a longer array containing many of these coordinates:
var coordinates = [coordinateA, coordinateB]
Imagine my surprise when the following statements turned out to be false:
jQuery.inArray(coordinateA, coordinates); // returns -1
coordinateA == coordinates[0]; // returns false
[0,3] == [0,3]; // returns false(!)
coordinateA == coordinateA; // returns true, thankfully
Could someone help me understand why this is the case? Also, is there a better way to represent 2D coordinates in Javascript? Thanks for any clues or suggestions.
This is because you have two separate array references.
The equality operator is checking that the references are equal, not the content of the arrays.
One of the puzzling things about JavaScript is how equality is dealt with. I will do my best to explain this.
The equality rules can be quite hard to grasp. Generally speaking, you can compare by relative equality (==) or strict equality (===).
relative equality:
This compares by value only and does not care about type.
Example
var x = '2';
var y = 2;
x == y;
=> false;
In relative equality, the string "2" equals the number 2. This will return true since types are not compared
strict equality
This compares by both value and type.
Example
var x = '2';
var y = 2;
x === y;
=> false
In this case, the string "2" does NOT equal the number 2. Because String and Number are two different types.
Comparisons with arrays and objects are done differently though.
In your case, arrays are considered objects.
typeof([1,2])
=> "object"
In JavaScript, all objects are different. They are compared by their object ids. To determine if arrays are equal, you have to perform type conversion to a string.
String([1,2]) == String([1,2])
=> true
However, the underscore library has an is_equal method that can determine whether two arrays are equal
_.isEqual(array1, array2);
Underscore does this by performing a deep comparison between two objects to determine if they should be considered equal.
It's important to note that order matters here, as it does in the string comparison.
_isEqual([1,2], [1,2])
=> true
_isEqual([1,2], [2,1])
=> false
I know that identical objects are not equal, i.e:
var obj = { name: "Value" };
var obj2 = { name: "Value" };
console.log("obj equals obj2: " + (obj === obj2)); //evaluates to false
Yet primitive types are:
var str = "string1";
var str2 = "string1";
console.log("str equals str2: " + (str === str2)); //evaluates to true
My question is why. Why are objects and primitives treated differently? If an object is nothing but an empty container, with only the attributes you specify to put in the container, why wouldn't the container's identical attributes evaluate to be the same? I looked around for this answer on SO and elsewhere, but didn't find an answer.
Is a JS object treated as something different in the DOM than a primitive type?
Thanks
This seems to really be a question about === so let's look at the Strict Equality Comparison Algorithm, in which point 7 says
Return true if x and y refer to the same object. Otherwise, return false.
So what does it mean to be "the same object"? It means they don't just look like eachother, but are at the same place in memory too. This means that the only time when an Object is === to an Object is when they're the same thing.
var a = {},
b = {}, // identical to `a`
c = a; // same as `a`
a === b; // false
a === c; // true
b === c; // false
When a variable's value is an object, well, it isn't an object: it's a reference to an object. Two variables that contain references to the same object are indeed equal:
var myObj = { hello: "world" };
var a = myObj;
var b = myObj;
if (a == b) alert("YES!!"); // YES!!
When the == operator has object references on both sides, the comparison made is to test whether the objects refer to the same object. When primitive values are involved, the semantics are different: the values are directly compared.
Generally, === operator checks for types, and if they are the same, checks values. Object type contains a reference, so, to be equal, they have to reference the same object and be of the same type. String literal value is not a reference, it is a value, so the === will produce true for string literals, but not for "abc" === new String("abc") because latter is an Object.
More information can be found here: A lot of details can be explored from here: Which equals operator (== vs ===) should be used in JavaScript comparisons?
First off, JavaScript objects aren't part of the DOM. The DOM (Document Object Model) are the HTML elements which make up your page. They cooperate together, but aren't directly linked.
Basically, yes, primitives are a special case. You can kind of think of it as if the value of a primitive is a constant (in a sense).
For example, take the example of the number 5. No matter how many times I declare 5, 5 will always equal 5. Thus, it isn't a stretch to say that {var a holding the value 5} is equivalent to {var b holding the value 5}. This concept is a little fuzzier with strings, but it still holds. A string that is "abc" is always the same as any other variable holding a string that is "abc".
This doesn't apply to objects either.
If you have two variables hold the same object, they are eqivalent.
var a = {};
var b = a;
console.log(a == b); // true
console.log(a === b); // true
However, if we create two objects that look similar:
var a = {};
var b = {};
console.log(a == b); // false
console.log(a === b); // false
This seems a bit weird at first, but think about the inner workings that are going on. Consider that when you pass an object in to a function, if you change that object, it is changed outside of the function to. It's passed by reference.
This means you can think of a pointer (a memory address) being stored in the variables. So, if you imagine that they have memory address in them (like 0x123456 and 0x654321), then it makes a little more sense (0x123456 and 0x654321 are different, so you wouldn't expend them to be equal). They are two separate things taking up their own area in the memory.
Make sense?
You can answer to this question at several levels.
strings
Factually, yes, strings are handled differently from objects as far as strict comparison operator is concerned.
Semantically, that is more convenient than having to resort to strcmp or equivalent mechanisms to compare two strings.
Implementation-wise, the cost is neglectible, so JavaScript can offer you that convenience.
By the way, people telling the strict equality operator checks if both variables point to the same memory location are wrong. In case of strings, === will succeed if the string contents are equal, wherever they might be located in memory.
Objects
Semantically, contrary to primitive types like numbers or strings, it is difficult to offer a consistent set of comparison operators for objects.
You could do an in-depth comparison for equality, but greater/lower operators would make little sense.
The choice of Javascript is rather inconsistent here.
the semantics of equality comparison (be it == or ===) are limited to references
(i.e. == or === will succeed if the references are equal).
Implementation-wise, a deep comparison could be quite costly.
There are also subtelties as how to interpret undefined properties.
At any rate, JavaScript did not choose to implement a deep comparison, so if you want one, you'll have to do it yourself.
And there have been terabytes of code written to try and provide the ideal in-depth object comparison function.
ordered comparison is handled quite differently.
You can define a valueOf method that will return whatever primitive value you want to be used for ordered comparison, e.g
myObject.prototype.valueOf = function(){return this.my_comparison_value; };
If not explicitely defined, valueOf will default to "[object Object]".
So if you don't supply a valueOf method:
< and > operators will always return false (which kind of makes sense).
>= and <= will always return true, regardless of the references being equal or not
(which makes a lot less sense).
Now if you take the pain to define a valueOf, equality comparison will still not use it.
The only way to have a consistent behaviour would be to combine <= and >=, e.g.
if (a >= b && a <= b) { // equality using valueOf
For browser-supplied primitive objects like DOM elements, the behaviour of ordering operators depends on what the browser decided to return as a default value.
I would not recomend using that unless you really know what you're doing.
I was encountering a lot of bugs in my code because I expected this expression:
Boolean([]); to evaluate to false.
But this wasn't the case as it evaluated to true.
Therefore, functions that possibly returned [] like this:
// Where myCollection possibly returned [ obj1, obj2, obj3] or []
if(myCollection)
{
// ...
}else
{
// ...
}
did not do expected things.
Am I mistaken in assuming that [] an empty array?
Also, Is this behavior consistent in all browsers? Or are there any gotchas there too? I observed this behavior in Goolgle Chrome by the way.
From http://www.sitepoint.com/javascript-truthy-falsy/
The following values are always falsy:
false
0 (zero)
0n (BigInt zero)
"" (empty string)
null
undefined
NaN (a special Number value meaning Not-a-Number!)
All other values are truthy, including "0" (zero in quotes), "false" (false in quotes), empty functions, empty arrays ([]), and empty objects ({}).
Regarding why this is so, I suspect it's because JavaScript arrays are just a particular type of object. Treating arrays specially would require extra overhead to test Array.isArray(). Also, it would probably be confusing if true arrays behaved differently from other array-like objects in this context, while making all array-like objects behave the same would be even more expensive.
You should be checking the .length of that array to see if it contains any elements.
if (myCollection) // always true
if (myCollection.length) // always true when array has elements
if (myCollection.length === 0) // same as is_empty(myCollection)
While [] equals false, it evaluates to true.
yes, this sounds bad or at least a bit confusing. Take a look at this:
const arr = [];
if (arr) console.log("[] is truethy");
if (arr == false) console.log("however, [] == false");
In practice, if you want to check if something is empty,
then check the length. (The ?. operator makes sure that also null is covered.)
const arr = []; // or null;
if (!arr?.length) console.log("empty or null")
[]==false // returns true
This evaluates to true, because of the Abstract Equality Algorithm as mentioned here in the ECMA Specification #Section 11.9.3
If you run through algorithm, mentioned above.
In first iteration, the condition satisfied is,
Step 7: If Type(y) is Boolean, return the result of the comparison x == ToNumber(y).
Hence the above condition transforms to -> [] == 0
Now in second iteration, the condition satisfied on [] == 0:
Step 9: If Type(x) is Object and Type(y) is either String or Number, return the result of the comparison ToPrimitive(x) == y.
[] is an object, henceforth, on converting to primitive, it transforms to an empty string ''
Hence, the above condition transforms to -> '' == 0
In third iteration, condition satisfied, is:
Step 5: If Type(x) is String and Type(y) is Number, return the result of the comparison ToNumber(x) == y.
As we know, empty string, '' is a falsy value, hence transforming an empty string to number will return us a value 0.
Henceforth, our condition, will transform to -> 0 == 0
In fourth iteration, the first condition is satisfied, where the types are equal and the numbers are equal.
Henceforth, the final value of the [] == false reduces to 0 == 0 which is true.
Hope this answers your question. Otherwise, you can also refer this youtube video
I suspect it has something to do with discrete math and the way a conditional (if then) works. A conditional has two parts, but in the instance where the first part doesn't exist, regardless of the second part, it returns something called a vacuous truth.
Here is the example stated in the wikipedia page for vacuous truths
"The statement "all cell phones in the room are turned off" will be true when no cell phones are in the room. In this case, the statement "all cell phones in the room are turned on" would also be vacuously true"
The wikipedia even makes specific mention of JavaScript a little later.
https://en.wikipedia.org/wiki/Vacuous_truth#:~:text=In%20mathematics%20and%20logic%2C%20a,the%20antecedent%20cannot%20be%20satisfied.&text=One%20example%20of%20such%20a,Eiffel%20Tower%20is%20in%20Bolivia%22.
Also want to add, that all objects in JavaScript (arrays are objects too) are stored in memory as links and these links are always not null or zero, that's why Boolean({}) === true, Boolean([]) === true.
Also this is the reason why same objects (copied by value not the link) are always not equal.
{} == {} // false
let a = {};
let b = a; // copying by link
b == a // true
As in JavaScript, everything is an object so for falsy and empty, I use the below condition:
if(value && Object.keys(value).length){
// Not falsy and not empty
}
else{
// falsy or empty array/object
}
In this answer there is a simple function that will return array equality for arrays that contain primitive values.
However, I'm not sure why it works. Here is the function:
function arrays_equal(a,b) { return !!a && !!b && !(a<b || b<a); }
I'm mostly interested in the second half; this bit:
!(a<b || b<a)
Why does the < and > work when comparing the arrays but the == doesn't?
How do the less than and greater than methods work within JavaScript?
With </>, the arrays are converted to strings first, and as such do not provide a reliable method of checking equality.
== does not work because objects are checked by reference:
[] == []; // false, two separate objects
var a = [];
a == a; // true, refer to the same object
The </> trick is flawed:
var a = [1, [2, 3]],
b = [[1, 2], 3];
!(a<b || b<a); // true
This evaluates to true, because they are both converted to the string "1,2,3" before they are checked (</> do not "directly" work for objects).
So basically, you are comparing equality of the strings. For strings, a == b is indeed the same as !(a<b || b<a) - </> for strings check character codes, so two equal strings are neither "smaller" nor "greater" because that's not the case for any character code in the strings.
However, I'm not sure why it works.
It doesn't work. Consider
arrays_equal(["1,2"], [1,2])
produces true even though by any definition of array equality based on element-wise comparison, they are different.
arrays_equal([[]], [])
and
arrays_equal([""], [])
are also spurious positives.
Simply adding length checking won't help as demonstrated by
arrays_equal(["1,2",3], [1,"2,3"])
arrays_equal(
["",","],
[",",""])
EDIT:
If you want a succinct way to test structural similarity, I suggest:
function structurallyEquivalent(a, b) {
return JSON.stringify(a) === JSON.stringify(b);
}
It doesn't stop early on inputs that are obviously different -- it walks both object graphs regardless of how disimilar they are, but so does the function in the OP.
One caveat: when you're using non-native JSON.stringify, it may do strange things for cyclic inputs like:
var input = [];
input[0] = input;
You can compare any two objects using ==. But since > and < are not defined for objects, they are converted to strings. Therefore, [1,2,3]>[2,1,3] is actually doing "1,2,3">"2,1,3"
I recently discovered that 2 == [2] in JavaScript. As it turns out, this quirk has a couple of interesting consequences:
var a = [0, 1, 2, 3];
a[[2]] === a[2]; // this is true
Similarly, the following works:
var a = { "abc" : 1 };
a[["abc"]] === a["abc"]; // this is also true
Even stranger still, this works as well:
[[[[[[[2]]]]]]] == 2; // this is true too! WTF?
These behaviors seem consistent across all browsers.
Any idea why this is a language feature?
Here are more insane consequences of this "feature":
[0] == false // true
if ([0]) { /* executes */ } // [0] is both true and false!
var a = [0];
a == a // true
a == !a // also true, WTF?
You can look up the comparison algorithm in the ECMA-spec (relevant sections of ECMA-262, 3rd edition for your problem: 11.9.3, 9.1, 8.6.2.6).
If you translate the involved abstract algorithms back to JS, what happens when evaluating 2 == [2] is basically this:
2 === Number([2].valueOf().toString())
where valueOf() for arrays returns the array itself and the string-representation of a one-element array is the string representation of the single element.
This also explains the third example as [[[[[[[2]]]]]]].toString() is still just the string 2.
As you can see, there's quite a lot of behind-the-scene magic involved, which is why I generally only use the strict equality operator ===.
The first and second example are easier to follow as property names are always strings, so
a[[2]]
is equivalent to
a[[2].toString()]
which is just
a["2"]
Keep in mind that even numeric keys are treated as property names (ie strings) before any array-magic happens.
It is because of the implicit type conversion of == operator.
[2] is converted to Number is 2 when compared with a Number. Try the unary + operator on [2].
> +[2]
2
var a = [0, 1, 2, 3];
a[[2]] === a[2]; // this is true
On the right side of the equation, we have the a[2], which returns a number type with value 2. On the left, we are first creating a new array with a single object of 2. Then we are calling a[(array is in here)]. I am not sure if this evaluates to a string or a number. 2, or "2". Lets take the string case first. I believe a["2"] would create a new variable and return null. null !== 2. So lets assume it is actually implicitly converting to a number. a[2] would return 2. 2 and 2 match in type (so === works) and value. I think it is implicitly converting the array to a number because a[value] expects a string or number. It looks like number takes higher precedence.
On a side note, I wonder who determines that precedence. Is because [2] has a number as it's first item, so it converts to a number? Or is it that when passing an array into a[array] it tries to turn the array into a number first, then string. Who knows?
var a = { "abc" : 1 };
a[["abc"]] === a["abc"];
In this example, you are creating an object called a with a member called abc. The right side of the equation is pretty simple; it is equivalent to a.abc. This returns 1. The left side first creates a literal array of ["abc"]. You then search for a variable on the a object by passing in the newly created array. Since this expects a string, it converts the array into a string. This now evaluates to a["abc"], which equals 1. 1 and 1 are the same type (which is why === works) and equal value.
[[[[[[[2]]]]]]] == 2;
This is just an implicit conversion. === wouldn't work in this situation because there is a type mismatch.
For the == case, this is why Doug Crockford recommends always using ===. It doesn't do any implicit type conversion.
For the examples with ===, the implicit type conversion is done before the equality operator is called.
[0] == false // true
if ([0]) { /* executes */ } // [0] is both true and false!
That's interesting, it's not that [0] is both true and false, actually
[0] == true // false
It is javascript's funny way of processing if() operator.
A array of one item can be treated as the item itself.
This is due to duck typing. Since "2" == 2 == [2] and possibly more.
To add a little detail to the other answers... when comparing an Array to a Number, Javascript will convert the Array with parseFloat(array). You can try it yourself in the console (eg Firebug or Web Inspector) to see what different Array values get converted to.
parseFloat([2]); // 2
parseFloat([2, 3]); // 2
parseFloat(['', 2]); // NaN
For Arrays, parseFloat performs the operation on the Array's first member, and discards the rest.
Edit: Per Christoph's details, it may be that it is using the longer form internally, but the results are consistently identical to parseFloat, so you can always use parseFloat(array) as shorthand to know for sure how it will be converted.
You are comparing 2 objects in every case.. Dont use ==, if you are thinking of comparison, you are having === in mind and not ==. == can often give insane effects. Look for the good parts in the language :)
Explanation for the EDIT section of the question:
1st Example
[0] == false // true
if ([0]) { /* executes */ } // [0] is both true and false!
First typecast [0] to a primitive value as per Christoph's answer above we have "0" ([0].valueOf().toString())
"0" == false
Now, typecast Boolean(false) to Number and then String("0") to Number
Number("0") == Number(false)
or 0 == 0
so, [0] == false // true
As for if statement, if there is not an explicit comparison in the if condition itself, the condition evaluates for truthy values.
There are only 6 falsy values: false, null, undefined, 0, NaN and empty string "". And anything that is not a falsy value is a truthy value.
Since [0] is not a falsy value, it is a truthy value, the if statement evaluates to true & executes the statement.
2nd Example
var a = [0];
a == a // true
a == !a // also true, WTF?
Again type casting the values to primitive,
a = a
or [0].valueOf().toString() == [0].valueOf().toString()
or "0" == "0" // true; same type, same value
a == !a
or [0].valueOf().toString() == [0].valueOf().toString()
or "0" == !"0"
or "0" == false
or Number("0") == Number(false)
or 0 = 0 // true