jQuery merge() and array.length data type
javascript, jquery
Solution
The `merge` function you're looking at is a public jQuery method: `jQuery.merge()` or `$.merge()`. So you might think that reading the `jQuery.merge()` documentation would shed some light on this question.
Unfortunately, it doesn't, at least as of this writing.
As explained in the other answers and comments, this test for a numeric `length` property will always succeed when `second` is an `Array`, because an `Array` will always have a numeric `length`. The only possible reason to have this test in the code is to handle the case where `second` is not an `Array`.
But the documentation doesn't mention this case at all. It only discusses cases where both `first` and `second` are both native JavaScript `Array` objects, not "array-like" objects.
If this documented use was all this function did, it would be pretty useless, since JavaScript provides a perfectly good native `array.concat(array)` method that can be used instead of `jQuery.merge()`.
So it would seem that the real purpose of this function must be in its undocumented use. Let's try it out in the Chrome console on the jQuery.merge() doc page. Open the developer tools, select the Console tab, and enter an example similar to the ones in the doc:
jQuery.merge( [ 'a', 'b' ], [ 'c', 'd' ] )
and then try the same thing with `.concat()`:
[ 'a', 'b' ].concat([ 'c', 'd' ])
They will both log exactly the same thing:
["a", "b", "c", "d"]
Since this is jQuery, the most likely case of an "array-like" object would be a jQuery object. For example, that doc page currently has three `<h3>` elements on it, so we can get them with:
$('h3')
That will log:
[►<h3>…</h3>, ►<h3>…</h3>, ►<h3>…</h3>]
That isn't an array, but it looks a lot like one, and it does have a numeric `length` property which we can check with:
typeof $('h3').length
So let's try it with `concat`:
['x'].concat( $('h3') )
Oops. That should give us an array of four elements, 'x' followed by the three DOM elements. Instead, i gives us an array of two element, with the jQuery object as the second element:
[►e.fn.init[3] ]
It looks like jQuery's "array-like" object isn't array-like enough for `.concat()`.
Oddly enough, some other array methods do with work jQuery's array-like object, such as `.slice()`:
Array.prototype.slice.call( $('h3'), 1 )
That logs the correct result, an array of two elements:
[►<h3>…</h3>, ►<h3>…</h3>]
So let's try `$.merge()`:
$.merge( ['x'], $('h3') )
Sure enough, that logs what we expected:
["x", ►<h3>…</h3>, ►<h3>…</h3>, ►<h3>…</h3>]
There's also another way to do this. jQuery provides a `.toArray()` method, so we can do the same thing by combining that with `.concat()`:
['x'].concat( $('h3').toArray() )
That logs the same thing as the `$.merge()` call.
What if `first` is a jQuery object?
$.merge( $('h1'), $('h3') )
That works too:
[<h1 class="entry-title">jQuery.merge()</h1>,
►<h3>…</h3>, ►<h3>…</h3>, ►<h3>…</h3>]
So it would appear that this is the purpose of this method: to do `array.concat()`-like operations on jQuery objects.
But wait! This still doesn't answer the question of why that extra code is in there for the non-numeric `.length`. After all, a jQuery object does have a numeric `.length`, so we still haven't exercised the code for the non-numeric case.
So this part of the code must be there for some purpose other than handling jQuery objects. What could that be?
We can find out a bit more by going to the jQuery source repository and locating the file in the `src` directory that has the `merge:` code (it happens to be `core.js`). Then use the Blame button and search within the page for the `merge:` code to see what the commit comment for the code is. Again, the code we're wondering about now is the `else` clause, where `typeof l` is not a number.
The commit was by John Resig on 2009-12-09, with a comment "Rewrote merge() (faster and less obtuse now). Fixed #5610." There's a bit of controversy in the discussion on that page:
Well, your version is faster only in the special (very rare) case of overwritted length property. In all other cases, my one was little faster. Also you are not considering the case obj.length = new Number(N) - but yeah it is not so relevant I suppose.
This helps explain it a little, but what is issue #5610? Maybe that will tell us. Don't see an issue tracker on the GitHub repo? jQuery has its own issue tracker and if we search there for `#5610` (or do a Google search for `jquery issue 5610`, we finally find issue #5610:
MAKEARRAY CAN CAUSE BROWSER CRASH ON NON ARRAY OBJECTS WITH LENGTH PROPERTY
Description
I haven't tested this with 1.4
For an object with a length property that casts to a non-zero positive integer, makeArray will create an array with that length filled with undefined.
>>> jQuery.makeArray({'length': '5'})
[undefined, undefined, undefined, undefined, undefined]
If the length property is zero, negative or a decimal then Firefox and Safari both hang. (FF shows the unresponsive script error, Safari hangs until it crashes)
>>> jQuery.makeArray({'length': '0'})
>>> jQuery.makeArray({'length': '5.2'})
>>> jQuery.makeArray({'length': '-3'})
Problem
I have been looking through some of the jQuery source and I ran across the `merge` function. Here is the source code for it: ``` function merge(first, second) { var l = second.length, i = first.length, j = 0; if (typeof l === "number") { for (; j < l; j++) { first[i++] = second[j]; } } else { while (second[j] !== undefined) { first[i++] = second[j++]; } } first.length = i; return first; } ``` While I understand the code, something doesn't make sense to me. Particularly the `if (typeof l === "number")` part. I have tried passing an array to this function when I have manually changed the `.length` property to something like `"3"` and checked it's type and I still get a type of `number`. My question is when would the `length` property ever not be a type of `number` in JavaScript arrays?