Since it seems like the first thing people do is convert arguments into a real array, I'm interested in why the Javascript language authors and implementers decided, and continue to think, that arguments should not be a real Array. I don't mean this as flamebait, I'm sincerely interested in the thinking behind it. Since the function is naturally being called when you're in its body, I don't think it's because the objects arguments are referencing can change, like with some of the DOM results...
My conjecture:
The concept of the arguments object has been on the language since the very beginning, it's even described in the ECMAScript First Edition Standard(PDF).
In that version of ECMAScript, the Array.prototype was really basic, array objects contained only 4 methods!: toString, join, reverse and sort.
I think that's one of the major reasons about they make arguments to inherit from Object.prototype, at that time those Array methods didn't look too useful.
But the Array.prototype object was extended in the next versions of the standard, now on ES5, Array objects have methods such as map, reduce, every, some, etc, that are really powerful.
The last year, there was a proposal in ES5 to make arguments inherit from Array.prototype, in the draft stages of the standard, but was dropped off time later.
In those drafts, arguments inherited from Array.prototype, but for backwards compatibility with ES3, the arguments object had defined two own properties, toString and toLocaleString, both pointing to the same methods on Object.prototype, but finally, the committee decided to keep inheriting from Object.prototype.
The arguments object has the very unusual feature that its array-like elements are synonyms for the local variables that hold the function arguments. For example:
function f(x) {
console.log(arguments[0]); // Displays the initial value of the argument x
x = 5; // Changes the value of the local variable x
console.log(arguments[0]); // Now displays 5
}
I always had the impression that this "magical behaviour" is the reason why arguments is not an array.
It's important to note that without one of the designers present, we can only really conjecture why. But we can come up with some decent reasons... here's mine:
From the perspective of a function, one reason could be because you can't - obviously - actually change the arguments that were passed into you. You could change an array that represents the arguments passed into you, but the arguments as they were passed is set in stone before you ever receive execution scope.
You can splice, dice and pop arrays, and if you did that to the arguments object then you just ruined what is conceptually an immutable structure (sad face!). The design of the real arguments object is closer to a kind of immutability JavaScript can offer.
It is similar to querystring parameters. You get a collection handed to you by the client sending the request. It's part of the request information, which is already set and done.
arguments doesn't just return the arguments. It returns callee object, and the array of arguments. If it were just an array, the first element might be the callee object and be more confusing.
Related
I may get hammered for asking something overly vague, so please interpret this as a meta-programming question, as it is meant to be, and not as some sort of indirect flame war.
In trying to understand Javascript's Array a bit deeper, I ran across this W3 reference for Array.valueOf.
It simply says:
The valueOf() method returns the array.
This method is the default method of the array object. Array.valueOf() will return the same as Array
Note: This method will not change the original array.
So, I ask: what's the point? Is there a reason to ever use the Array.valueOf() method? Is it useful in some more complicated constructs, such as when using call or apply? Does it help in piping together functions? Is this simply because it creates a standard method, compared to other objects where the associated valueOf is more useful, and therefore help to generalize Array to other objects?
As far as I can tell, it is exactly identical, so I don't see its value.
This method is inherited from Object.prototype; every object that descends from Object.prototype has it. Primitive wrappers override it to return the corresponding primitive, for example, converting new Number(5) to 5. The default implementation returns the object unchanged.
JavaScript calls the valueOf method to convert an object to a primitive value. You rarely need to invoke the valueOf method yourself; JavaScript automatically invokes it when encountering an object where a primitive value is expected.
I was just goin through the Electron API Demo code samples when suddenly a wild expression - which is completely foreign to me - appeared:
const links = document.querySelectorAll('a[href]');
Array.prototype.forEach.call(links, function (link) {
// WWIII here
})
I definitely understand what this piece of code is doing but I am used to a syntax like this:
links.forEach(function (links) {});
So what exactly is the difference between those two? I have already read various StackOverflow threads about this topic but they are either ambiguous or don't answer the question at all. Some said it had something todo with array-like collections not being iteratable by .forEach() as opposed to Array.prototype.forEach.call(). Is that the only advantage of the overly tedious and long version?
Thanks in advance!
"class methods" in JavaScript are actually functions defined on a prototype. That means that even if an object does not inherit from the Array prototype, you can call Array methods on it, as long as it follows the array structure (i.e.: It is an object with a length property and properties indexed by integers). However, the object holds no reference to Array.prototype, so you need to explicitly select Array.prototype as the object the method lives in.
The document.querySelectorAll function returns a NodeList, which is neither an Array nor inherits from the Array prototype. However, as a NodeList has a similar internal structure to an Array, you can still use the forEach function. But since NodeList does not inherit from the Array prototype, trying to use .forEach on a NodeList would raise an error (this is not exactly true - see the note at the end of my answer). For this reason, you need to explicitly state that you are calling a method from Array.prototype on the NodeList, and that is done by using the .call method from Function.prototype.
In summary:
Array.prototype.forEach.call(links, function(link) { /* something */ })
means:
Take the forEach function from Array.prototype and call it on links, which is a non-Array object, with some function as its argument.
Note that on recent versions of browsers, the NodeList prototype does provide a forEach method which works the same as the Array one, so the example from the Electron API is probably using the Array version for compatibility with older versions. If you have a web app and only care about supporting modern versions of Chrome and Firefox, you can just call forEach on your NodeList. In fact, since Electron updates about 2 weeks after whenever Chrome updates, it should be safe to use NodeList.prototype.forEach in Electron. :)
This is interesting question. Half a year ago I would say that link.forEach is not about shorter syntax, but it is actually not supposed to work. Then I would explain what it means that many of array methods deliberately generic, which means that their internal implementation only considers numeric indexes and length property of the this object, but doesn't care about it being Array instance. Basically what #Pedro Castilho said in his answer.
However, now I will say that these days evergreen browsers (except IE11, Edge, as of April 2017) already implemented NodeList.prototype.forEach convenience method so you no longer need to use .call hack or Array.from in order to just iterate NodeList with forEach.
So my summary: if you don't need to support IE, then use NodeList.prototype.forEach rather than Array.prototype.forEach. It might be the same internally, but cleaner conceptually. If you do need to support IE and you don't want to include one more pollyfill then use Array.prototype.call or better Array.from.
This may be related to how the document.querySelectorAll returns a static NodeList rather than an Array.
Using the Array's prototype, you may still call the forEach, it is similar to operating on arguments.
I think it is an old Javascript behavior (Crockford said it is a design error) that inside a function, arguments is like an array, except it is not a real array, so array methods cannot be invoked on it:
function foo() { console.log(arguments.slice(1)) } // won't work
foo(1,2,3);
And I just tried it on the latest Firefox and Chrome, and it won't work on both.
So we may have to use
function foo() { console.log(Array.prototype.slice.call(arguments, 1)) }
foo(1,2,3);
But why not make arguments a real array in the modern JavaScript? There probably shouldn't be any program that depends on arguments not being a real array? What might be a reason not to make it a real array now?
One reason I can think of is, if programmers start treating it as an array, then the code won't work in older browsers, but there are other things in ECMA-5 that won't work in older browsers too.
Until very late in the development of ECMAScript 5, argument object were going to inherit all of the Array.prototype methods. But the "final draft" of ES5 approved by TC39 in Sept. 2009 did not have this feature.
In August 2009, Oliver Hunt of Apple posted this to the the es5-discuss mailing list
https://mail.mozilla.org/pipermail/es5-discuss/2009-August/003112.html
I implement the logic to make the Arguments object inherit from Array
in WebKit last friday and it's quickly turned up a severe
incompatibility with Prototype.js
...
This breaks at the very least a number of Apple sites and Nasa.gov --
...
Due to these site breakages, caused by a major compatibility problem
in a fairly major library it seems infeasible to attempt to retrofit
array like behaviour onto arguments.
The rest of TC39 agreed with Oliver's assessment and the feature was removed from the final draft.
Perhaps, such code has disappeared sufficiently from the web that the proposed ES5 solution would work today. However, it doesn't matter because rest parameters in ES6 is a better solution to the problem and a completely new syntactic feature that can't have any backwards compatibility issues with existing code.
There are sites online that rely upon arguments not being an array, such as those using older versions of Prototype and script.aculo.us. This means that any browser that changed it (ES4 included this, and it was implemented along with numerous other parts in Futhark, used in Opera from 9.5–10.10) would break these sites, and there's a strong market encouragement to not break sites (any browser that breaks websites will not get used by users who care about those sites for obvious reasons, given many sites are rarely updated).
It is mainly because it needs to be read-only as far as I can deduct.
If it were an array, then it needs to be a read-only array which means that we have to get rid of push, pop, splice etc... any method that modifies the array. By this point, even though I agree other methods like slice might come handy but it's already a data structure that has different requirements than javascript Array.
I think they shouldn't have said it is an array-like object, in my view, it is just a different object that happened to have a property called length (same as Array).
what prevents modern implementation not to treat arguments as a real array?
Implementations of what? The EcmaScript 5.1 specification, yes. Yet there is quite accurate specified that there should be an arguments binding and what such an Arguments object is.
Also, an Arguments object just is not a real Array, as it has some very special behaviour regarding its properties. [[Get]], [[Delete]] etc are overwritten to reflect the function argument variables. Calling push, splice etc on such an object if it were an array could cause havoc.
In the next version of ECMAScript, this issue (and several others) is being addressed with rest parameters.
function foo(...rest) {
console.log(rest.slice(1))
}
foo(1, 2, 3);
Unlike arguments, rest parameters will be real arrays, so this will work.
Rest parameters can do even more. In the above example you probably wanted to use the first argument for one thing and everything after it for something else. You could do this instead:
function foo(first, ...rest) {
console.log('first: ', first);
console.log('rest: ', rest);
}
foo(1, 2, 3);
This will log:
first: 1
rest: [ 2, 3 ]
The proposal: http://wiki.ecmascript.org/doku.php?id=harmony:rest_parameters
I've heard alot of people saying that accessing the arguments object is expensive. (example: Why was the arguments.callee.caller property deprecated in JavaScript?)
Btw what exactly does that statement mean at all? isn't accessing the arguments object simply a simple property lookup? what exactly is the big deal?
The big deal is at least twofold:
1) Accessing the arguments object has to create an arguments object. In particular, modern JS engines don't actually create a new object for the arguments every time you call a function. They pass the arguments on the stack, or even in machine registers. As soon as you touch arguments, though, they have to create an actual object. This is not necessarily cheap.
2) Once you touch the arguments object, various optimizations that JS engines can otherwise perform (e.g. detecting cases in which you never assign to an argument and optimizing that common case) go out the window. Every access to the function arguments, not just ones through arguments becomes much slower because the engine has to deal with the fact that you might have messed with the arguments via arguments.
I have also never heard a serious explanation for why accessing the arguments object is expensive. However, this site: http://www.playmycode.com/blog/2011/03/simple-yet-effective-javascript-optimisations/ notes that arguments is not really an array and is less efficient than accessing an array. The above linked site even suggests converting arguments to an array as an optimization.
Going to check with those who know JS interpreters more intimately...
Looking at a lot of NodeJS and Javascript code recently, it seems arguments is not an instance of Array but still behaves like one, so people do stuff like Array.prototype.slice.call(arguments, ...) or [].slice.call(arguments) which adds verbosity and increases hurdle for newbies to understand etc.. Is there a reason why arguments isnt an instance of Array or is this just one those bad parts?
NO. arguments is a standalone object that just so happens to have a length property and the ability to use [] to index it. But otherwise, it is just an object, not an Array object.
And yes, this is indeed one of the bad parts of JavaScript.