Friday, November 16, 2018

How do you clone an Array of Objects in Javascript?

C'mon guys, it is the 21st century: as long as your objects contain JSON-serializable content (no functions, no Number.POSITIVE_INFINITY, etc.) there is no need for any loops to clone arrays or objects. Here is a pure vanilla one-line solution.
var clonedArray = JSON.parse(JSON.stringify(nodesArray))
To summarize the comments below, the primary advantage of this approach is that it also clones the contents of the array, not just the array itself. The primary downsides are its limit of only working on JSON-serializable content, and it's performance (which is significantly worse than a slice based approach).
------
I solved cloning of an array of objects with Object.assign
const newArray = myArray.map(a => Object.assign({}, a));
or even shorter with spread syntax
const newArray = myArray.map(a => ({...a}));

Tuesday, November 13, 2018

Remove, insert, update array with Array.prototype.splice()

The splice() method changes the contents of an array by removing existing elements and/or adding new elements.

SyntaxSection

array.splice(start[, deleteCount[, item1[, item2[, ...]]]])

ParametersSection

start
Index at which to start changing the array (with origin 0). If greater than the length of the array, actual starting index will be set to the length of the array. If negative, will begin that many elements from the end of the array (with origin -1) and will be set to 0 if absolute value is greater than the length of the array.
deleteCount Optional
An integer indicating the number of old array elements to remove.
If deleteCount is omitted, or if its value is larger than array.length - start (that is, if it is greater than the number of elements left in the array, starting at start), then all of the elements from start through the end of the array will be deleted.
If deleteCount is 0 or negative, no elements are removed. In this case, you should specify at least one new element (see below).
item1, item2, ... Optional
The elements to add to the array, beginning at the start index. If you don't specify any elements, splice() will only remove elements from the array.

Return valueSection

An array containing the deleted elements. If only one element is removed, an array of one element is returned. If no elements are removed, an empty array is returned.

DescriptionSection

If you specify a different number of elements to insert than the number you're removing, the array will have a different length at the end of the call.

ExamplesSection

Remove 0 (zero) elements from index 2, and insert "drum"Section

var myFish = ['angel', 'clown', 'mandarin', 'sturgeon'];
var removed = myFish.splice(2, 0, 'drum');

// myFish is ["angel", "clown", "drum", "mandarin", "sturgeon"] 
// removed is [], no elements removed

Remove 1 element from index 3Section

var myFish = ['angel', 'clown', 'drum', 'mandarin', 'sturgeon'];
var removed = myFish.splice(3, 1);

// removed is ["mandarin"]
// myFish is ["angel", "clown", "drum", "sturgeon"]

Remove 1 element from index 2, and insert "trumpet"Section

var myFish = ['angel', 'clown', 'drum', 'sturgeon'];
var removed = myFish.splice(2, 1, 'trumpet');

// myFish is ["angel", "clown", "trumpet", "sturgeon"]
// removed is ["drum"]

Remove 2 elements from index 0, and insert "parrot", "anemone" and "blue"Section

var myFish = ['angel', 'clown', 'trumpet', 'sturgeon'];
var removed = myFish.splice(0, 2, 'parrot', 'anemone', 'blue');

// myFish is ["parrot", "anemone", "blue", "trumpet", "sturgeon"] 
// removed is ["angel", "clown"]

Remove 2 elements from index 2Section

var myFish = ['parrot', 'anemone', 'blue', 'trumpet', 'sturgeon'];
var removed = myFish.splice(myFish.length - 3, 2);

// myFish is ["parrot", "anemone", "sturgeon"] 
// removed is ["blue", "trumpet"]

Remove 1 element from index -2Section

var myFish = ['angel', 'clown', 'mandarin', 'sturgeon'];
var removed = myFish.splice(-2, 1);

// myFish is ["angel", "clown", "sturgeon"] 
// removed is ["mandarin"]

Remove all elements after index 2 (incl.)Section

var myFish = ['angel', 'clown', 'mandarin', 'sturgeon'];
var removed = myFish.splice(2);

// myFish is ["angel", "clown"] 
// removed is ["mandarin", "sturgeon"]

Monday, November 12, 2018

Simplify your JavaScript – Use .map(), .reduce(), and .filter()

