javascript functions and arguments object, is there a cost involved
It is common place to see code like that around the web and in frameworks:
var args = Array.prototype.slice.call(arguments);
In doing so, you convert the arguments Object
into a real Array
(as much as JS has real arrays anyway) and it allows for whatever array methods you have in your Array prototypes to be applied to it, etc etc.
I remember reading somewhere that accessing the arguments
Object directly can be significantly slower than an Array clone or than the obvious choice of named arguments. Is there any truth to that and under what circumstances / browsers does it incur a performance penalty to do so? Any articles on the subject you know of?
update interesting find from http://bonsaiden.github.com/JavaScript-Garden/#function.arguments that invalidates what I read previously... Hoping the question gets some more answers from the likes of @Ivo Wetzel who wrote this.
At the bottom of that section it says:
Performance myths and truths
The arguments object is always created with the only two exceptions being the cases where it is declared as a name inside of a function or one of its formal parameters. It does not matter whether it is used or not.
this goes in conflict with http://www.jspatterns.com/arguments-considered-harmful/, which states:
However, it's not a good idea to use arguments for the reasons of :
- performance
- security
The arguments object is not automatically created every time the function is called, the JavaScript engine will only create it on-demand, if it's used. And that creation is not free in terms of performance. Th开发者_开发技巧e difference between using arguments vs. not using it could be anywhere between 1.5 times to 4 times slower, depending on the browser
clearly, can't both be correct, so which one is it?
ECMA die-hard Dmitrty Soshnikov said:
Which exactly “JavaScript engine” is meant? Where did you get this exact info? Although, it can be true in some implementations (yep, it’s the good optimization as all needed info about the context is available on parsing the code, so there’s no need to create arguments object if it was not found on parsing), but as you know ECMA-262-3 statements, that arguments object is created each time on entering the execution context.
Here's some q&d testing. Using predefined arguments
seems to be the fastest, but it's not always feasible to do this. If the arity of the function is unknown beforehand (so, if a function can or must receive a variable amount of arguments), I think calling Array.prototype.slice
once would be the most efficient way, because in that case the performance loss of using the arguments
object is the most minimal.
The arguments
has two problems: one is that it's not a real array. The second one is that it can only include all of the arguments, including the ones that were explicitly declared. So for example:
function f(x, y) {
// arguments also include x and y
}
This is probably the most common problem, that you want to have the rest of the arguments, without the ones that you already have in x
and y
, so you would like to have something like that:
var rest = arguments.slice(2);
but you can't because it doesn't have the slice
method, so you have to apply the Array.prototype.slice
manually.
I must say that I haven't seen converting all of the arguments to a real array just for the sake of performance, only as a convenience to call Array methods. I'd have to do some profiling to know what is actually faster, and it may also depend faster for what, but my guess would be that there's not much of a difference if you don't want to call the Array methods in which case you have no choice but to convert it to a real array or apply the methods manually using call or apply.
The good news is that in new versions of ECMAScript (Harmony?) we'll be able to write just this:
function f(x, y, ...rest) {
// ...
}
and we'll be able to forget all of those ugly workarounds.
No one's done testing on this in a while, and all the links are dead. Here's some fresh results:
var res = []
for(var i = 0, l = arguments.length; i < l; i++){
res.push(arguments[i])
}
}
function loop_variable(){
var res = []
var args = arguments
for(var i = 0, l = args.length; i < l; i++){
res.push(args[i])
}
return res
}
function slice(){
return Array.prototype.slice.call(arguments);
}
function spread(){
return [...arguments];
}
function do_return(){
return arguments;
}
function literal_spread(){
return [arguments[0],arguments[1],arguments[2],arguments[3],arguments[4],arguments[5],arguments[6],arguments[7],arguments[8],arguments[9]];
}
function spread_args(...args){
return args;
}
I tested these here: https://jsben.ch/bB11y, as do_return(0,1,2,3,4,5,6,7,8,9)
and so on. Here are my results on my Ryzen 2700X, on Linux 5.13:
Firefox 90.0 | Chromium 92.0 | |
---|---|---|
do_return |
89% | 100% |
loop_variable |
74% | 77% |
spread |
63% | 29% |
loop |
73% | 94% |
literal_spread |
86% | 100% |
slice |
68% | 81% |
spread_args |
100% | 98% |
I would argue against the accepted answer.
I edited the tests, see here: http://jsperf.com/arguments-performance/6
I added the test for slice
method and a test for memory copy to preallocated array. The latter is multiple times more efficient in my computer.
As You can see, the first two memory copy methods in that performance test page are slow not due to loops, but due to the push
call instead.
In conclusion, the slice
seems almost the worst method for working with arguments
(not counting the push
methods since they are even not much shorter in code than the much more efficient preallocation method).
Also it might be of interest, that apply
function behaves quite well and does not have much performance hit by itself.
First existing test:
function f1(){
for(var i = 0, l = arguments.length; i < l; i++){
res.push(arguments[i])
}
}
Added tests:
function f3(){
var len = arguments.length;
res = new Array(len);
for (var i = 0; i < len; i++)
res[i] = arguments[i];
}
function f4(){
res = Array.prototype.slice.call(arguments);
}
function f5_helper(){
res = arguments;
}
function f5(){
f5_helper.apply(null, arguments);
}
function f6_helper(a, b, c, d){
res = [a, b, c, d];
}
function f6(){
f6_helper.apply(null, arguments);
}
精彩评论