3

I have been reading underscore.js source code and noticed this compare in _.each():

(obj.length === +obj.length)

I know that + before a variable casts it to a number/integer. So in this cause

[1,2,3].length === +[1,2,3].length

is true. If I pass in an object:

var obj = {a: 1, b: 2, c: 3};

+obj.length produces NaN

In the last case, I have

[1,2,3, {a: [4,5,6]}].length

which is 4. Casting it to a number.. Is still 4.

Deciding from the else case, I can see that this comparation is probably done to distinguish arrays from objects as in else case it uses:

  for (var key in obj) { ...

I fail to see any reason to use such a comparation. Can anyone explain ?

lukas.pukenis
  • 13,057
  • 12
  • 47
  • 81

4 Answers4

6

Basically, it's a way of testing the type of the length property is Number without using typeof and that the number is not NaN. So in effect:

if (typeof obj.length === "number" && !isNaN(obj.length))

The comparison n === +n will only be true for a number, because === tests both type and value, and +n will create a Number (possibly, as you found, the number NaN). And because NaN === NaN is false, it weeds those out too.

So it's a way of detecting, according to one definition ("has a numeric length that isn't NaN"), whether the object to be iterated is array-like. There are several array-like things in the JavaScript and browser world, such as the arguments pseudo-array and DOM's NodeLists.


Silly performance comparisons:

Upshot: Without the NaN aspect, typeof is faster (markedly so when the type is wrong). With the NaN aspect, + is faster when the result will be true and when it will be false because it's NaN, and slower (markedly so) when the result will be false because it's the wrong type. (Opera is, of course, different as always.)

Not that it's likely to matter in the real world (see "silly" above).

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • 1
    Thanks for the examples like arguments and NodeList! – lukas.pukenis Jul 01 '13 at 07:56
  • 2
    more precisely, it tests if `length` is a number, but not NaN. They also could've used `isFinite` for this. – georg Jul 01 '13 at 08:01
  • 1
    @thg435: **Very** good point. But `isFinite` is `false` for `Infinity`, too. :-) – T.J. Crowder Jul 01 '13 at 08:06
  • @thg435: Of course, that (`isFinite` being `false` for `Infinity`) could be a good thing: With their current check, assuming they go with `for (i = 0; i < obj.length; ++i)`, they'll loop forever. – T.J. Crowder Jul 01 '13 at 08:24
1

I suppose it's a fast way to distinguish an Object instance from an Array instance or 'Arrayish' Objects (like arguments).

It would break if an Object instance contains a length property. Like {a:1, length: 1}, or as a result of [].push.call({},'a value') (result of that would be: {0:'a value',length: 1}). As demonstrated in this jsFiddle

KooiInc
  • 119,216
  • 31
  • 141
  • 177
1

From the context, I think it's trying to figure out whether obj is an array, since iterating over one would require a sequential for loop rather than for-in: https://stackoverflow.com/a/3010848/367273

Community
  • 1
  • 1
NPE
  • 486,780
  • 108
  • 951
  • 1,012
0

The + operator returns the numeric representation of the object.As Your code +obj.length means its returning a number reference ....this. You are getting +obj.length value as Number when its an array but it does not return length in case var obj = {a: 1, b: 2, c: 3}; as its not an array...and when you are using [1,2,3, {a: [4,5,6]}].length its returning 4 as object is in array...

Rahul
  • 591
  • 1
  • 7
  • 21