If you’re starting in JavaScript, maybe you haven’t heard of .map().reduce(), and .filter(). For me, it took a while as I had to support Internet Explorer 8 until a couple years ago. But if you don’t need to be compatible with this very old browser, you have to become familiar with those methods.
Take note that this article most likely applies to whatever other programming language you might be using, as these are concepts that exist in many other languages.

.map()

Let me explain how it works with a simple example. Say you have received an array containing multiple objects – each one representing a person. The thing you really need in the end, though, is an array containing only the id of each person.
// What you have
var officers = [
  { id: 20, name: 'Captain Piett' },
  { id: 24, name: 'General Veers' },
  { id: 56, name: 'Admiral Ozzel' },
  { id: 88, name: 'Commander Jerjerrod' }
];
// What you need
[20, 24, 56, 88]
There are multiple ways to achieve this. You might want to do it by creating an empty array, then using .forEach().for(...of), or a simple .for() to meet your goal.
Let’s compare!
Using .forEach():
var officersIds = [];
officers.forEach(function (officer) {
  officersIds.push(officer.id);
});
Notice how you have to create an empty array beforehand? Let’s see what it looks like when using .map():
var officersIds = officers.map(function (officer) {
  return officer.id
});
We can even be more concise with arrow functions (requires ES6 support, Babel or TypeScript)
const officersIds = officers.map(officer => officer.id);
So how does .map() work? Basically is takes 2 arguments, a callback and an optional context (will be considered as this in the callback) which I did not use in the previous example. The callback runs for each value in the arrayand returns each new value in the resulting array.
Keep in mind that the resulting array will always be the same length as the original array.

.reduce()

Just like .map().reduce() also runs a callback for each element of an array. What’s different here is that reduce passes the result of this callback (the accumulator) from one array element to the other.
The accumulator can be pretty much anything (integer, string, object, etc.) and must be instantiated or passed when calling .reduce().
Time for an example! Say you have an array with these pilots and their respective years of experience:
var pilots = [
  {
    id: 10,
    name: "Poe Dameron",
    years: 14,
  },
  {
    id: 2,
    name: "Temmin 'Snap' Wexley",
    years: 30,
  },
  {
    id: 41,
    name: "Tallissan Lintra",
    years: 16,
  },
  {
    id: 99,
    name: "Ello Asty",
    years: 22,
  }
];
We need to know the total years of experience of all of them. With .reduce(), it’s pretty straightforward:
var totalYears = pilots.reduce(function (accumulator, pilot) {
  return accumulator + pilot.years;
}, 0);
Notice that I’ve set the starting value as 0. I could have also used an existing variable if necessary. After running the callback for each element of the array, reduce will return the final value of our accumulator (in our case: 82).
Let’s see how this can be shortened with ES6’s arrow functions:
const totalYears = pilots.reduce((acc, pilot) => acc + pilot.years, 0);
Now let’s say I want to find which pilot is the most experienced one. For that, I can use reduce as well:
var mostExpPilot = pilots.reduce(function (oldest, pilot) {
  return (oldest.years || 0) > pilot.years ? oldest : pilot;
}, {});
I named my accumulator oldest. My callback compares the accumulator to each pilot. If a pilot has more years of experience than oldest, then that pilot becomes the new oldest so that’s the one I return.
As you can see, using .reduce() is an easy way to generate a single value or object from an array.

.filter()

What if you have an array, but only want some of the elements in it? That’s where .filter() comes in!
Here’s our data:
var pilots = [
  {
    id: 2,
    name: "Wedge Antilles",
    faction: "Rebels",
  },
  {
    id: 8,
    name: "Ciena Ree",
    faction: "Empire",
  },
  {
    id: 40,
    name: "Iden Versio",
    faction: "Empire",
  },
  {
    id: 66,
    name: "Thane Kyrell",
    faction: "Rebels",
  }
];
Say we want two arrays now: one for rebel pilots, the other one for imperials. With .filter() it couldn’t be easier!
var rebels = pilots.filter(function (pilot) {
  return pilot.faction === "Rebels";
});
var empire = pilots.filter(function (pilot) {
  return pilot.faction === "Empire";
});
That’s it! And it’s even shorter with arrow functions:
const rebels = pilots.filter(pilot => pilot.faction === "Rebels");
const empire = pilots.filter(pilot => pilot.faction === "Empire");
Basically, if the callback function returns true, the current element will be in the resulting array. If it returns false, it won’t be.

