DEV Community

Cover image for Modern JavaScript, 10 things you should be using, starting today
Chris Noring for Microsoft Azure

Posted on • Updated on

Modern JavaScript, 10 things you should be using, starting today

Follow me on Twitter, happy to take your suggestions on topics or improvements /Chris

You may be completely new to JavaScript or you may have only used it sporadically over the years. One thing is clear though - a lot has changed and there are some features YOU should be using. This article describes the features I think you should be using on a daily basis, if you are serious about JavaScript

Here is the video version:

Subscribe

Resources

These are my favorite resources for anything ES6+:

-1- Spread operator

This is denoted as a ... before an object or an array and accomplishes just what the name is saying, it's turning something from being a structure into a comma-separated list. Let's demonstrate this:

Spread array

let firstHalf = [ 'one', 'two'];
let secondHalf = ['three', 'four', ...firstHalf];
Enter fullscreen mode Exit fullscreen mode

This is such a nice and compact way of writing it. Doing the same without this would mean doing something like this:

NO Array spread

let firstHalf = [ 'one', 'two'];


let secondHalf = ['three', 'four'];
for(var i=0, i <firstHalf.length; i++ ) {
  secondHalf.push(firstHalf[i]);
}


Enter fullscreen mode Exit fullscreen mode

This can also be used on objects as a way of merging their properties:

Spread object

const hero = {
  name: 'Xena - Warrior Princess',
  realName: 'Lucy Lawless'
}


const heroWithSword = {
 ...hero,
 weapon: 'sword'
}

Enter fullscreen mode Exit fullscreen mode

Doing this the hard way would be us looping through all the properties on the object:

NO Object spread

let keys = Object.keys(hero);
let obj = {};

for(var i=0; i< keys.length; i++) {
   obj[keys[i]] = keys[props[i]];
}
Enter fullscreen mode Exit fullscreen mode

To be fair, there's also Object.assign(), that looks like so:

const heroWithSword = Object.assign({}, hero, {weapon:"sword"})
Enter fullscreen mode Exit fullscreen mode

I would still argue though that this is an eaven easier read:

const heroWithSword = {
 ...hero,
 weapon: 'sword'
}
Enter fullscreen mode Exit fullscreen mode

-2- Rest parameter

Rest parameters are about collecting the remaining parameters into an array. JavaScript has the ability to be flexible on the number of input parameters you give it. Normally there is an arguments variable that collects these. Let's look at what we mean:

function add(first, second, ...remaining) {
  return first + second;
}
Enter fullscreen mode Exit fullscreen mode

Now, this above only summarize the parameters first and second. Which means invoking it with add(1,2) or add(1,2,3, 4) would yield the same results. To fix this we would type:

function add(first, second, ...remaining) {
  return first + second + remaining.reduce((acc, curr) => acc + curr, 0);
}
Enter fullscreen mode Exit fullscreen mode

The above means we are fixing the problem and use all input parameters.

As stated earlier using the rest parameter, i.e adding a preceding ... as a way to collect the remaining parameters, is a way for us to name them and make it more explicit that we want to work with them. arguments have been around since at least ES5 but is less known I think.

-3- String interpolation

Have you ever seen a statement like this?

class Product {
 constructor(name, description, price) {
   this.name = name;
   this.description = description;
   this.price = price;
 }

 getDescription() {
   return " Full description \n" + 
   " name: " + this.name + 
   " description: " + this.description

 }
}
Enter fullscreen mode Exit fullscreen mode

I'm of course talking about the getDescription() method, a long, multiline, hard-to-read statement. This is a reality in most programming languages. There is also string interpolation available in some languages and JavaScript is no different, fortunately. We can turn our getDescription() method into the following:

getDescription() {
   return `Full description \n: 
   name: ${this.name}
   description ${this.description}
   `;

 }
Enter fullscreen mode Exit fullscreen mode

