DEV Community

loading...
Cover image for JS Array Manipulation Quirks

JS Array Manipulation Quirks

jenc profile image jen chan Updated on ・2 min read

In my previous post I mentioned I’d been practicing problem-solving by handwriting answers. It's taking much longer than on an IDE or cargo-programming, but bringing to light a lot of misunderstandings I had about JS methods:

1 The difference between slice() and splice()

.slice() extracts [0, n-1] array items as a new array.
.splice() mutates the original array by deleting items from [0, n] positions and returning it in place of the initial array. It also offers a third param to add items.

This is explained more thoroughly here and here from the functional programming perspective

2 One cannot simply iterate through a string

Applying .split('') by empty string or [...] (spread operator) returns an array of discrete letters

Exception: using charAt() in a for loop

Good points! You could iterate through a string directly though with a for loop, accessing the character at each index!

function forEachChar(str, cb) {
   for (let i = 0; i < str.length; i++) {
      cb(str.charAt(i))
   }
}
Enter fullscreen mode Exit fullscreen mode

3 The spread operator produces a shallow copy

If the array-to-copy is more than one level deep, thou shall not [...spread]. In a shallow copy, nested arrays (or objects) retain references to the original copy. Any changes of them affect initial and subsequent copy.

Shallow and deep copying in greater detail by Laurie Barth.

4 for (i of ...) vs for (i in ...)

The former enables iteration over arrays, strings, DOM node collections, maps, sets and generators. The latter iterates through object properties such as keys. for...of vs for...in

5 .join() vs. .push() vs .concat()

.push() mutates arrays and adds items to the end of the length
.concat() merges arrays, and runs faster than .join()

6 Some of my faves are problematic: they mutate arrays

i.e. shift(), unshift(), splice(), pop(), push()

It's now become my hobby to find alternatives that don't mutate the state, such as reduce() filter(), map(), some() and concat()

7 find() vs filter()

find() returns the first value that matches from a collection and stops unless I put it in a for loop.filter() returns an array of matched values.

8 forEach is a void function

It wasn’t clear when I read the MDN docs, and it seemed there were arguments both ways on blogs that it would mutate the original array. It doesn’t return anything, and with the help of the DEV community ❤️ I was able to discover that!

Related Reading


Are there any others you've come across that you'd like to add to this list? Let me know!

Discussion (14)

Collapse
shiftyp profile image
Ryan Lynch (he/him) • Edited

Good points! You could iterate through a string directly though with a for loop, accessing the character at each index!

function forEachChar(str, cb) {
   for (let i = 0; i < str.length; i++) {
      cb(str.charAt(i))
   }
}
Collapse
willsmart profile image
willsmart • Edited

As a rule I see charAt/charCodeAt/length for strings as useful optimisations in well-curated performance code and anti-patterns in typical code.

They are great if you know what's in the string (i.e. you made it or "sanitised" it to take all the interesting bits out), but can get you in trouble for arbitrary user input where at some stage some fool is going to try out some fancier unicode.

spreadem = s => {
  for (let i=0; i<s.length; i++) console.log(`Char at index ${i}: '${s.charAt(i)}'`); 
  console.log(`Spread graphemes: '${[...s].join("', '")}'`);
}

charAt works nicely in the usual case:

> spreadem("01234")
< Char at index 0: '0'
< Char at index 1: '1'
< Char at index 2: '2'
< Char at index 3: '3'
< Char at index 4: '4'
< Spread graphemes: '0', '1', '2', '3', '4'

But get's funky when multi-char graphemes exist:

> spreadem("𝟘𝟙𝟚𝟛𝟜")
< Char at index 0: ''
< Char at index 1: ''
< Char at index 2: ''
< Char at index 3: ''
< Char at index 4: ''
< Char at index 5: ''
< Char at index 6: ''
< Char at index 7: ''
< Char at index 8: ''
< Char at index 9: ''
< Spread graphemes: '𝟘', '𝟙', '𝟚', '𝟛', '𝟜'

(see speakingjs.com/es5/ch24.html for a bit of background)

Collapse
shiftyp profile image
Ryan Lynch (he/him)

This is a really good point! Multi-character glyphs do mess with string iteration whether you split, spread, or loop. Trying to remember my solution to this the last time I encountered it in practice. I may have used a library to convert the strings to multi character arrays...

Collapse
jenc profile image
jen chan Author

Omg I have not thought of this either. Multi-char graphemes really mess with my head

Collapse
jenc profile image
jen chan Author

Wow yesss! I didn't think to!

Collapse
link2twenty profile image
Andrew Bone

const newArr = arr.slice()

Is my favourite way to clone an array

Collapse
jenc profile image
jen chan Author

woah woah woah! Does it clone past 1 level deep?

Collapse
link2twenty profile image
Collapse
karataev profile image
Eugene Karataev

forEach doesn't mutate an array. Mutation happens only if you explicitly mutate array items in iteration function.

Collapse
link2twenty profile image
Andrew Bone • Edited

I guess if you're using forEach like map you could say it mutates

arr.forEach(a => a = {id: a})

// VS

const newArr = arr.map(a => {id: a})

// VS

const newArr = [];
arr.forEach(a => newArr.push({id: a}))
Collapse
karataev profile image
Eugene Karataev

Well, actually if we execute your examples, we can notice that forEach doesn't mutate the original array.
forEach

Thread Thread
jenc profile image
jen chan Author • Edited

Thanks for pointing that out. This seems like a gray area. I don't know how I got under the impression that forEach mutates the original array items, maybe it was this post I looked up as I was writing the post. It's not entirely clear to me what forEach actually returns if I ran it on an array its own.

I have around the web read general opinions that forEach iterates straight through arrays without breaking, and skips empty values, so that if one wanted to include a condition it would be easier to go with a for-loop. I don't have enough experience with forEach to tell, tbh

Thread Thread
belvederef profile image
Francesco Belvedere

To address your point "what forEach actually returns", the answer is "nothing". forEach does not return a value, it is a void function.

Thread Thread
jenc profile image
jen chan Author

Thank you! That makes a lot of sense.

Forem Open with the Forem app