Combining .map(), .reduce(), and .filter()

Since all three are called on arrays and since .map() and .filter() both return arrays, we can easily chain our calls.
Let’s check out another example. Here’s our data:
var personnel = [
  {
    id: 5,
    name: "Luke Skywalker",
    pilotingScore: 98,
    shootingScore: 56,
    isForceUser: true,
  },
  {
    id: 82,
    name: "Sabine Wren",
    pilotingScore: 73,
    shootingScore: 99,
    isForceUser: false,
  },
  {
    id: 22,
    name: "Zeb Orellios",
    pilotingScore: 20,
    shootingScore: 59,
    isForceUser: false,
  },
  {
    id: 15,
    name: "Ezra Bridger",
    pilotingScore: 43,
    shootingScore: 67,
    isForceUser: true,
  },
  {
    id: 11,
    name: "Caleb Dume",
    pilotingScore: 71,
    shootingScore: 85,
    isForceUser: true,
  },
];
Our objective: get the total score of force users only. Let’s do it step by step!
First, we need to filter out the personnel who can’t use the force:
var jediPersonnel = personnel.filter(function (person) {
  return person.isForceUser;
});
// Result: [{...}, {...}, {...}] (Luke, Ezra and Caleb)
With that we have 3 elements left in our resulting array. We now need to create an array containing the total score of each Jedi.
var jediScores = jediPersonnel.map(function (jedi) {
  return jedi.pilotingScore + jedi.shootingScore;
});
// Result: [154, 110, 156]
And let’s use reduce to get the total:
var totalJediScore = jediScores.reduce(function (acc, score) {
  return acc + score;
}, 0);
// Result: 420
And now here’s the fun part… we can chain all of this to get what we want in a single line:
var totalJediScore = personnel
  .filter(function (person) {
    return person.isForceUser;
  })
  .map(function (jedi) {
    return jedi.pilotingScore + jedi.shootingScore;
  })
  .reduce(function (acc, score) {
    return acc + score;
  }, 0);
And look how pretty it is with arrow functions:
const totalJediScore = personnel
  .filter(person => person.isForceUser)
  .map(jedi => jedi.pilotingScore + jedi.shootingScore)
  .reduce((acc, score) => acc + score, 0);
Boom! 💥
Note: In my previous example, .map() and .filter() weren’t even necessary. We could easily achieve the same result with only .reduce(). I left them in there for the sake of this example. Can you guess how we could only keep .reduce() and get the same result with one line of code? See the solution on CodePen

Why not use .forEach()?

I used to use for loops everywhere instead of .map().reduce(), and .filter(). But a couple of years ago I started working a lot more with data that came from an API. That’s where I began to see the advantages of leaving .forEach behind.

Formatting

Say you need to display a list of people, with their name and job title.
var data = [
  {
    name: "Jan Dodonna",
    title: "General",
  },
  {
    name: "Gial Ackbar",
    title: "Admiral",
  },
]
The API gives you the above data, but you only need the title and the last name of each person… You need to format the data. However, your app also needs to have a single view for each person, so you must write a data formatting function that both works in a list view and in a single view.
That means you can’t have the .forEach loop inside of your formatting function, or else you would have to wrap your single element in an array before you pass it to the function just to make it work, like so:
var result = formatElement([element])[0];
// Yeah... that's not right at all
So your loop has to wrap the call of the function, like this:
data.forEach(function (element) {
  var formatted = formatElement(element);
  // But what then....
});
But .forEach() doesn’t return anything. It can’t. That means you have to push the results inside a predetermined array.
var results = [];
data.forEach(function (element) {
  var formatted = formatElement(element);
  results.push(formatted);
});
As a result, you have 2 functions: your formatElement() function and your function that pushes the results in your array.
Why have 2 functions when you can have just one?
var results = data.map(formatElement);

Testing is easier

If you write unit tests for your code, you’ll find it simpler to test the functions you call with .map().reduce(), or .filter().
All you have to do is provide inbound data for the function and expect a result to come out. Basically “what comes out if this is passed?”. Less manipulation, less beforeEach()s and afterEach()s. It’s straightforward, simple testing.

Try it!

Try to replace some of your for loops with .map().reduce().filter()where it seems to fit. I guarantee your code will be way less clunky and much easier to read.
Keep coding!