The next step in your Javascript journey is to learn the front-end component framework React.
But before we get to React, there are a few more Javascript features you need to learn. Knowing these features will help you better understand the example code you’ll encounter while learning React (or any other component library).
This module is a collection of modern Javascript tips and tricks.
Upon completion of this module, you should be able to:
for of and ForEach loops to iterate over Javascript collections.Functions are one of the key building blocks of Javascript applications.
A standard function is defined like this:
function snail() {
console.log("Hello Snail! π");
}
snail(); // And called by name.
Unnamed function are called anonymous*:
setTimeout(function(){ console.log("Hello Mail! π¨"; }, 500);
Anonymous functions can be assigned to variables:
let nail = function() {
console.log("Hello Nail! π
");
}
nail(); // And called liked named functions.
*Anonymous, derived from the Greek word αΌΞ½ΟΞ½Ο ΞΌΞ―Ξ±, anonymia, meaning “without a name” or “namelessness”.
In Javascript functions can be passed as parameters to other functions. We call these “callback functions” as they aren’t executed immediately.
Example: setTimeout takes a callback function and a time in milliseconds as arguments. The callback function is executed after the provided time elapses.
function whale() {
console.log("Hello Whale! π");
}
let tail = function() {
console.log("Hello Tail! π¦¨");
}
setTimeout(whale, 500); // Named function as callback.
setTimeout(tail, 1500); // Variable-assigned anonymous function as callback.
// Inline anonymous callback functions are also handy:
setTimeout(function(){ console.log("Hello Sail! β΅"); }, 2000);
βοΈ Callbacks can be named functions, anonymous functions, or anonymous functions assigned to variables.
Another way to define anonymous function is with the “arrow operator” =>:
let gale = () => { console.log("Hello Gale! π¬οΈ"); }
gale(); // Called by variable name.
Arrow functions can also be defined with parameters:
let rail = (times) => {
for(let i = 0; i < times; i++) {
console.log("Hello Rail! π");
}
}
rail(5);
Arrow functions that contain a single statement can be written without curly braces.
These are called “implicit* return” functions.
let implicit = (word, emoji) => `Hello ${word}! ${emoji}`;
Implicit return functions automatically return the value of the function statement.
// Here, the implicit function returns a string which is then logged:
console.log(implicit('Trail', 'π '));
// Hello Trail! π
The above implicit function is equivalent to:
let explicit = (word, emoji) => {
return `Hello ${word} ${emoji}`; // Explicit Return Statement
}
console.log(explicit('trail', 'π '));
*Implicit means something implied but not stated directly. Explicit means something made clear and stated plainly.
One line, one argument, arrow functions can be written without parentheses or curly braces:
let unadorned = emoji => `Hello Scale! ${emoji}`;
console.log(unadorned('βοΈ'));
// Hello Scale! βοΈ
Unadorned arrows are also implicit return functions.
β³ Wait for it… We’ll use this style a lot in the coming sections on array helpers.
Arrow functions are a nice way to define clear and concise callback functions.
Compare the readability of this regular callback:
setTimeout(function() { console.log("Hello Veil! π°"); }, 225);
With the arrow version:
setTimeout(() => console.log("Hello Veil! π°"), 225);
There’s a lot less “punctuation noise”, which makes the code easier to read for others.
Beyond the standard for loop, there are two other popular ways to loop through Arrays in Javascript.
The for of loop assigns each element in turn a loop variable:
let fruitBasket = ['π', 'π', 'π', 'π₯'];
// for-of: Concise loop when you don't need the index value.
for(const fruit of fruitBasket) {
console.log(fruit);
}
The fruit variable is scoped to the curly-braced code block.
βοΈ Best Practice: Define your loop variables as const to prevent accidental modification of array elements.
The array forEach method passes each element in turn as an argument to a provided callback:
// Print every element using an arrow function:
fruitBasket.forEach(fruit => console.log(fruit));
// With forEach you can also get access to the index:
fruitBasket.forEach((fruit, i) => console.log(`${i}: ${fruit}`));
// 0: π
// 1: π
// 2: π
// 3: π₯
Longer forEach callbacks are also possible.
Let’s build a fruit pyramid:
fruitBasket.forEach((fruit, i) => {
let length = fruitBasket.length;
let row = ' '.repeat(length-i) + fruit.repeat(i+1);
console.log(row);
});
// π
// ππ
// πππ
// π₯π₯π₯π₯
Javascript has lots of built in methods that make working with arrays a joy.
Imagine we have the following array of objects:
const animals = [
{ name: 'Monkey', emoji: 'π', legs: 2, mana: 45, count: 3 },
{ name: 'Dog', emoji: 'π', legs: 4, mana: 89, count: 2 },
{ name: 'Cow', emoji: 'π', legs: 4, mana: 3, count: 5 },
{ name: 'Gorilla', emoji: 'π¦', legs: 2, mana: 56, count: 2 },
{ name: 'Deer', emoji: 'π¦', legs: 4, mana: 97, count: 5 },
{ name: 'Kangaroo', emoji: 'π¦', legs: 2, mana: 4387, count: 1 },
{ name: 'Turkey', emoji: 'π¦', legs: 2, mana: 17, count: 7}
];
Let’s use find, map, filter, join, includes, every, some, and reduce to make sense of this data.
Remember, we’ll be making heavy use of implicit-return arrow functions.
The array map method creates a new array of the same size.
The provided callback is used to transform each element in the original array to the corresponding element in the new array.
map an array of animal emojis based on each animal’s count:
const emojiZoo = animals.map(animal => animal.emoji.repeat(animal.count));
console.log(emojiZoo); // Display the new array of strings.
// [ "πππ", "ππ", "πππππ", "π¦π¦", "π¦π¦π¦π¦π¦", "π¦", "π¦π¦π¦π¦π¦π¦π¦" ]
The filter method returns a new array of elements selected from the original array.
Each element is tested using the callback function. The new array contains elements where the callback returned true.
filter for only four legged animals:
const fourLegsGood = animals.filter(animal => animal.legs == 4);
console.log(fourLegsGood); // Two legs baaaaaaaaad-π
// [ { name: 'Dog', emoji: 'π', legs: 4, mana: 89, count: 2 },
// { name: 'Cow', emoji: 'π', legs: 4, mana: 3, count: 5 },
// { name: 'Deer', emoji: 'π¦', legs: 4, mana: 97, count: 5 } ]
Sometimes you want to find an array element based on its property values. The callback you provide must return a Boolean value.
find only the cow:
const cow = animals.find(animal => animal.name == 'Cow' );
console.log(cow.emoji);
// π
If multiple elements match, only the first is returned.
Sometimes you wish to know if your array contains a specific object.
Determine if the array includes a Gorilla:
// Map to names and then test inclusion of 'Gorilla'.
if (animals.map(a => a.name).includes('Gorilla')) {
console.log("There are Gorillas in the mist.")
}
// There are Gorillas in the mist.
Calling a method on the result of a previous method is called “method chaining”. In the above example map returns an array of strings and then includes looks through that array for a specific string.
Sometime you wish to know if a specific Boolean condition occurs in some or every one of your array elements.
Are some animals ready for the rap battle?
const manaNeededToBattle = 17;
const someAreReady = animals.some(animal => animal.mana >= manaNeededToBattle); // true
Does every animal have enough mana for the rap battle?
if (animals.every(animal => animal.mana >= manaNeededToBattle)) {
console.log("We can battle."); // Will not execute as Cow's mana is not sufficient.
}
Sometimes we want to reduce an array down to a single value.
reduce the animal array down to a sum of all animal counts:
// Sum animal counts into 'total' with a starting sum of zero.
const totalCount = animals.reduce((total, animal) => total + animal.count, 0);
console.log(`In total there are ${totalCount} animals.`);
// In total there are 25 animals.
The reduce method takes a callback and a starting value. As it iterates through the array, the return value of the callback is assigned to an βaccumulatorβ variable.
const totalCount = animals.reduce((total, animal) => total + animal.count, 0);
Here, the callbackβs first parameter total is the accumulator. It stays in scope across the entire array.
The callbackβs second parameter animal is assigned each array element in turn.
In this example the accumulator’s starting value is set to zero.
Sometimes we want to join all array element together into a string.
First map to names and then join into a guest list:
const guestList = animals.map(animal => animal.name).join(', ');
console.log(guestList);
// Monkey, Dog, Cow, Gorilla, Deer, Kangaroo, Turkey
The join method takes a string value to use as “glue” between each element. You can use a empty string to go “glueless”.
Destructuring is a way of teasing apart collections and assigning the component values to variables.
Instead of indexed array assignment:
const hearts = ['π€','β€οΈ', 'π']
const black = hearts[0];
const red = hearts[1];
const yellow = hearts[2];
console.log(yellow) // π
We can write:
const hearts = ['π€','β€οΈ', 'π']
const [black, red, yellow] = hearts;
console.log(yellow) // π
Let’s say you a function named weather that returns an array of three temperatures.
const temperatures = weather(); // [10, 15, 22]
And you know that the forecasted low, high, and average temperature appear in that order in the array.
You can destructure the returned array into the variables low, high, and average:
const [low, high, average] = weather(); // [10, 15, 22]
console.log(`Average: ${average} Low: ${low} High: ${high} `);
// Average: 22 Low: 10 High: 15
We can also promote object keys to variables using destructuring:
const adventures = { mountain: 'β°οΈ', desert: 'ποΈ' };
const { mountain, desert } = adventures;
console.log(mountain); // β°οΈ
console.log(desert); // ποΈ
We can also name the variables sometime different from the object keys:
const { mountain: everest, desert: sahara } = adventures;
console.log(sahara); // ποΈ
console.log(everest); // β°οΈ
Destructuring can also be used to pluck data out of nested data structures, like an array of objects:
const animalFriends = [
{ name: 'Monkey', emoji: 'π'},
{ name: 'Dog', emoji: 'π'},
{ name: 'Cow', emoji: 'π'}
];
// - Skip the zeroth position in the array with a comma.
// - Grab the emoji out of the object at first position.
// - Skip the object in the final position of the array.
const [ , {emoji: dogEmoji} ] = animalsFriends;
console.log(dogEmoji); // π
This example from the MDN shows the destructuring of the kind of object you might get back from an API:
// Let's pretend this data came from an API.
const apiResponse = {
title: 'JS Environment',
translations: [
{
locale: 'de - Germany',
last_edit: '2014-04-14T08:43:37',
title: 'JS Umgebung'
}
]
};
// Pluck the English and German title from the data.
let {
title: englishTitle,
translations: [
{
title: localeTitle,
},
],
} = apiResponse;
console.log(englishTitle); // "JS Environment"
console.log(localeTitle); // "JS Umgebung"
Functions can be added to objects too:
const user = {
username: 'stungeye',
age: 42,
printInfo: function() { console.log(`${this.username} is ${this.age}.`); }
}
user.printInfo();
// "stungeye is 42."
Functions attached to objects are called methods.
β οΈ Warning: Arrow functions don’t bind to the object’s this so they don’t work well as object properties:
const brokenUser = {
username: 'stungeye',
age: 42,
printInfo: () => { console.log(`${this.username} is ${this.age}.`); }
}
user.printInfo(); // "undefined is undefined."
Object properties can be access by string index or by “dot” access.
const games = { soccer: 'β½', baseball: `βΎ` };
// Bracket notation using a string index:
console.log(games['soccer']);
// Dot notation using a period:
console.log(games.baseball);
// β½
// βΎ
We can use destructuring along with the Object.entries method to loop over the key/value pairs in an object.
for(const [game, ball] of Object.entries(games)) {
console.log(`We use a ${ball} to play ${game}.`);
}
// We use a β½ to play soccer.
// We use a βΎ to play baseball.
This works because Object.entries(games) returns an array of arrays:
[[ "soccer", "β½" ], [ "baseball", "βΎ" ]]
We sometimes need to “spread out” the contents of an array or a string into the arguments of a function call.
We can do this with the ... spread syntax.
For example, the Math.max method takes any number of arguments.
console.log(Math.max(42, 56, 102, 12, 15)); // 102
But what if the numbers are in an array?
const numbers = [42, 56, 102, 12, 15];
console.log(Math.max(...numbers)); // 102
The ... spread syntax can also be used to “spread out” an array within a new array literal.
Array concatenation* without concat():
const litanyStart = ['Fear', 'is', 'the'];
const litanyEnd = ['mind', 'killer'];
const litanyFull = [...litanyStart, ...litanyEnd];
console.log(litanyFull);
// [ 'Fear', 'is', 'the', 'mind', 'killer' ]
Or even:
const middle = ['is', 'the'];
const litany = ['Fear', ...middle, 'mind', 'killer'];
console.log(litany);
// [ 'Fear', 'is', 'the', 'mind', 'killer' ]
*In the coding world, concatentation means gluing collections together.
We can also involve the ... spread syntax in our destructuring:
const sentence = ['this', 'is', 'the', 'house', 'that', 'π¦', 'built'];
const [first, second, ...theRest] = sentence;
console.log(first);
console.log(second);
console.log(theRest);
// this
// is
// [ 'the', 'house", "that", "π¦", "built" ]
The spread syntax can also be used to merge objects.
const userSome = { fullName: 'Wally Glutton', age: 42 };
const moarUser = { username: 'stungeye', friend: 'Daisy' };
const userFull = {...userSome, ...moarUser};
// { fullName: "Wally Glutton", age: 42, username: "stungeye", friend: "Daisy" }
If a key appears in more than one of the objects it’s the last pair that gets to set the value.
let book = { name: 'Anathem', quote: 'Boredom is a mask frustration wears.' };
// Book already contains a 'quote' key but not an 'author' key.
book = { ...book, author: 'Neal Stephenson', quote: 'Topology is destiny.' };
// { name: "Anathem", quote: "Topology is destiny.", author: "Neal Stephenson" }
Rest parameters use the same ... syntax as spread, but here we use it to create functions that can take any number of arguments.
function sayGoodnight(...names) {
const nameList = names.join(', ');
console.log(`Goodnight ${nameList}!`);
}
sayGoodnight('Wally');
sayGoodnight('Wally', 'Daisy');
sayGoodnight('Wally', 'Daisy', 'Sammy', 'June');
// Goodnight Wally!
// Goodnight Wally, Daisy!
// Goodnight Wally, Daisy, Sammy, June!
The Javascript language is standardized and improved by way of the ECMAScript specification.
Starting with ECMASCript 6 (ES6 in 2015) a new version of the specification has been published yearly.
Additions to the language don’t end up in the browsers immediately, but we can check caniuse.com to see when new features are widely supported.
Along with improvements to the language syntax there are also new Web APIs being added to the browser.
A great way to keep up with the Javascript language is to listen to web development podcasts like Syntax.fm and Javascript Jabber, or subscribe to email roundups like Javascript Weekly and Frontend Focus.