So double backticks ` is what we use to define a multi-line string. We also use ${} to interpolate. There your world is hopefully a lot better now :)

-4- Shorthand properties

You might be using this one without already knowing about it. In ES5 you had to write the following:

function createCoord(x, y) {
  return {
    x: x,
    y: y
  }
}
Enter fullscreen mode Exit fullscreen mode

In ES6 and onwards you can omit the what's to the right of : if it has the same name, like so:

function createCoord(x, y) {
  return {
    x,
    y
  }
}
Enter fullscreen mode Exit fullscreen mode

Looks less cluttered right?

-5- Method properties

This is how you define properties that point to methods in an object. Consider the following ES5 example:

const math = {
  add: function(a,b) { return a + b; },
  sub: function(a,b) { return a - b; }, 
  multiply: function(a,b) { return a * b; }
}
Enter fullscreen mode Exit fullscreen mode

You don't actually need to have the whole add: business from ES6 and forwards. You can just type it like so:

const math = {
  add(a,b) { return a + b; },
  sub(a,b) { return a - b; },
  multiply(a,b) { return a * b; }
}
Enter fullscreen mode Exit fullscreen mode

-6- Destructuring

Destructuring is about your own mental sanity as a developer.

Object destructuring

Consider the following code:

function handle(req, res) {
 const name = req.body.name;
 const description = req.body.description;
 const url = req.url;

 log('url endpoint', url);

 // lots of logic happening
 dbService.createPerson( name, description )
}
Enter fullscreen mode Exit fullscreen mode

The code above is not perfect, in any way, but it does represent a case where we want to dig out data from an object at different levels. What's the problem you ask? Well, what if I didn't have to declare all those variables and save a few keystrokes? You can do just that:

function handle(req, res) {
 const { body: { name, description }, url }, = req;

 log('url endpoint', url);

 // lots of logic happening
 dbService.createPerson( name, description )
Enter fullscreen mode Exit fullscreen mode

Above you see how three rows become one.

Array destructuring

This is not limited to objects. It can be done on arrays as well. Consider the following code:

const array = [1,2,3,4,5,6];

const a = array[0];

const c = array[2];
Enter fullscreen mode Exit fullscreen mode

This can be done in a much more elegant way, like so:

const array = [1,2,3,4,5,6];
const [a, ,c, ...remaining] = arr;

// remaining = [4,5,6]
Enter fullscreen mode Exit fullscreen mode

We can just break out the values from the array by the above pattern matching. If we want to skip something we type , , and as a bonus, I threw in a REST statement to grab the remaining items.

Parameter matching

We can also do this on a function and its parameters. It has become the de-facto standard to collect all parameters in an object when you have more than 2-3 parameters in a function so you get a function looking like this:

function doSomething(config) {
  if(config.a) { ... }
  if(config.b) { ... }
  if(config.c) { ... }
}
Enter fullscreen mode Exit fullscreen mode

The better way to do this is:

function doSomething({ a, b, c }) {
  if(a) { ... }
  if(b) { ... }
  if(c) { ... }
}
Enter fullscreen mode Exit fullscreen mode

-7- Array methods

ES6 brought a whole slew of usable array methods like:

  • find(), finds an item in list else null
  • findIndex(), find the index of the item
  • some(), is the predicate true for at least one item in the list
  • includes(), is an item part of a list

Consider the following code to understand the usage:

const array = [{ id: 1, checked: true }, { id: 2 }];
arr.find(item => item.id === 2) // { id: 2 }
arr.findIndex(item => item.id === 2) // 1
arr.some(item => item.checked) // true

const numberArray = [1,2,3,4];
numberArray.includes(2) // true
Enter fullscreen mode Exit fullscreen mode

-8- Promises + Async/Await

If you have been around the block a while you might remember a time when callbacks were all we had, like this:

function doSomething(cb) {
  setTimeout(() =>  {
    cb('done')
  }, 3000)
}

doSomething((arg) => {
 console.log('done here', arg);
})
Enter fullscreen mode Exit fullscreen mode

We used this to handle the fact that some operations were asynchronous and simply took time to finish. Then we got promise libraries that people started using and eventually, we got native support in the language. So now we can do things like:

function doSomething() {
  return new Promise((resolve, reject) => {
    setTimeout(() =>  {
      resolve('done')
    }, 3000)
  })
}

doSomething().then(arg => {
 console.log('done here', arg);
})
Enter fullscreen mode Exit fullscreen mode

We can even chain the whole experience so we can do calls like this:

getUser()
  .then(getOrderByUser)
  .then(getOrderItemsByOrder)
  .then(orderItems => {
    // do something with order items
  })

Enter fullscreen mode Exit fullscreen mode

Async/await

Then we got async/await and life became even more glorious. Consider the above example with Promises now becoming this:

async function getItems() {
  try {
    const user = await getUser();
    const order = await getOrderByUser(user);
    const items = await getOrderItemsByOrder(order);
    return items;
  } catch(err) {
    // handle error here, the suggestion to return something or rethrow
  }
}

getItems().then(items => {
  // do something with order items
})
Enter fullscreen mode Exit fullscreen mode

We get a synchronous-looking asynchronous code. :)

 -9- Modules

Pretty much any coding language supports the concept of modules. The ability to divide up your code in many different files, files that are also self-contained units, so-called modules. Consider the following code:

// math.js

export function add(a,b) { return a + b; }
export function sub(a,b) { return a - b; }

export default (a,b) => a * b;

// main.js
import mult, { add, sub } from './math';

mult(2, 4) // 8
add(1,1)   // 2
sub(1,2)   // -1

Enter fullscreen mode Exit fullscreen mode

Above we are using the export keyword to signal that these constructs add and sub is publically available for any module importing this module. The export default keyword is what we get if we just import it. In main.js we import the default as having name mult and we also specifically pick out methods add() and sub()

-10- Arrow functions + Lexical this

I've been using Arrow functions throughout this article and it's simply another function notation. In the past we could only write functions like this:

function printArray(arr) {
 // do something
}
Enter fullscreen mode Exit fullscreen mode

Now we can define that as:

const printArray = (arr) => {
 // do something
}
Enter fullscreen mode Exit fullscreen mode

One line functions

We can also define functions as one-liners:

const add = (a,b) => a + b
Enter fullscreen mode Exit fullscreen mode

This automatically means we do the operation and return the result. We can do the same and return an object, our syntax then becomes:

const create = (a,b) = > ({ x: a, y: b })
Enter fullscreen mode Exit fullscreen mode

Lexical this
The problem we used to face was no knowing what this is. Consider the following problem:

let array = [1,2,3];

function sum() {
  this.total = 0;

  arr.forEach(function(item) {
    this.total+= item;  // `this` is the inner functions `this`, BAD
  })
  return total;
} 
Enter fullscreen mode Exit fullscreen mode

this in the above case points wrong inside of the forEach. The way we used to solve this was by doing:

function sum() {
  this.total = 0;
  var self = this;

  arr.forEach(function(item) {
    self.total+= item;  // now we are using `self`, it solves it but feels hacky
  })
  return total;
} 
Enter fullscreen mode Exit fullscreen mode

Arrow functions fix this, no more self, so now the code looks like this:

function sum() {
  this.total = 0;

  arr.forEach((item) => {
    this.total+= item;  // all is well `this` points to outer function
  })
  return total;
} 
Enter fullscreen mode Exit fullscreen mode

WINNING!

Summary

There are more things I could mention about ES6 and forward but I just wanted to show you my favorites that I think you should adopt today :)

Discussion (39)

Collapse
mistval profile image
Randall

Without the spread operator, you can do:

let firstHalf = [ 'one', 'two'];
let secondHalf = ['three', 'four'].concat(firstHalf);
Enter fullscreen mode Exit fullscreen mode

I kind of prefer that way, since it doesn't involve extra syntax. But I'm used to using the spread operator now.

Collapse
samselfridge profile image
samselfridge

The fact that this results in an out-of-order array called 'secondHalf' that has ALL the values on it is still annoying...but that's OPs fault not yours @randall

Collapse
grad profile image
Alex Grad
[].concat(firstHalf, secondHalf);
Enter fullscreen mode Exit fullscreen mode
Thread Thread
samselfridge profile image
samselfridge

const fullArray = [].concat(firstHalf, secondHalf);

Collapse
payapula profile image
payapula

Thanks for the article!

One correction:

find(), finds an item in list else null

find() actually returns undefined if no value is matched in the array.

Collapse
ssenyonga_yasin_codes profile image
ssenyonga yasin

good items here

Collapse
zirkelc profile image
Chris

Great post with short and concise examples!
I'd like to add one more example for array destructuring. You can use the fact that arrays are objects and the array indices are object keys. That means with key 0 you get the fist element, but more importantly with a computed property key like [length-2] you can get an element in the middle of the array which is not possible with array destructuring and rest parameters:

const arr = [ 'one', 'two', 'three', 'four'];

const { 0: one, 1: two, length, [length-2]: three, 3: four } = arr;

console.log({ one, two, three, four })
Enter fullscreen mode Exit fullscreen mode

codesandbox.io/s/fancy-dust-24cwhr...

Collapse
samselfridge profile image
samselfridge

This seems like one of those things thats neat, but actually using it in practice will just result in some weird behavior thats difficult to track down...

Or that could just be my inner-old-man

Collapse
zirkelc profile image
Chris

Extracting elements at the beginning of the array should be should be equivalent using array or object destructuring, because if the index does not exist the element is a going to be undefined in both cases: codesandbox.io/s/unruffled-babbage...

And if you want to extract only the last element (not possible using array destructuring), you have to use the length to calc the last index in the way as a direct array index access via brackets: codesandbox.io/s/elated-tdd-6nte4r...

Nevertheless, I wouldn't recommend object destructuring over array destructuring in all cases, but knowing about it shouldn't hurt either.

Collapse
atinypixel profile image
Aziz Kaukawala • Edited on

Great post! 5th & 10th points were my major take away! Thanks!


I don't know if this would be helpful.

For defining constants, instead of regular object,

const config = {
    APP_ENV: 'local',
    API_TOKEN: 'BREWING_SOMETHING_AWESOME',
}
Enter fullscreen mode Exit fullscreen mode

define constants with Object.freeze().

const config = Object.freeze({
    APP_ENV: 'local',
    API_TOKEN: 'BREWING_SOMETHING_AWESOME',
});
Enter fullscreen mode Exit fullscreen mode

This way, you won't be able to change the entries accidentally.

Happy Coding 🧡

Collapse
gustavoisensee profile image
Gustavo Isensee • Edited on

The good news for me at least, is that I use them all!

. + const and let.

nice post!

Collapse
madza profile image
Madza • Edited on

Some good tips in here 👍✨💯

Collapse
ajshivali profile image
Shivali Pandey

Good one

Collapse
jasonstathum6 profile image
Jason Stathum

Thank you for sharing, I've learned a lot.

Collapse
gabrielpedroza profile image
Gabriel Pedroza

Nice job! The only thing that you might reconsider to change if you do is the async/await example. When fetching multiple data from different api's, you should use promise.all instead of async so you can fetch all the data at once instead of waiting for the first one to be finished and to start the second fetch and so on.

Collapse
phyberapex profile image
PhyberApex

Not really. If you look at the example he uses the return value of the first await for the second one, so he can't send them in parallel. It's exactly the use case for async/await :)

If there are no dependencies, I'd agree to use promises and a promise.all()

~Cheers

Collapse
patilganesh1010 profile image
Ganesh Patil

New things to learn thankyou for sharing

Collapse
andrewbaisden profile image
Andrew Baisden

Good post full of practical examples thanks for sharing it with the community.

Collapse
mjcoder_5 profile image
MJCoder

Thank you for the article, straight to the point with really good examples. Loads of stuff to learn and implement.

Collapse
digioi profile image
Mike DiGioia • Edited on

I don't think your NO spread for objects is right, I would likely do it like

const heroWIthWeapon = Object.assign({}, hero, {weapon: "sword"})
Enter fullscreen mode Exit fullscreen mode
Collapse
softchris profile image
Chris Noring Author

hi Mike, are you saying this code should use Object.assign()?

let keys = Object.keys(hero);
let obj = {};

for(var i=0; i< keys.length; i++) {
   obj[keys[i]] = keys[props[i]];
}
Enter fullscreen mode Exit fullscreen mode

Because my point was to showcase how painful it can be without spread? Help me understand, thanks :)

Collapse
digioi profile image
Mike DiGioia

I get that, but just because you can do something more painfully doesn't mean that it can't be done simpler. On top of that, there is no assignment of the weapon key.

I think it can be argued that
{ ...hero, weapon: "sword"} is easier to read than Object.assign({}, hero, {weapon:"sword")

Thread Thread
softchris profile image
Chris Noring Author

yes I agree that spread is easier to read than Object.assign().. so are you saying that it doesn't have to be as painful as looping through the keys but rather Object.assign is there and if you want to improve on that use spread syntax?

Thread Thread
digioi profile image
Mike DiGioia

I think that is fair to say. Since prior to browser support that was what the transpilers often did for you.

Collapse
andrewbridge profile image
Andrew Bridge

I was thinking exactly this.

Even better: with this approach, you can choose to merge elements into an pre-existing object which can help with keeping object references and reduces the need for the engine to garbage collect short-lived objects.

Feels like a better approach!

Collapse
kartjim profile image
kart jim

It's very useful!

Collapse
bencull39289525 profile image
Ben Cullen

This is a good article in the sense that nothing totally new or ground breaking but some excellent explanations and examples. Thank for sharing.

Collapse
morganconrad profile image
Morgan Conrad • Edited on

The destructuring argument is unconvincing: the "old fashioned" code is extremely clear, easier to update if the structure of the object changes, and allows one to change the variable name, if needed, for even more clarity, e.g.

const nameNeedsValidation = req.param.name.

As others have noted, the Object Spread comment about needing looping is wrong: Object.assign() does the job nicely. IMO, it is also clearer than using spread, but I admit that's just my opinion.

Collapse
patrickb profile image
Patrick Bradshaw

Destructuring like so:

const { name: nameNeedsValidation } = req.param

Creates a variable called nameNeedsValidation from the property req.param.name.

I find it preferable to destructure in the case of when I'm working on a React class component and the state object is complex (10+ properties).

In the template, instead of having to write:

<>
   <h3>Hi I'm {this.state.name}</h3>
   <span>Name: {this.state.name}</span>
   <span>Address: {this.state.address}</span>
   <span>Zip Code: {this.state.zipCode}</span>
</>
Enter fullscreen mode Exit fullscreen mode

I can just write:

const { name, address, zipCode } = this.state
//...
<>
   <h3>Hi I'm {name}</h3>
   <span>Name: {name}</span>
   <span>Address: {address}</span>
   <span>Zip Code: {zipCode}</span>
</>
Enter fullscreen mode Exit fullscreen mode

Note that this would be unconventional:

const name = this.state.name
const address = this.state.address
const zipCode = this.state.zipCode
// ... 
Enter fullscreen mode Exit fullscreen mode

React class components are "old school" now I think, but destructuring is convenient any time you have to work with a complex object in which the properties have to referenced multiple times. That's the use case I'm most familiar with.

Collapse
jt3k profile image
Andrey Gurtovoy

looks like a great article for 2015)

Collapse
softchris profile image
Chris Noring Author

you don't agree with the advice?

Collapse
spock123 profile image
Lars Rye Jeppesen

Hehe totally my thoughts. ( I know, bad me bad me)

Collapse
rerodrigues profile image
Renato Rodrigues

Now that IE is retired, it looks like the IE team started writing JS articles

Collapse
spock123 profile image
Lars Rye Jeppesen

Merging objects using spread operator does not make a deep copy. Be very careful with this or you'll really mess up

Collapse
masekere profile image
Gift Masekere

Quite a helpful post and helps in doing stuff in a less painful way

Collapse
ale_zampa profile image
Ale Zampa • Edited on

Last one you could use just .reduce and something like: return [1,2,3].reduce((acc,e)=>acc+e,0)

Collapse
tannguyen248 profile image
tannguyen248

I believe the spread object and rest parameter shouldn't be used. source code will be hard to maintain and become out of control when used so much

Collapse
jacksonkasi profile image
Jackson Kasi

Thanks 🙂

Collapse
jamiepgood profile image
Jamie Good

Very clear explanation thanks!