Buy Access to Course
17.

Array, Set and ES2016

Share this awesome video!

|

Keep on Learning!

The Map object is perfect for maps, or associative arrays as we call them in the PHP biz. But what about true, indexed arrays? Well actually, JavaScript has always had a great way to handle these - it's not new! It's the Array object.

Well, the Array object isn't new, but it does have a new trick. Let's check out an example: when the page loads, we call loadRepLogs():

235 lines | web/assets/js/RepLogApp.js
// ... lines 1 - 2
(function(window, $, Routing, swal) {
// ... lines 4 - 6
class RepLogApp {
constructor($wrapper) {
// ... lines 9 - 11
this.loadRepLogs();
// ... lines 13 - 28
}
// ... lines 30 - 39
loadRepLogs() {
$.ajax({
url: Routing.generate('rep_log_list'),
}).then(data => {
for (let repLog of data.items) {
this._addRow(repLog);
}
})
}
// ... lines 49 - 170
_addRow(repLog) {
// ... lines 172 - 175
const html = rowTemplate(repLog);
this.$wrapper.find('tbody').append($.parseHTML(html));
this.updateTotalWeightLifted();
}
}
// ... lines 182 - 233
})(window, jQuery, Routing, swal);

This fetches an array of repLog data via AJAX and then calls _addRow() on each to add the <tr> elements to the table.

But once we add the table rows... we don't actually store those repLog objects anywhere. Yep, we use them to build the page... then say: Adios!

Now, I do want to start storing this data on my object, and you'll see why in a minute. Up in the constructor, create a repLogs property set to new Array():

239 lines | web/assets/js/RepLogApp.js
// ... lines 1 - 2
(function(window, $, Routing, swal) {
// ... lines 4 - 6
class RepLogApp {
constructor($wrapper) {
// ... line 9
this.repLogs = new Array();
// ... lines 11 - 30
}
// ... lines 32 - 184
}
// ... lines 186 - 237
})(window, jQuery, Routing, swal);

If you've never seen that Array object before... there's a reason - stay tuned! Then, down in _addRow(), say this.repLogs() - which is the Array object - this.repLogs.push(repLog):

239 lines | web/assets/js/RepLogApp.js
// ... lines 1 - 2
(function(window, $, Routing, swal) {
// ... lines 4 - 6
class RepLogApp {
// ... lines 8 - 173
_addRow(repLog) {
this.repLogs.push(repLog);
// ... lines 176 - 183
}
}
// ... lines 186 - 237
})(window, jQuery, Routing, swal);

Back up in loadRepLogs(), after the for loop, let's see how this looks: console.log(this.repLogs). Oh, and let's also use one of its helper methods: this.repLogs.includes(data.items[0]):

239 lines | web/assets/js/RepLogApp.js
// ... lines 1 - 2
(function(window, $, Routing, swal) {
// ... lines 4 - 6
class RepLogApp {
// ... lines 8 - 41
loadRepLogs() {
$.ajax({
// ... line 44
}).then(data => {
// ... lines 46 - 48
console.log(this.repLogs, this.repLogs.includes(data.items[0]));
})
}
// ... lines 52 - 184
}
// ... lines 186 - 237
})(window, jQuery, Routing, swal);

Obviously, this item should have been added to the Array!

Refresh! Yea! We see the fancy Array and the word true. Awesome!

But hold on! The Array object may not be new, but the includes() function is new. In fact, it's really new - it wasn't added in ES2015, it was added in ES2016! ES2015 came with a ton of new features. And now, new ECMAScript releases happen yearly, but with many fewer new things. The Array' includes() function is one of those few things in ES2016. Cool!

Oh, and by the way, you don't typically say new Array()... and PHPStorm is yelling at us! In the wild, you just use []:

239 lines | web/assets/js/RepLogApp.js
// ... lines 1 - 2
(function(window, $, Routing, swal) {
// ... lines 4 - 6
class RepLogApp {
constructor($wrapper) {
// ... line 9
this.repLogs = [];
// ... lines 11 - 30
}
// ... lines 32 - 218
}
// ... lines 220 - 237
})(window, jQuery, Routing, swal);

That's right, when you create an array in JavaScript, it's actually this Array object.

Calculating the Total Weight

But... why are we keeping track of the repLogs? Because now, we can more easily calculate the total weight. Before, we passed the Helper object the $wrapper element so that it could find all the tr elements and read the weight from them. We can simplify this! Instead, pass it our Array: this.repLogs:

238 lines | web/assets/js/RepLogApp.js
// ... lines 1 - 2
(function(window, $, Routing, swal) {
// ... lines 4 - 6
class RepLogApp {
constructor($wrapper) {
// ... line 9
this.repLogs = [];
HelperInstances.set(this, new Helper(this.repLogs));
// ... lines 13 - 30
}
// ... lines 32 - 183
}
// ... lines 185 - 236
})(window, jQuery, Routing, swal);

