Why never use new Array in Javascript
I love javascript, but sometimes it seems it doesn't love me back. Take for instance the Array
constructor function.
var a = new Array(1,2,3,4,5);
a[0] // returns 1
a.length // returns 5
Wanna take a shot in what this returns?
var a = new Array(10);
a[0] // returns undefined
a.length // returns 10, because of reasons.
This only happens when you give only one integer to the Array constructor. Why, what it's doing? Well, the new Array
constructor is taking a page from some programming languages where you needed to specify the memory for your array so you don't get those ArrayIndexOutOfBounds Exceptions.
int *a = (int *) malloc( 10*sizeof(int) ); // ya ol' c
int *a = new int[10]; // c++
int[] a = new int[10]; // java
Yep, it's actually creating an array of with length of 10. We don't have a sizeof
function in Javascript, but a toString
is enough to prove it.
a.toString() // returns ",,,,,,,,,", a comma for each element + 1
You may think that it is correct "semantics", and it is. How else would you create an array of X length? The catch here is, that I've never needed to define a size in an array, since Javascript doesn't need to allocate memory for an array. They are more like... ArrayLists, if you want to go all Computer Science on it. For instance, all the following code is valid in Javascript:
a.pop(); // returns last object of array, removes it
a.push(20); // puts 20 as the last element of the array, returns length
a.shift(); // returns first object of the array, removes it
a.unshift(30); // puts 30 as the first element of the array, returns length
Using new Array[int]
doesn't really have any sense, use, and can prompt to confusion in Javascript. Why? Because native methods of javascript think you know this. For instance, imagine this.
var a = new Array(10);
a.push(30); // returns 11, which if it were a proper stack/queue it would return 10
a.toString() // returns ",,,,,,,,,,20", probably not what you want
We should always use the []
constructor to avoid this kind of things.
var a = [10]
a.toString() // returns "10"
a[0] // returns 10
a.length // returns 1
Am I thinking interview question here? Ask a java/c programmer to create an array of X size and then push elements to it. Just don't be a jerk and ask questions like "What does {} + []
returns?". That's just mean.
Written by Jose Jesus Perez Aguinaga
Related protips
18 Responses
I smiled at the last paragraph :)
Actually it appears that in JavaScript, in 99% of cases you don't need to use constructors for any built-in type (e.g. Array, Number, Boolean, Object), except for maybe Error
:
throw new Error('whoops!')
It's funny how the constructors are there but you don't need to use them directly.
@dpashkevich True indeed! It just brings all sorts of bugs. My favorite one was in a code some friends were having problems with, it looked something like this:
var hasStarted = new Boolean(false);
if(hasStarted || isViewProfile || isViewUsers ... ) { ... }
It had a lot of other flags and OR's and never thought that the hasStarted
was actually being evaluated as an object. Gotta love that silly language.
Mind blown with {} + [], btw.
And with [] + 10, {} + 10, and so on.
Couldn't JS specification just make sense?
@fuadsaud There's great progress being made, though slowly. Making drastic changes might just break the internet... or a few pieces of it.
@zagnaut hum, I see. But, if changing things like this would break something, I can conclude that people really rely on 'features' like this, which should be considered a crime lol
but I really hope to see a JS more consistent in the - hopefully near - future.
There is one very good reason for use of new Array(length)
:
You want to duplicate a string multiple times. Let's say
var times = 5
var a = new Array(times + 1)
var newstring = a.join(oldstring)
^ The easiest way to produce placeholder strings of given lengths for number padding (preceeding zeros) or string padding (preceeding spaces) without needing to loop.
Daniel.
I don't thing the syntax "[10,11,12]" is a good replacement for "new Array(10,11,12)". The former embeds the API into the language syntax, the latter spells out the API clearly. This is also the reason I don't like perl compared to JavaScript. For example, if I have a better implementation for Array class, I can simply replace the name Array with MyArray.
@jamesxli Well, the thing is that new Array
returns an object, not an actual array. You can have Array operations, but it's not a native array type. Try new Array(1,2,3) === [1,2,3]
. For me, it's more misleading the fact that when I type new Array
I get an object.
We don't really replace classes in Javascript, we only extend prototypes. We can do it even on run time (not a good idea, but it's possible). For instance, let's say we want to add the Array the famous foldr
function and we wrap that in a specific "class" called superArray.
function superArray() {
Array.call(this);
var self = this;
this.foldr = function(fun, acum, arr) {
if(arr.length)
if(arr.length === 1) return fun(arr[0], acum);
else return fun(Array.prototype.splice.apply(arr, [0,1])[0], self.foldr(fun, acum, arr));
else return acum;
};
}
Now, if we have already created an object with our class "Array", we can overload the internal [[proto]] property to have fun.
var b = new Array(1,2,3,4);
b.prototype; // returns []
b.__proto__ = new superArray();
b.foldr(function(x, y){ return (x+y); }, 0, b) // returns 10
This is not recommended (it's a non standard and I think it's going to be dropped on Harmony), but it's possible, because Javascript holds a reference to the object's prototype, something that it's not possible in other languages. The proper way is to overload the prototype of the class we are calling.
superArray.prototype = new Array();
superArray.prototype.constructor = superArray;
This allows us to have our extended methods (from our new class) without removing the prototype chain (which changing [[proto]] does).
@jjperezaguinaga are you sure that the Array constructor doesn't return Array instances?
[1,2,3] === [1,2,3] // --> false
Array.isArray([1,2,3]) // --> true
Array.isArray(new Array(1,2,3)) // --> true
Also, while I never use the constructor format to initialize my array values, it's very useful for creating medium sized arrays quickly. Suppose I need to create a 10 length array quickly. It's much faster in most engines to do:
var arr = new Array(100);
for (var i = 0; i < 100; i++) {
arr[i] = i * i;
}
vs
var arr = [];
for (var i = 0; i < 100; i++) {
arr.push(i * i);
}
..b.ecause while arrays can resize on the fly, that isn't without cost. It does have to reallocate memory when the size required exceeds the backing store.
@creationix You are right, arrays created whether with new Array
or []
are instances, which should make them behave as object (being stored by reference). However, this is not the case. Javascript handles Arrays in a different way it handles complex objects (Object, Function) or primitive/native ones (Number, String, Boolean). I have yet to find a way to define how Javascript manipulates Arrays, which might explain why things like the arguments
variable behaves the way it behaves.
Still, I fail to see why would I use new Array(number)
for a storage purpose solution (which is the reason why arrays were created by). Quick and dirty solutions like dvdotsenko's ones with the placeholders are nice usage of a tool for specific scenarios, but that's as much as I can say about that combination.
@jjperezaguinaga, I promise you that Array
instances are normal objects just like any other object in javascript. Semantically they work just like Object
instances except they have a magic .length
property and the addition of a bunch of array methods like .forEach()
, .map()
, .join()
, etc.
Arrays are stored by reference just like any other non primitive value. They are mutable. If two variables point to the same array then when the array is changed through one reference, the other reference will see the change since it is the same object.
Regarding using the constructor form with numerical lengths, I find this very useful. There are many cases that I need to quickly create an array of a known size and fill it with data that I get through a loop. For example, to implement Array.prototype.map
in ES3 browsers, I would do:
if (typeof Array.prototype.map !== "function") {
Array.prototype.map = function (callback, thisArg) {
// Create a new array of the same size
var length = this.length;
var arr = new Array(length);
for (var i = 0, i < length; i++) {
arr[i] = callback.call(thisArg, this[i], i, this);
}
return arr;
};
}
It would be silly and slow to create an empty array and grow it using .push()
. While it's technically true that setting values outside the array length also cause it to grow, the VMs are able to often optimize the backing store to the array and speed things up when you declare up front how large it is to be.
@creationix My, my, I did a bad test with a new Array
. You are right, they behave in the same way objects do (stored by reference not by value). Pardon my mistake good sir.
In the other hand, I stand to my words: new Array
is a no no. Not only because of performance, but because of readability. Multiple constructors can confuse the heck out of developers! Since Javascript syntax allows having as many arguments as we want, we would need to write extra code in order to check which function you are calling (IDE's or Code Editors can't read the prototype of the function and guess you are calling it wrong, like in Java or C++). We already have native functions with multiple optional parameters, do we really need to have our own code with ugly splices
to arguments
when same behaviour is expected?. The fact that the behaviour is incredible different by just adding one extra parameter is just nonsense! "If you put one parameter with value N, I create an array of length N, while if you put two, I create an array of length 2 with those two parameters as values". Woot? I would bet my magic mouse that somewhere someone is head-butting his desk because of this.
We don't need to allocate memory in Javascript. That doesn't mean that we don't care about it in Javascript. We just do it by our own ways. If you most optimal implementation of a code relies in a language feature instead of programming strategies, whenever you move from one language (add southpark meme), "You are going to have a bad time".
For instance, take this tail recursion implementation of Array.prototype.map
Array.prototype.map = function (callback, thisArg) {
var self = this, acc, i, tail_map;
acc = [];
i = 0;
tail_map = function(c, a, r, i) {
if(self[i]) {
r[i] = c.call(a, self[i], i, self);
return arguments.callee(c, a, r, ++i);
} else {
return r;
}
}
return tail_map(callback, thisArg, acc, i);
};
77% faster and no need to use new Array
. The code is language agnostic (other than the use of call
, callee
or this
, but you can use a named function for that) so you don't have to worry what the heck Python/Ruby/Haskell/Erlang/Scala/Clojure/PHP/Java/Dart/Groovy/Go/Scheme/Lisp/R mean when you do new Array
: They all agreed that [1,2,3]
is an array/list of 3 elements.
@dvdotsenko you can use it without creating "a" object and call "new" by var newstring = Array(times + 1).join(oldstring)
I'm going to agree with both @jjperezaguinaga and @creationix -- while new Array(n)
is most of the time unnecessary, it is faster than just attaching items to non-existant indexes, and certainly faster than pushing.
Also, I "fixed" and extended @jjperezaguinaga perf tests to demonstrate this more clearly, as well as nearly doubling the performance of the original tailmap (why arguments.callee
rather than just tail_map
recursion?).
@zaus Impressive work right there. Thanks for the demonstration! On why arguments.callee
vs naming the function, I think it was because when I was coding the example I was using an anonymous function. My bad!
I think I'm more open today about new Array
than some time ago. I think my fear is that in this Javascript world were people are not always aware or the usage of new
or Function
constructors, this code can be a pitfall easily avoidable. For instance, to "clean" an array, you don't do array = []
as might be logical, but array.length = 0
. Oh Javascript.
Finally, unless you are doing iterative pushing, the difference in terms of performance won't kill anybody. Bugs that confuse people due non-VM oriented code, however, can turn a Dev into a serial killer.
i find nothing wrong with new Array(N) and the examples you provided as "wrong" or "unexpected" are completely normal. if i want to allocate 20 spaces in a new array - that's exactly what is it for. if i push something in the array (that has already 20 indeces reserved), it gets pushed further. like you said, arrays are dynamic in javascript and that's the result of it.
an example use: we want to keep last N timestamps of something and then cycle through it if we run out of space. we comfortably define the size of the N in the array definition, without having to write conditions later, like this:
//... the definition
var last_stamps = new Array(20);
//... then somewhere later on some event ...
for(var i=0;i<last_stamps.length;i++){
if(i<last_stamps.length-1){
last_stamps[i]=last_stamps[i+1];
}
}
last_stamps[last_page_stamps.length-1]=(new Date()).getTime();
like first-in last-out queue
I would argue exactly almost the opposite as what this article suggests.
In all programming languages I can think of, [...] denotes creating an array of a certain size. So if you don't think about the content of this article too much, you might think "Oh OK, I'll never use 'new Array()'" and write something like:
[10].join('a')
... thinking this will create a string of 10 a's.
So instead of "Never use 'new Array()'", I would suggest "ALWAYS use 'new Array()' if you want to create an array with a specific size".
Thanks for sharing this useful code!