JavaScript equality transitivity is weird
Asked Answered
E

3

43

I've been reading Douglas Crockford's JavaScript: The Good Parts, and I came across this weird example that doesn't make sense to me:

'' == '0'           // false
0 == ''             // true
0 == '0'            // true

false == undefined  // false
false == null       // false
null == undefined   // true

The author also goes on to mention "to never use == and !=. Instead, always use === and !==". However, he doesn't explain why the above behavior is exhibited? So my question is, why are the above results as they are? Isn't transitivity considered in JavaScript?

Exacting answered 27/3, 2011 at 4:9 Comment(7)
The best way to answer this question is to read the specification: ecma-international.org/publications/standards/Ecma-262.htm If you really want to know the nitty-gritty of why the statements above are the way they are, there's really no better way. Be prepared that it can be a bit of a slog in places, but if you're asking a question like this, it's well worth the effort.Inscription
@T.J. Crowder Very good suggestion (+1), I edited that link into my answer, hope you don't mind.Smew
It should be pointed out that the second set of equalities involving false, undefined, and null does not violate transitivity - it's the same as A != B, A != C, B == C.Yearling
@Nacht... yes, you're right! but the first one is weird :PExacting
See level 3: alf.nu/ReturnTrueDeterioration
Another one is false=='', false==' ', but ''!=' '. In words, both the empty string and the string with a single space in it are both false(y) but not equal.Schizomycete
I can't agree more on this - JavaScript equality transitivity is weird.Lueck
S
35
'' == '0' // false

The left hand side is an empty string, and the right hand side is a string with one character. They are false because it is making a comparison between two un identical strings (thanks Niall).

0 == '' // true

Hence, why this one is true, because 0 is falsy and the empty string is falsy.

0 == '0' // true

This one is a bit trickier. The spec states that if the operands are a string and a number, then coerce the string to number. '0' becomes 0. Thanks smfoote.

false == undefined // false

The value undefined is special in JavaScript and is not equal to anything else except null. However, it is falsy.

false == null // false

Again, null is special. It is only equal to undefined. It is also falsy.

null == undefined // true

null and undefined are similar, but not the same. null means nothing, whilst undefined is the value for a variable not set or not existing. It would kind of make sense that their values would be considered equal.

If you want to be really confused, check this...

'\n\r\t' == 0

A string consisting only of whitespace is considered equal to 0.

Douglas Crockford makes a lot of recommendations, but you don't have to take them as gospel. :)

T.J. Crowder makes an excellent suggestion of studying the ECMAScript Language Specification to know the whole story behind these equality tests.

Further Reading?

The spec.

yolpo (on falsy values)

Smew answered 27/3, 2011 at 4:14 Comment(9)
"Douglas Crockford makes a lot of recommendations, but you don't have to take them as gospel." Well put. Crockford is a smart person who's thought a lot on purpose about the language, and so it's well worth reading the points he raises and the arguments he makes. Then come to your own conclusions.Inscription
@alex... in your third paragraph starting with "This one is a bit trickier", don't you mean == does check types?Exacting
@Exacting == doesn't check types, just values.Smew
@alex... I disagree. developer.mozilla.org/en/JavaScript/Reference/Operators/…. I think == does check types and values. === doesn't convert types, just checks strict equality.Exacting
@Exacting Well it does check the types implementation wise (it will convert the types to make the comparison), but I thought it might be easier to mention values, i.e. 1 == '1'. So in that example, '1' will be coerced to Number.Smew
@Smew good answer overall, but your reasoning of why '' == '0' evaluates to false is incorrect. Both operands are the same type (String) so no type conversation takes place (therefore the falsyness of an empty string is not relevant here). The expression evaluates to false for exactly the same reason 'dog' == 'cat' does, i.e. the operands are two different strings.Grandee
@Niall You are totally correct and I'm not sure why I wrote that :P I'll make an edit.Smew
Your explanation for why 0 == '' is true is also incorrect. ECMAScript always coerces strings to numbers when performing non-strict equality comparison of a string and a number.Kuibyshev
@Kuibyshev Good catch, I should have consulted the spec more before I wrote this answer.Smew
K
8

The answer to this question has to do with how JavaScript handles coercion. In the case of ==, strings are coerced to be numbers. Therefore:

'' == '0' is equivalent to '' === '0' (both are strings, so no coercion is necessary).

0 == '' is equivalent to 0 === 0 because the string '' becomes the number 0 (math.abs('') === 0).

0 == '0' is equivalent to 0 === 0 for the same reason.

false == undefined is equivalent to 0 === undefined because JavaScript coerces booleans to be numbers when types don't match

false == null is equivalent to 0 === null for the same reason.

null == undefined is true because the spec says so.

Thanks for asking this question. My understanding of == is much better for having researched it.

Kuibyshev answered 27/2, 2014 at 5:13 Comment(0)
S
4

You can actually write a JavaScript function that behaves exactly like == that should give you some insight into how it behaves.

To show you what I mean here is that function:

// loseEqual() behaves just like `==`
function loseEqual(x, y) {
    // notice the function only uses "strict" operators 
    // like `===` and `!==` to do comparisons

    if(typeof y === typeof x) return y === x;

    if(typeof y === "function" || typeof x === "function") return false;

    // treat null and undefined the same
    var xIsNothing = (y === undefined) || (y === null);
    var yIsNothing = (x === undefined) || (x === null);

    if(xIsNothing || yIsNothing) return (xIsNothing && yIsNothing);

    if(typeof x === "object") x = toPrimitive(x);
    if(typeof y === "object") y = toPrimitive(y);

    if(typeof y === typeof x) return y === x;

    // convert x and y into numbers if they are not already use the "+" trick
    if(typeof x !== "number") x = +x;
    if(typeof y !== "number") y = +y;

    return x === y;
}

function toPrimitive(obj) {
    var value = obj.valueOf();
    if(obj !== value) return value;
    return obj.toString();
}

As you can see == has a lot of complicated logic for type conversion. Because of that it's hard to predict what result you are going to get.

Here are some examples of some results you wouldn't expect:

Unexpected Truths

[1] == true // returns true
'0' == false // returns true
[] == false // returns true
[[]] == false // returns true
[0] == false // returns true

'\r\n\t' == 0 // returns true

Unexpected Conclusions

// IF an empty string '' is equal to the number zero (0)
'' == 0 // return true

// AND the string zero '0' is equal to the number zero (0)
'0' == 0 // return true

// THEN an empty string must be equal to the string zero '0'
'' == '0' // returns **FALSE**

Objects with Special Functions

// Below are examples of objects that
// implement `valueOf()` and `toString()`

var objTest = {
    toString: function() {
        return "test";
    }
};

var obj100 = {
    valueOf: function() {
        return 100;
    }
};

var objTest100 = {
    toString: function() {
        return "test";
    },
    valueOf: function() {
        return 100;
    }
};

objTest == "test" // returns true
obj100 == 100 // returns true
objTest100 == 100 // returns true

objTest100 == "test" // returns **FALSE**
Stability answered 9/8, 2016 at 17:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.