I commonly see developers use an expression like the following in JavaScript:
arr = []
arr[arr.length] = "Something"
arr[arr.length] = "Another thing"
Wouldn't push be more appropriate?
arr = []
arr.push("Something")
arr.push("Another thing")
I actually asked myself the same question at the start of this year. UPDATED with new test cases http://jsperf.com/array-push-vs-unshift-vs-direct-assignment/2
It appears that push is much faster in chrome, and about equal in FF. Also direct is faster in IE9, but I would be interested to see how it performs in IE10.
I would say that most developers would assume setting the length of the array, and then using direct assignment is faster, as is the case with most programming languages. But JavaScript is different. Javascript arrays aren't really arrays, they're just key/value maps just like all other JavaScript objects. So the pre-allocation is essentially falling on deaf ears.
Personally I prefer push (:
I believe that it's mostly habit.
Some developers use it simply because it's the way they are used to do it, and haven't considered that push would be an alternative.
Some developers have learned once upon a time that one method is much faster than another, and haven't reviewed this in light of the recent performance improvements of the Javascript engines.
Personally I use push frequently. Most of the time readability and maintainability is more important than performance, at least when the performance impact is small enough. The performance tests posted in the answers here show that the performance difference between various methods isn't very big.
Very often when I'm pushing an object into an array, I want the reference to that object to be returned to me. For example:
// Returns object
function createAndAdd(arr) {
return arr[arr.length] = { id: 1 };
}
var obj = createAndAdd(arr);
Now I have the new object and I've added it to an array. If I'd used push then I would have been returned the length of the array. It would look like the following:
function createAndAdd(arr) {
var obj = { id: 1 };
arr.push(obj);
return obj;
}
or even uglier, especially if the object gets big:
function createAndAdd(arr) {
return arr[arr.push({ id: 1 }) -1];
}
Personally, I love functions with immediate return statements. Keeps it simple and easy to look at. Love the question. Thanks for asking it.
It's a way to limit nested braclets. If you have enough of them you cant see howmany there are or howmany you need (when later looking at the code). I would use a var, one should only have to count things one time.
bar = foo.length;
foo[ bar++ ] = "new item 0";
foo[ bar++ ] = "new item 1";
foo[ bar++ ] = "new item 2";
foo[ bar++ ] = "new item 3";
http://jsfiddle.net/vEUU3/
I think its not about performance, or at least is not such a big deal... what really matters here is declarative way vs imperative way.
Array.prototype.push method returns the updated array's length while the direct declaration returns the value that is being assigned. That's the only difference I see, but I usually read some best-practices recommendations about the push method being a better way to assign new values to an array.
Some coders don't recommend to use array.push() cause it modify the principal object. Instead of push(), you could use the spread operator:
let arrayOne = [1, 2, 3, 4];
let arrayTwo = arrayOne;
arrayTwo.push(5);
console.log(arrayOne);
console.log(arrayTwo);
// equals to arrayOne before the push()
let arrayOneAux = [1, 2, 3, 4];
// spread operator
let arrayThree = [...arrayOneAux, 5];
console.log(arrayOneAux);
console.log(arrayThree);
Related
I'm learning Svelte, and read in the documentation that arrays need to be reassigned in order for a component or page to update it. For that they devised a more idiomatic solution. Instead of writing:
messages.push('hello');
messages = messages;
you can write instead:
messages = [...messages, 'hello'];
Alright, makes sense. But then the documentation says:
You can use similar patterns to replace pop, shift, unshift and splice.
But how? I cannot see how you can remove items from an array. More to the point, how could I write the following more idiomatically?
messages.splice(messages.indexOf('hello'), 1);
messages = messages;
You could e.g. use the filter array method to create a new array without the element 'hello':
messages = messages.filter(m => m !== 'hello');
As mentioned, Svelte's reactivity is triggered by assignments. The current Svelte tutorial uses JavaScript's (ES6) spread syntax (three dots) to add the next-higher number to an array, providing a more idiomatic solution than a redundant assignment using push:
function pushNumber() {
numbers = [...numbers, lastnumber]; // 1, 2, 3, 4, 5
}
You could use spread syntax to replace pop, shift, unshift and splicethough it might increase the time and complexity of the operation in some cases:
function unshiftNumber() {
numbers = [firstnumber, ...numbers]; // 0, 1, 2, 3, 4
}
function popNumber() {
numbers = [...numbers.slice(0,numbers.length - 1)]; // 1, 2, 3
}
function shiftNumber() {
numbers = [...numbers.slice(1,numbers.length)]; // 2, 3, 4
}
function spliceNumber() {
numbers = [firstnumber, ...numbers.slice(0,numbers.length-1)];// 0, 1, 2, 3
}
Spread is just one way to do it, though. The purpose behind not using pop/push etc is to encourage immutability. So any removal can just be a filter, for example.
There are several things to consider here.
Given this code:
messages.splice(messages.indexOf('hello'), 1);
messages = messages;
What's happening here is:
Looking for the first occurrence of the string "hello" in the array
Removing such element from the array, based on the index found.
The assumption here is that "hello" needs to exists, otherwise the could would remove the last item from the array (since indexOf returns -1).
The original array is therefore mutate: depends by the context, that sometimes can be preferable instead of copying the whole array into a new one; otherwise it's generally a better practice avoid such mutation.
So. If you want to have this behavior exactly, probably this is the best code you can have. For example, takes the filter example:
messages = messages.filter(message => message !== "hello")
What's happening here is:
Filter out any element equals to "hello"
Returns a new array without such element
So it's quite different from the original code: first of all, it always loop the whole array. If you have thousands of element, even if you have only one "hello" at the second index, it would always iterate all of them. Maybe it's what you want, maybe not. If the element is unique, such as an id, maybe you want to stop once you find it.
Second, it returns a new array. Again, that usually a better practice than mutate the array, but in some context it's preferable mutate it instead of create a new one.
So, if you want to mutate the original array, it's probably better to stick to your original code.
If, instead, you don't care (such as the example of push), I believe that in the intention of svelte's developers, your code would be roughly translate to:
let i = messages.indexOf("hello");
messages = [...messages.slice(0, i), ...messages.slice(i + 1)];
(Still assuming there is a "hello" message and you're interested only in the first occurrence).
It's unfortunate that JS doesn't have a better syntax to handles slices.
In case you're wandering, filter can also be used to remove elements using a given index:
let elements = ['a','b', 'c'];
let idx = 1;
elements = elements.filter( (e,i) => i !== idx );
// => ['a', 'c']
You can perform the usual push and pop or `splice on your Array
But because Svelte's reactivity is triggered by assignments, using array methods like push and splice won't automatically cause updates.
According to All about Immutable Arrays and Objects in JavaScript you can do it this way...
let messages = ['something', 'another', 'hello', 'word', 'another', 'again'];
const indexOfHello = messages.indexOf('hello');
messages = [...messages.slice(0, indexOfHello), ...messages.slice(indexOfHello + 1)];
Note the difference between splice and slice
The splice() method adds/removes items to/from an array, and returns
the removed item(s). Note: This method changes the original array.
Syntax: array.splice(start, deleteCount, itemstoAdd, addThisToo);
But
The slice() method returns the selected elements in an array, as a new array object. The slice() method selects the elements starting at the given start argument, and ends at, but does not include, the given end argument.
Note: The original array will not be changed.
In order words
It return a shallow copy of a portion of an array into a new array
object selected from begin to end (end not included). The original
array will not be modified.
Syntax: array.slice(start, end);
You can try this: https://svelte.dev/repl/0dedb37665014ba99e05415a6107bc21?version=3.53.1
use a library called svelox. It allows you to use the Array native api(push/splice...etc.) without reassignment statements.
Spread the spliced array to reassign it to itself ;)
messages = [...messages.splice(messages.indexOf('hello'), 1)];
The goal is to make Svelte detecting that array messages (a property of your component or a variable in the Svelte store) has changed. This is why the array messages must be declared with let or var keyword, not const. This way you're allowed to reassign it. And the reassign operation itself is sufficient to make Svelte detecting that the array has changed.
Perhaps even, simply by doing so works too:
messages = messages.splice(messages.indexOf('hello'), 1);
If entries are being added and removed in an array (not pushed/popped) what is the optimal way to scan and find the first undefined element, so that it can be set with a new value?
If the array contains holes with findIndex you can find the first one like so:
[1, 2,, 4, 5].findIndex(Object.is.bind(null, undefined))
indexOf will ignore holes. So
[1, 2,, 4, 5].indexOf(undefined)
Always returns -1.
Assuming we have no prior knowledge of operations being performed on the array, the fastest way is to simply iterate through the entire array linearly
var arr = [1, 2, , 4];
for (var i = 0; i < arr.length; i++) {
if (typeof arr[i] === 'undefined') {
arr[i] = 'foo';
break;
}
}
Or we can keep track of whats being removed using something like this
var earliestRemoved;
if (newRemoved < earliestRemoved || !earliestRemoved) {
earliestRemoved = newRemoved;
}
I guess the simplest way would be to use Array.indexOf to find the index of the first undefined element.
Today I've got the same question, so here's my take on it.
It all depends on your specific problem. My problem was this:
I have an array instances[], with objects in it;
Methods of some of these are registered as callbacks elsewhere;
Sometimes I do need to remove some instance from an array;
Sometimes I need to add new instance;
The problem is that I can't just splice an array, because it'll invalidate all my callback registrations. Well, I can just leave them in the array and forget, adding new elements at the end. But it's.... bad. It's leaking memory.
So I came with an idea of making "holes" in an array in place of removed elements, and then re-using these holes for new elements.
Thus the question "how to find undefined elements in an array?".
Someone here suggested using array.indexOf(undefined) - unfortunately that won't work... for some reason.
But, if it's okay in your case to use null or 0 instead, then it will work.
Here's an example:
var A = [1,1,1,1, , ,0,0,null,1,1];
A.indexOf(undefined); // output: -1 (doesn't work)
A.indexOf(0); // output: 6
A.indexOf(null); // output: 8
Hope this'll be useful.
With the hindsight of about 4.3 years behind me, What I'd do now is, since I'm the owner of the array, is to make an object with 2 arrays in it, the 2nd just contains the indexes that are cleared out. (prob. manage it in a class). So always have clear set with knowledge of what's undefined.
This to avoid iterating through arrays linearly.
2021
Nowadays you can safely use:
myArray.findIndex((element) => (typeof element === "undefined"));
Is there a simple way to verify that an ES6 Set contains a value that is a particular array? I'd like a solution that doesn't require me to use a reference:
var set = new Set();
var array = [1, 2];
set.add(array);
set.has(array); // true
set.add([3, 4]);
set.has([3, 4]); // false
So far my solution is to store everything as a string, but this is annoying:
set.add([3, 4].toString());
set.has([3, 4].toString()); // true
No there is not.
A Set works on objects and primitives and is useful for preventing identical primitives and re-adding the same object instance.
Each array is their own object, so you can actually add two different arrays with the same values.
var set = new Set();
set.add([3, 4]);
set.add([3, 4]);
console.log(set.size);//2
Additionally, there's nothing to prevent an object from being changed once in a set.
var set = new Set();
var a1 = [3, 4];
var a2 = [3, 4];
set.add(a1);
set.add(a2);
a2.push(5);
for (let a of set) {
console.log(a);
}
//Outputs:
// [3, 4]
// [3, 4, 5]
A set does not have a mechanism for checking the values of objects in a set. Since the value of an object could change at any time, it wouldn't be much more efficient than simply looping over them yourself.
The functionality you are looking has been kicked around in various ECMAScript proposals, however it does not appear to be coming anytime soon.
Too low rep, can't add comment so I'm adding it as an answer here.
In advance, apologies for the wall of text.
I like CTS_AE's answer, and wanted to take the same route.
However, there is one thing to note, and that is how are arrays put to String.
let memory = {};
memory[[1,2,3]] = "123";
console.log(memory[[1,2,3]]); // "123"
console.log([1,2,3].toString(); // "1,2,3"
console.log(memory["1,2,3"]); // "123"
Now this wouldn't be such an issue if you knew what you were putting in are exclusively arrays... or would it?
MDN about Array.prototype.toString() says
For Array objects, the toString method joins the array and returns one string containing each array element separated by commas.
Which brings 2 big issues:
indexing via array is the same as indexing via array.toSring()
since toString() is recursive, nested arrays end up being stringified into the same format as single-level arrays.
let memory = {};
memory[[1,2,3]] = "123";
console.log(memory[[1,2,3]]); // "123"
console.log(memory["1,2,3"]); // "123"
console.log(memory[[[1],[2],[3]]]); // "123"
console.log(memory[[[[1],2],3]]); // "123"
...and the list goes on. It's not hard to see when these issues can really break your project. I encountered such issue just a while ago when I tried memoizing
function doSomeStuff(string, sourceIdxs, targetIdxs) {
if (memo[[string, sourceIdxs, targetIdxs]])
return memo[[string, sourceIdxs, targetIdxs]];
// ...
}
In such case for example ["foo", [1, 3, 5], [6, 10]] and ["foo", [1, 3], [5, 6, 10]] point to the same value, and I ended up overwriting existing values, effectively corrupting the function memory.
Now, in the specific case of the ArraySet answer above, the issue persists. While you don't mind if you overwrite existing key with another "same" key, you can end up getting false positives.
So how to fix this?
Option 1. stringify
The easy way out is using JSON.stringify() to write the "exact" string representations of all the key data.
let memory = {};
memory[JSON.stringify([1,2,3])] = "123";
console.log(memory[JSON.stringify([1,2,3])]); // "123"
console.log(memory[JSON.stringify([[1],[2,3]])); // undefined
This helps with the false positives.... kind of. For one, you won't have issues with overlapping elements in the array. [[1,2],[3]] no longer points to where [[1],[2,3]] does.
However, [1,2,3] and "[1,2,3]" do. Also (in some bizarre cases), the array elements might contain [ or ] characters, which might complicate the issue further.
And you might not care for such cases. Your keys may still be restrained well enough for such things not to happen. And you might even want such behaviour. If you do, go for it.
Good:
Fixes most of issues of the ArraySet
Is easy to implement
Bad:
JSON.stringify()is fairly slow.
Option 2. separators
Much simpler and quicker, while still giving a bit of advantage over the old solution.
function toSepString(arr) { return arr.join("|") }
let memory = {};
memory[toSepString([[1,2],3])] = "123";
console.log(memory[toSepString([1,[2,3]])]); // undefined
Now of course this helps only with the "outer-most" layer.
console.log(toSepString([1,[2,[3]]])); // "1|2,3"
console.log(toSepString([1,[[2],[[3]]]]); // "1|2,3"
So should you use this, you need to make sure the specific elements of your key array can't become ambiguous when converted to string.
Of course, you could make the function recursive and add "[", "]" on each beginning and end, essentially copying a part of the functionality of JSON.stringify(). I would imagine this would be still more performant than calling JSON.stringify() directly, but that would require tests.
Good:
faster than its built-in counterpart
Bad:
issues with nested arrays
issues with separator not appearing in values
It's not using a Set, but may solve your problem.
If you want to make sure that an array can only exist in a collection once, and you want instant lookups you can use a hash by abusing its keys. They do not necessarily need a value. You can set them to undefined, to true, or something else if it makes you sleep better at night 😛.
Inspired by: javascript search array of arrays
It is halfway tempting to make a wrapper, but it is also simple enough to handle a small job.
const hash = {};
hash[[1, 2, 3]] = undefined;
hash[[3, 4]] = undefined;
hash[[3, 4]] = undefined;
console.log({hash});
console.log("hash has [3, 4]?", hash.hasOwnProperty([3, 4]));
For me, this will work fine, because I am checking if a coordinate exists in a collection. Unfortunately from this stackoverflow it seems that Set cannot be the answer to this problem.
Coincidentally it seems to implicitly solve your problem of not calling toString on the array which this seems to do under the hood. Personally I don't care for hash.hasOwnProperty.
A More Elegant Option
You could even write a wrapper to your code like:
class ArraySet extends Set {
add(arr) {
super.add(arr.toString());
}
has(arr) {
return super.has(arr.toString());
}
}
const arraySet = new ArraySet();
arraySet.add([1, 2]);
arraySet.add([3, 4]);
arraySet.add([3, 4]);
console.log("ArraySet has [3, 4]?", arraySet.has([3, 4]));
Apparently, it doesn't look like set.has() will check for Array Elements like that. They are added, however:
var set = new Set([1, 2]);
set.add([3, 4]);
console.log(Array.from(set));
Janky solution using Object Keys as a set:
let set = {};
set[[1,4]] = true;
set[[4,5]] = true;
set[[4,5]] = true; // Already added, won't make a difference
console.log(set); // "{ '1,4': true, '4,5': true }"
let setAsArr = (Object.keys(set).map(a => a.split(",")));
console.log(setAsArr) // [ [ '1', '4' ], [ '4', '5' ] ]
This will let you create a "set" that prevents duplicate array item entries and has easy item access.
Note: Does not work on arrays of strings that contain the "," character
Which is faster in JavaScript: looping through an array with integer indices or looping through an object with properties? What is the performance difference, if any?
var array = [1, 2, 3];
for (var i = 0; i < array.length; i++) {
}
Compared to:
var object = {"1":1, "2":2, "3":3};
for (var x in object) {
}
To the best of my understanding JavaScript arrays are actually backed by a map, so I would expect both to perform the same. Any information is greatly appreciated!
EDIT: I should probably clarify that I'm interested in the loop overhead (like if for in is more expensive) and also the lookup time of accessing each element (object[x] vs array[i]).
The accepted answer uses an invalid benchmark and is completely wrong. As the comments point out console.log will dominate the test results, rendering them useless. Take a look at this comparison: http://jsperf.com/performance-of-array-vs-object/17
Array iteration is about 48 times faster than object iteration. It improves considerably if you know the range of the keys of the object in advance, but is still significantly slower than array iteration.
i created a jsperf http://jsperf.com/array-vs-object-loop.
i thought the extra property-access would need more time. in chrome 24 it doesnt make much of a difference, in firefox 19 the object way needs 5-10% longer.
you should probably also take the size of the object/array into account.
EDIT: forget about that answer, an take a look at the accepted one!
What are the downsides to doing:
var myArray = [];
myArray[myArray.length] = val1;
myArray[myArray.length] = val2;
instead of:
var myArray = [];
myArray.push(val1);
myArray.push(val2);
I'm sure the push method is much more "acceptable", but are there any differences in functionality?
push is way faster, almost 300% faster.
Proof: http://jsperf.com/push-vs-length-test
Since arrays in JavaScript do not have holes the functionality of those two methods is equal. And yes, using .push() is much cleaner (and shorter).
I've generally thought length assignment was faster. Just found Index vs. push performance which backs that up; for my Chrome 14 browser anyway, over a single test run. However there is not much in it in Chrome.
There seems to be discrepancy on which test is faster among the varying JavaScript engines. The differences in speed may be negligible (unless an unholy amount of pushes are needed). In that case, the prudent developer should always err on the side of readability. In this case, in my opinion and the opinion of #TheifMaster is that [].push() is cleaner and it is easier to read. Maintenance of code is the most expensive part of coding.
As I tested, the first way is faster, I'm not sure why, keep researching. Also the ECMA doesn't mentioned which one is better, I think it is depending on how the browser vendor implements this.
var b = new Array();
var bd1 = new Date().getTime();
for(var i =0;i<1000000; i++){
b[b.length] = i;
};
alert(new Date().getTime()- bd1);
var a = new Array();
var ad1 = new Date().getTime();
for(var i =0;i<1000000; i++){
a.push(i);
};
alert(new Date().getTime()- ad1);
In JS there are 3 different ways you can add an element to the end of an array. All three have their different use cases.
1) a.push(v), a.push(v1,v2,v3), a.push(...[1,2,3,4]), a.push(..."test")
Push is not a very well thought function in JS. It returns the length of the resulting array. How silly. So you can never chain push() in functional programming unless you want to return the length at the very end. It should have returned a reference to the object it's called upon. I mean then it would still be possible to get the length if needed like a.push(..."idiot").length. Forget about push if you have intentions to do something functional.
2) a[a.length] = "something"
This is the biggest rival of a.push("something"). People fight over this. To me the only two differences are that
This one returns the value added to the end of the array
Only accepts single value. It's not as clever as push.
You shall use it if the returned value is of use to you.
3. a.concat(v), a.concat(v1,v2,v3), a.concat(...[1,2,3,4]), a.concat([1,2,3,4])
Concat is unbelievably handy. You can use it exactly like push. If you pass the arguments in array it will spread them to the end of the array it's called upon. If you pass them as separate arguments it will still do the same like a = a.concat([1,2,3],4,5,6); //returns [1, 2, 3, 4, 5, 6] However don't do this.. not so reliable. It's better to pass all arguments in an array literal.
Best thing with concat is it will return a reference to the resulting array. So it's perfect to use it in functional programming and chaining.
Array.prototype.concat() is my preference.
4) A new push() proposal
Actually one other thing you can do is to overwrite the Array.prototype.push() function like;
Array.prototype.push = function(...args) {
return args.reduce(function(p,c) {
p[p.length] = c;
return p
}, this)
};
so that it perfectly returns a reference to the array it's called upon.
I have an updated benchmark here: jsbench.me
Feel free to check which is faster for your current engine. arr[arr.length] was about 40% faster than arr.push() on Chromium 86.