§15.4.4.4 of the ECMAScript 5.1 specification states:
The concat function is intentionally generic; it does not require that its this value be an Array object. Therefore it can be transferred to other kinds of objects for use as a method.The code in this post uses [] as a shortcut for Array.prototype. That is a common technique, but it is slightly dirty: You access the methods of Array.prototype via an instance. However, this access is very fast in modern JavaScript engines, I suspect that some of them don’t even create an array instance, any more. All of the examples have been tried on Firefox and V8.
Let’s check the assertion that concat is generic: Its result must be the same regardless of whether this is a real array or just array-like (with a property length and indexed access to elements). We first try concat with an array as this:
> ["hello"].concat(["world"])
["hello", "world"]
> [].concat.call(["hello"], ["world"]) // same as above
["hello", "world"]
Next, we do the above concatenation with an array-like object. The result should be the same.
> [].concat.call({ 0: "hello", length: 1 }, ["world"])
[ { '0': 'hello', length: 1 }, 'world' ]
The special variable arguments is also array-like. The result is again not what we would expect from a generic method:
> function f() { return [].concat.call(arguments, ["world"]) }
> f("hello")
[ { '0': 'hello' }, 'world' ]
To see a truly generic method in action, you just need to look at push:
> var arrayLike = { 0: "hello", length: 1 };
> [].push.call(arrayLike, "world")
2
> arrayLike
{ '0': 'hello', '1': 'world', length: 2 }
Reference:
- “Uncurrying `this` in JavaScript” [explains what generic methods are]
3 comments:
Concat is generic but the usages I think are confusing. Concat has no dependency on `this` being an array, or in fact even an arraylike at all. It's essentially a function twisted into being a prototype method. Unlike most functions, it acts as if its first argument comes from `this` and then it continues with the normal arguments. [].concat.call('any value here', 'etc', 'continuing on') demonstrates it pretty clearly. Using call shifts its signature back to that of a normal function.
I would say that concat's operation is so unique that using the term "generic" is misleading, but for different reasons. It is MORE generic than the rest of the array methods. So much so that it has no dependency on being passed any arraylikes at all. It's basically just an accumulator that collapses Array `this`, arguments, and Array arguments into a single array, along with any other values (while decidedly not respecting arraylikes as you indicated).
Also, .concat() is consistently removing the first level of Array-ness from args, including its own `this`. When `this` is a genuine Array, its elements are concatted into the resulting array. When it is something else, there is no Array-ness to remove and `this` just goes in as the first element in the resulting array. Making .concat() treat non-Array `this` differently would be a special case.
For concat, I find apply() more useful.
function f() { return [].concat.apply([].concat.apply([], arguments), ["world"]) };
f("hello") ; //["hello", "world"]
Post a Comment