43

A friend of mine discovered some interesting behaviour in some Javascript code, which I decided to investigate further.

The comparison

(function (x) {return x*x;}) > [1,2,3]

returns true in most major browsers (Firefox, Chrome, Opera and Safari) and false in IE9. To me, there is no logical result of this comparison other than undefined as there is no way to say that a function is greater than an array.

Reading up on this in the ECMA-script standard, it says that the actual arguments of > when it is used on objects are the result of calling the ToNumber internal operation on the arguments. Some experiments and further reading tells me that this is not the same as applying a type conversion such as (Number) arg. Reading the specification, I have a hard time figuring out what's going on here.

Can anyone fill me in on what's really happening here?

evilcandybag
  • 1,942
  • 17
  • 17

4 Answers4

60

In IE<9, .toStringing (function (x) {return x*x;}) gives

"(function (x) {return x*x;})" 

While in chrome it gives:

"function (x) {return x*x;}"

If you compare:

"function (x) {return x*x;}" > "1,2,3" // true
"(function (x) {return x*x;})"  > "1,2,3"  // false

Which is effectively the same as comparing:

"f" > "1"
"(" > "1"

Which is the same as comparing:

102 > 49
40 > 49

So that's how we get from a function and array comparison to a simple number comparison :)

Esailija
  • 138,174
  • 23
  • 272
  • 326
  • Interesting analysis of obvious incompatibility, allowed by the spec. – Abel Feb 21 '12 at 16:52
  • Not in my IE9: http://i.imgur.com/1zhLa.png. I only see the parens in IE8/IE7 mode. Is your browser in compatibility mode? – gilly3 Feb 21 '12 at 21:41
  • @gilly3 true I had IE7 mode, the OP must have had the same mistake as it returns true for the comparison (same as the modern browsers) – Esailija Feb 21 '12 at 22:17
54

The operands to > are not necessarily converted to numbers. The abstract relational comparison algorithm calls ToPrimitive with the hint Number, but ToPrimitive may still return a string (and in the case of both functions and arrays, it does).

So you end up comparing two strings. The result of calling toString on function objects is not defined by the spec, although most major engines return the source code of the function (or some form of it, and the formatting varies). The result of calling toString on arrays is the same as join.

So the odds are that you'll end up basically doing this:

"function (x) {return x*x;}" > "1,2,3"

Since the exact form of the string for the function may vary from browser-to-browser (and note Esailija's investigations — looks like IE9 keeps the outer (), Chrome doesn't), it's not too surprising that the result may vary.

Community
  • 1
  • 1
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • 1
    Could also be 'function(){...' and '1,2,3' (I think that's what Chrome will make of those). – Michael Krelin - hacker Feb 21 '12 at 16:40
  • @Pointy: Yeah, and `toString` on functions returns the source (usually, not guaranteed). I was thinking of `Object.prototype.toString.call(func)` -- doh! – T.J. Crowder Feb 21 '12 at 16:40
  • Ah, I see. Thanks! I think I hurried too much while reading through the spec. That aside, this is a horrible way to deal with comparisons. – evilcandybag Feb 21 '12 at 17:10
  • @evilcandybag: Have a read through the algorithm and the various operations it triggers, there *is* a method to the (seeming) madness. I have to admit I'd kind of like to have the option of using a "strict relational comparison" operator (like we have strict equality/inequality operators, `===` and `!==`), though. :-) – T.J. Crowder Feb 21 '12 at 17:22
  • @T.J.Crowder: Yeah, read through it before I checked your answer as correct. Sure, there is a method to it all. It just seems to me that the language designers want to give a faux sense of flexibility by pretending to do something useful. Then again, I'm no fan of weak typing overall. =P – evilcandybag Feb 21 '12 at 19:25
  • @evilcandybag: Ah, that explains it. :-) And yeah, it results in some pretty bizarre behavior. – T.J. Crowder Feb 21 '12 at 19:36
5

Let's dive into the ECMA Specification. I've included the section numbers so you can reference.

11.8.2 The Greater-than Operator ( > )

The production RelationalExpression : RelationalExpression > ShiftExpression is evaluated as follows:

  1. Let lref be the result of evaluating RelationalExpression.
  2. Let lval be GetValue(lref).
  3. Let rref be the result of evaluating ShiftExpression.
  4. Let rval be GetValue(rref) .
  5. Let r be the result of performing abstract relational comparison rval < lval with LeftFirst equal to false. (see 11.8.5).

The important part of that is the Abstract Relational Comparison. Which is defined:

11.8.5 The Abstract Relational Comparison Algorithm

The toPrimitive function will first be called on the Objects. Although this is biased to return Numbers if it can, Strings can also be derived. Once this has occurred, the following will be examined:

a. If py is a prefix of px, return false. (A String value p is a prefix of String value q if q can be the result of concatenating p and some other String r. Note that any String is a prefix of itself, because r may be the empty String.)

b. If px is a prefix of py, return true.

c. Let k be the smallest nonnegative integer such that the character at position k within px is different from the character at position k within py. (There must be such a k, for neither String is a prefix of the other.)

d. Let m be the integer that is the code unit value for the character at position k within px. e. Let n be the integer that is the code unit value for the character at position k within py. f. If m < n, return true. Otherwise, return false.

This means that the first character in the String that is different than the other will be examined. As it has been pointed out by Esailija, IE's toString() function returns a slightly different String to that of the other browsers, resulting in a different comparison taking place.

This difference between the browsers appears to be valid as is stated here:

15.2.4.4 Object.prototype.valueOf ( )

When the valueOf method is called, the following steps are taken:

  1. Let O be the result of calling ToObject passing the this value as the argument.
  2. If O is the result of calling the Object constructor with a host object (15.2.2.1), then a. Return either O or another value such as the host object originally passed to the constructor. The specific result that is returned is implementation-defined.
  3. Return O.
Jivings
  • 22,834
  • 6
  • 60
  • 101
2

Both IE and other browsers will use the same string comparison for both objects. The reason for the difference is IE will convert the function into the literal string as entered:

(function (x) {return x*x;})

Other browsers (testing on Firefox) will output its own compiled interpretation of the function:

function (x) {
    return x * x;
}

Since the first character of IE's function representation is (, which is higher than 1, it will return false. Since f is lower then 1, other browsers will return true.

Mike Christensen
  • 88,082
  • 50
  • 208
  • 326