At the bottom of this file, change the constructor() for Helper to have a repLogs argument. Set that on a repLogs property:

238 lines | web/assets/js/RepLogApp.js
// ... lines 1 - 2
(function(window, $, Routing, swal) {
// ... lines 4 - 185
/**
* A "private" object
*/
class Helper {
constructor(repLogs) {
this.repLogs = repLogs;
}
// ... lines 193 - 217
}
// ... lines 219 - 236
})(window, jQuery, Routing, swal);

Below in calculateTotalWeight(), instead of using the $wrapper to find all the tr elements, just pass this.repLogs to the static function. Inside of that, update the argument to repLogs:

238 lines | web/assets/js/RepLogApp.js
// ... lines 1 - 2
(function(window, $, Routing, swal) {
// ... lines 4 - 185
/**
* A "private" object
*/
class Helper {
// ... lines 190 - 193
calculateTotalWeight() {
return Helper._calculateWeights(
this.repLogs
);
}
// ... lines 199 - 209
static _calculateWeights(repLogs) {
// ... lines 211 - 216
}
}
// ... lines 219 - 236
})(window, jQuery, Routing, swal);

Previously, _calculateWeights() would loop over the $elements and read the data-weight attribute on each. Now, loop over repLog of repLogs. Inside, set totalWeight += repLog.totalWeightLifted:

238 lines | web/assets/js/RepLogApp.js
// ... lines 1 - 2
(function(window, $, Routing, swal) {
// ... lines 4 - 185
/**
* A "private" object
*/
class Helper {
// ... lines 190 - 209
static _calculateWeights(repLogs) {
let totalWeight = 0;
for (let repLog of repLogs) {
totalWeight += repLog.totalWeightLifted;
}
return totalWeight;
}
}
// ... lines 219 - 236
})(window, jQuery, Routing, swal);

It's nice to calculate the total weight from our source data, rather than reading it from somewhere on the DOM.

Okay! Try that out! The table still loads... and the total still prints!

Tip

Actually, we made a mistake! When you delete a rep log, the total weight will no longer update! That's because we now need to remove the deleted repLog from the this.repLogs array.

No problem! The fix is kinda cool: it involves adding a reference to the $row element: the index on the this.repLogs array that the row corresponds to. This follows a pattern that's somewhat similar to what you'll see in ReactJS.

249 lines | web/assets/js/RepLogApp.js
// ... lines 1 - 2
(function(window, $, Routing, swal) {
// ... lines 4 - 6
class RepLogApp {
// ... lines 8 - 73
_deleteRepLog($link) {
// ... lines 75 - 83
return $.ajax({
// ... lines 85 - 86
}).then(() => {
$row.fadeOut('normal', () => {
// we need to remove the repLog from this.repLogs
// the "key" is the index to this repLog on this.repLogs
this.repLogs.splice(
$row.data('key'),
1
);
$row.remove();
this.updateTotalWeightLifted();
});
})
}
// ... lines 102 - 180
_addRow(repLog) {
this.repLogs.push(repLog);
// ... lines 183 - 186
const html = rowTemplate(repLog);
const $row = $($.parseHTML(html));
// store the repLogs index
$row.data('key', this.repLogs.length - 1);
this.$wrapper.find('tbody').append($row);
this.updateTotalWeightLifted();
}
}
// ... lines 196 - 247
})(window, jQuery, Routing, swal);

Introducing Set

But, ES2015 added one more new object that's related to all of this: Set. It's a lot like Array: it holds items... but with one important difference.

Open up play.js and set foods to an array:

7 lines | play.js
let foods = [];
// ... lines 2 - 7

Let's add gelato to the array and tortas. Clear everything else out:

7 lines | play.js
let foods = [];
foods.push('gelato');
foods.push('tortas');
// ... lines 4 - 7

And ya know what? Gelato is so good, we should add it again. At the bottom, log foods:

7 lines | play.js
let foods = [];
foods.push('gelato');
foods.push('tortas');
foods.push('gelato');
console.log(foods);

When you run the script, there are no surprises: gelato, tortas, gelato.

But now, change the array to be a new Set(). To add items to a Set, you'll use add() instead of push() - but it's the same idea:

7 lines | play.js
let foods = new Set();
foods.add('gelato');
foods.add('tortas');
foods.add('gelato');
console.log(foods);

Try the script now.

Woh! Just two items! That's the key difference between Array and Set: Set should be used when you need a unique collection of items. It automatically makes sure that duplicates aren't added.

Oh, and there is also a WeakSet, which has the same super powers of WeakMap - all that garbage collection stuff. But, I haven't seen any decent use-case for it. Just use Set... or Array if values don't need to be unique.