5

Here is a line of code from underscore. What is that plus prefix for in this line?

if (obj.length === +obj.length) { // plus prefix?
  • 3
    Converts to a number. – the system Feb 26 '13 at 00:13
  • 1
    possible duplicate of [What is unary + used for in Javascript?](http://stackoverflow.com/questions/9081880/what-is-unary-used-for-in-javascript) – the system Feb 26 '13 at 00:14
  • 1
    I don't know where you found that code, but the `breaker` thing will never work. It looks like it's intended that the iterator function can return an empty object to indicate that it's time to return, but it will inevitably return a *different* empty object than that one because "breaker" is local to that function. – Pointy Feb 26 '13 at 00:15
  • @pure_code no, the `+` prefix **converts** to a number, so that test makes sure that "obj" is an array-like object and that its "length" property is a number. – Pointy Feb 26 '13 at 00:16

5 Answers5

7

Adding a + symbol effectively converts a variable into a number, such that:

+"1" === 1;

However, please note that

+"1" === "1"; // FALSE
+"1" ==  "1"; // TRUE

This is because == will convert its operands to the same type, whereas === will not.

That means that the test:

obj.length === +obj.length

Is essentially trying to test whether obj.length is numeric.

In Underscore, this code is trying to figure out if a variable of unknown type has a property called length and whether it is numeric. The assumption is that, if these are both true, you can iterate over the variable is if it were an array.

EDIT

Please note, the OP's code has a number of bugs in it, not least of which is this approach to detecting if something is an Array (or Arraylike). The following object would cause problems:

var footballField = {
    covering: "astroturf",
    condition: "muddy",
    length: 100
};

I'm not advocating the above approach... just explaining someone else's.

Dancrumb
  • 26,597
  • 10
  • 74
  • 130
  • 1
    Because an `Object` could have a `length` property that could be anything. – Dancrumb Feb 26 '13 at 00:19
  • @pure_code: Use your imagination. `{length:"I'm clearly not a number"}` – the system Feb 26 '13 at 00:19
  • Checking for a numeric `length` property is *not* good practice... the code snippet provided by the OP has a number of bugs in it, this being one of them. – Dancrumb Feb 26 '13 at 00:19
  • @Dancrumb: It's neither a good nor bad practice. There needs to be *some* criteria used to determine if an object is "array-like" and therefore can be iterated. That's the criteria they've chosen. It's the user's fault if they didn't read the documentation, and therefore pass an object that they shouldn't. – the system Feb 26 '13 at 00:24
  • Well, if the documentation for a library said: "Works for any object, so long as that object doesn't have a property called 'length', regardless of whether it makes sense", I'd consider it a pretty poor quality library. – Dancrumb Feb 26 '13 at 00:27
  • It's worth noting that Underscore (which the OP's code is 'modified from') does *not* implement `isArray` in this way. – Dancrumb Feb 26 '13 at 00:29
3

The plus prefix converts the variable into a number. Basically, the obj.length === +obj.length is a sanity check that obj.length really is a number. If the obj.length was not a number, and for example a string "foo", then "foo" === +"foo" would equate to false since +"foo" comes out as NaN.

Daiz
  • 328
  • 2
  • 6
1

The + prefix converts the value into a number.

christopher
  • 26,815
  • 5
  • 55
  • 89
1

This forces the value of obj.length to be a Number. Essentially this is done to make sure that the default length value for an array-like object has not been overridden so that it can be iterated properly.

breaker will do nothing in this context because even another empty object {} will evaluate to false when compared to breaker .. even without an equivalence comparison.

However, breaker is not used in that context because it is defined outside of the .each function, which appears different than what you are showing here. Instead, it is used to force a "break" from other looping methods:

_.every = _.all = function(obj, iterator, context) {
  /* snip */
  if (!(result = result && iterator.call(context, value, index, list)))
      return breaker;

You can see that if the result is not truthy in "every," we want to break immediately. _.every calls _.each, and it will return breaker which will be true when compared to itself, allowing for an immediate break.

Explosion Pills
  • 188,624
  • 52
  • 326
  • 405
  • @pure_code if you define `breaker` inside of `.each`, then it will always be a new object on every call to `.each`. We don't want that -- what we want is for `breaker` to be the same throughout all of `_` – Explosion Pills Feb 26 '13 at 00:26
  • @pure_code don't focus on the fact that it's `{}`. This is just something that `_` uses as an "internal flag," per se, to indicate that a `.each` loop should be broken. – Explosion Pills Feb 26 '13 at 00:32
  • 1
    @pure_code technically yes, but the problem is that you could in theory compare to a string `'break'` in the loop, which would be a problem. There can only ever be one instance of `breaker = {}` – Explosion Pills Feb 26 '13 at 00:34
  • @pure_code no it's not the same; if you knew what the hash digest was, you could *still* have it as an element of the array, which would cause unexpected loop breaking. There can only be *one* `breaker` – Explosion Pills Feb 26 '13 at 13:44
  • @pure_code not that I know of without some kind of debugging tools – Explosion Pills Feb 26 '13 at 14:00
-1

If the object does not have a length property (being a regular Object, instead of an Array), then calling obj.length will return undefined. A test for undefined would be clearer, but the implementor chose to first convert it to a number (so it will become NaN) and then compare it with the original value (using a strict comparison, which will ensure it will yield false).

Update: as others pointed out, this code seems to be concerned not only with Arrays, but also "array-like" objects (i.e. objects that have a numeric length property and numeric indices). In such case, a test for instanceof Array would not be enough (and just testing for undefined might not be the best option, since there could be a length but of another type).

mgibsonbr
  • 21,755
  • 7
  • 70
  • 112
  • That's not what this comparison is for – Dancrumb Feb 26 '13 at 00:15
  • @Dancrumb then what is it for? I see two code blocks, one to deal with arrays (using `for`) and another for objects (using "foreach"). Is my interpretation wrong? – mgibsonbr Feb 26 '13 at 00:17
  • 1
    A numeric conversion of `undefined` becomes `NaN`, not `0`. – the system Feb 26 '13 at 00:17
  • For one thing, an Object could legitimately have a `length` property. Thus, checking for `undefined` would not be sufficient – Dancrumb Feb 26 '13 at 00:18
  • @Dancrumb by making sure that the un-cast "length" property is equal to the "length" property converted to a number, the code is effectively the same as a test for a "length" property and a conjunctive test for that property to be numeric. Thus, it's a "duck typing" test to see if the object is an array. – Pointy Feb 26 '13 at 00:19
  • @Dancrumb I agree, that's why I would test for `instanceof Array` myself (instead of using this confusing trick). But from the context, it's clear that the intention of the implementor was that (unless there's some more plausible explanation). – mgibsonbr Feb 26 '13 at 00:20
  • @Pointy except the presence of a property called `length` is a woefully insufficient test to see if an object is an array. The defining characteristics of an array include numeric indexes, not just a numeric `length`. – Dancrumb Feb 26 '13 at 00:23
  • @Dancrumb well not if all the code wants to do is iterate through numeric-indexed properties. Lots of libraries do essentially that very thing. (Indeed, the code here is just a slightly broken version of code in the Underscore.js library.) Also, the numeric-indexed properties of an array object are *not really numeric-indexed* - **all** property names of **all** objects are treated as strings by the `[]` operator! Really, it's just the "magic" `length` property that makes arrays special. – Pointy Feb 26 '13 at 00:29
  • @Pointy That's one of the reasons that I use the Lodash implementation. Simply checking for `length` is shoddy work that opens users up to subtle bugs; especially since there is actually a *definitive* way to check. – Dancrumb Feb 26 '13 at 00:34
  • @Dancrumb I think it boils down to a question of whether one believes in a "duck typing" world or not :-) It could be considered a feature that non-array objects can actually pretend to be arrays with facilities like this. I see both sides of the argument, and for me it's academic since I can't recall needing to do it in real life lately! – Pointy Feb 26 '13 at 00:45
  • @Pointy: someone pointed to `HTMLCollection` and `NodeList` (in chat) as examples of non array objects that have numeric indexes. I guess the bottom line is that no implementation of `forEach` can be a perfect solution for all iterable entities in JS. I think I see both sides better now; I still prefer Lodash's rigour on this point, but I can see where it limits itself now. – Dancrumb Feb 26 '13 at 00:50