6 Things to Start with in ES6 (ECMA 6/ ES2015)

Variables & Parameters, Destructuring, Default and Rest Parameters, Spread Operator and Templates

Posted by Anas R Firdousi on April 16th, 2016
20 - 25 mins read

Moving to the new JavaScript

At the time of this writing, ES6(aka ECMA 2015/ ES2015) is widely accepted and implemented across different platforms. JavaScript is now a mainstream language and it's all over the place. From application frontend to application backend, to databases, to platforms, to robotics (and much more), you can do it all in one universal language called "JavaScript". Since the re-advent of JavaScript(jQuery days and others), it has evolved and emerged as a one-stop-shop for different kinds of applications and the popularity of this language has increased by leaps and bounds. ES2015 is the most extensive update to the language since it was first released in 1997.

The current version of JavaScript is called ES6 or ES2015 released in June 2015. TC-39 ECMA Script committee has decided to role out new specifications almost every year from now onwards which will be implemented in browsers and other platforms like Node. I this post, I want to focus on 6 things to start with, if you haven't yet started writing ES6 code. ES6 is major improvement over traditional JavaScript we all have been writing for years! ES6 adds much-needed features such as modules and classes, and some useful ones, Maps, Sets, Promises or Generators and many others. In spite of being a large release, ES6 is fully backwards-compatible with previous versions, the standardization committee having decided to avoid “breaking the web” caused by noncompatible versions of the language. As a result, all the previous code runs and the transition is smoother.

The 6 basic features we'll discuss are designed to make JavaScript code easier to read, write and also to avoid some of the strange behaviours JavaScript had in the past. ES6 code is also safer than older versions.

1. Defining Variables & Constants

All of us have used "var" to define a variable in the past, we now have a "let" keyword and we will see in a bit why it is better. There was no notion of a constant in older JavaScript. We now have the "const" keyword to help us with that.

Let's understand "let"

Let's understand what limitations var keyword in JavaScript had and why let is a better choice in many situations.

Scope is a space where any variable is legal to use. var keyword had limitations. There were only 2 types of scope for a variable defined with var keyword, the function in which it is defined and then the global scope. What's missing was a block-level scope. "var" variable did not have block level scope which was unusual compared to other languages. Let's get this, here is a function getData returning some result.


  
  var getData = function(someCondition){

      if(someCondition){
          var result = 3;
      }

      return result;

  }

  getData();  // undefined
  getData(true); // 3


Notice on line 13, getData() is called without any parameters returns "undefined" instead of throwing an error. Why? The "if" statement never went through and , we may think, that variable "result" was never declared and hence return result; on line 9 should return an error saying variable not defined. This wasn't true for var

This is called variable hoisting in JavaScript. All var statements were "hoisted" to start of the function before it ran. Hence, the function written above was converted to the following by JavaScript engine before it ever ran:


  
  var getData = function(someCondition){
    
      var result;

      if(someCondition){
          result = 3;
      }

      return result;

  }


Notice the result variable hoisted to top of the function at line 5. This is done by JavaScript engine and this was the reason why there were no errors on our return statement because result was available within scope of the return statement. This is different from most of the programming languages and you may think of it as unusual. Let's take another example to further strengthen our understanding of variable hoisting (You may skip this example if you sure you already know hoisting).


  
  function loadMessages(users) {

    if(users.length > 2){

        console.log("Loading data ...");
        displayLoadingSpinner();
        console.log(flashMessage); // What will this print? Think!

    }else{

      var flashMessage = "Hello! This is a dummy message";
      displayMessage(flashMessage); // Hello! This is a dummy message 

    }

  }

  loadMessage(["user1","user2","user3"]);

What do you think of line no.9 ? If you are not sure, copy paste this code to your developer console and call the function loadMessages() with different parameters.

Let's write our first example the getData() function replacing "var" with a "let".


  
  var getData = function(someCondition){

      if(someCondition){
          let result = 3;
      }

      return result;

  }

  getData(); // Uncaught ReferenceError: result is not defined
  getData(1); // Uncaught ReferenceError: result is not defined
  getData(true); // Uncaught ReferenceError: result is not defined


As you can see above, our code is throwing "ReferenceError" which was actually the behavior we expected. "let" to the rescue! "let" is .

  • Not hoisted
  • Is block scoped meaning it is only available in the scope it is defined and it respects scopes other the global and function scope. In this example, it gives respect to scope created by "if" statement (because it was defined within) and not visible outside of the scope it was defined in.

Let's take one more example before we move, how about putting "let"/"var" in a for loop within a function. Loops also have their own scope like conditional statements. Is that true? Let's check out!


  var loopOverMyWorld = function(){

      for(var i=0;i<5;i++){
        console.log(i);
      }

      console.log("Last value of i where loop stoped",i);

  }

  loopOverMyWorld(); 


  //1,2,3,4
  //Last value of i where loop stoped 5

We already know that this before this function ran, it got converted to:


  var loopOverMyWorld = function(){
      var i;

      for(i=0;i<5;i++){
        console.log(i);
      }

      console.log("Last value of i where loop stoped",i);

  }

What If Moment: What if we replace "var" by "let"? When I did that, I got the following output:
              
                //1,2,3,4
                //Uncaught ReferenceError: i is not defined
              
          

Do you know why this happened? Do you want to give it a try ?

"let" can be very beneficial while writing a long function routine. In JavaScript, its easy to mess up with variables and they are hard to debug since all "var" get hoisted to the top and can create painful debug scenarios.

Welcome to "const"

How can we create an initialize a read-only variable ( read-only variable? isn't that funny?) in JavaScript? Before ES6, there were no notions of constants. A general CAPITAL letter convention was used to denote something as constant but those were only helpful for developers to be mindful not to change the value of those variables. They were not actually constant and JavaScript engines would allow changing values of those variables. Like:

  
      var HOURS_IN_WEEK = 24 x 7;
      HOURS_IN_WEEK = 10000; // Legal statement, throws no errors
  
It is a classic software convention to write name of constants in capital letters with an underscore for spaces. You can always name it like any normal variable hoursInWeek if you like to. The convention is there just to differentiate between the two.

In ES6, you can now create a real constant holding a value that can never change. So "const":

  • Can never change a value once assigned
  • Block-level scoped same as "let".
  • Needs a value assignment at the time of declaration else it will throw a syntax error "Unexpected token".


    const HOURS_IN_WEEK = 24 x 7;
    HOURS_IN_WEEK = 10000; // Uncaught SyntaxError: Assignment to constant variable.
    //In some envioronments it might say : Attempting to override 'HOURS_IN_WEEK' 
    //which is a constant.

Aside: Before the ES6 "const", there has been a const supported by Chrome & Firefox versions. That const would just ignore the new values assigned to something defined as "const" but would shout that you are doing it wrong. This was all pre-ES6. Now "const" is part of JavaScript and changing value of a const (in a ES6 environment) will throw an error or either fail silently but one thing is for sure, the value won't be changed.
Let's take one more quick example to see if "const" really has block-scope semantics.


  const WORK_HOURS = 100;

  var getWorkingHours = function(){
    const WORK_HOURS = 80;
    return WORK_HOURS;
  };

  var hours = getWorkingHours();

  console.log(hours); //80
  console.log(WORK_HOURS); //100

  

We can clearly see the constant "WORK_HOURS" defined within the "getWorkingHours()" function is scoped to that function itself and hence does not conflict with "WORK_HOURS" defined outside of the function.

Skip the next example and jump to "Destructuring Structures" if you are not new to JavaScript or programming in general. In next example, we will understand why on earth do we use constants after all ?

Constants are used to define constants, that's true but what else? We mostly use constants to increase code readability and avoid magic numbers. Take a look at this example:


  function logDataToServer(data){

    if(data.length > 100 ){
      //do something
    }else{
      //do something else
    }

    let something = getData(); //suppose getData() returns a number

    while(something > 100){
      //do something
    }

  }

  
Looks like "100" is has some importance for us and its used over and over again. It can be a fixed threshold value. What if we want to change our fixed threshold value to something else later. We than have to go to each occurrance of "100" and change it. "100" is the magic number here. Let's remove all magic numbers instances and define a const. This will increase our code readability and avoid any magic numbers in the code.


  function logDataToServer(data){

    const MAX_DATA = 100;

    if(data.length > MAX_DATA ){
      //do something
    }else{
      //do something else
    }

    let something = getData(); //suppose getData() returns a number

    while(something > MAX_DATA){
      //do something
    }

  }

  

How cleaner is that code? What do you think? That's the kind of thing we use constants for, generally.

2. Destructuring Structures

Destructing is a powerful feature that allows you to set values to a set of variables by some kind through pattern matching. This kind of stuff is already available in some other languages but is new to JavaScript. Quick example can help us:


  let a = 10;
  let b = 20;

  [a,b] = [b,a]; // The all new swap algo !!!

  console.log(a); //20
  console.log(b); //10

Do you believe we literally swapped values of 2 variables? Where is our swapping algo? You guessed it right! with destructuring in JavaScript, we don't need it anymore!

The most important part to notice here is that [a,b] on left-hand side of "=" is not an array, it looks like an array but its not. We are literally referring to two variables "a" and "b" in order. What we see right-hand side of "=" is an array! So how do we read line number 5 right to the left?

Create an array and place value of b on index 0, value of a on index 1 and then assign value of index 0 to a variable called "a" and value of index 1 to a variable called "b". We destructured an array and assigned values to two variables all in one statement! Are you excited? Let's look at another example:


  let [a,b] = getData();

  function getData(){
      return ["first item","second item"];
  }

  console.log(a); //first item
  console.log(b); //second item
  

      

Wow! That's pretty awesome! Isn't it ? Think of getData() function as some real function that returns you a complex structure. You then take that structure and assign to different variables one by one. You can do it all in one line now! What if getData() returned more than 2 things and may I want to skip the first and assign 2nd to a and 3rd value to b.Check this out:


  let [,a,b] = getData(); // Notice the extra comma!

  function getData(){
      return ["first item","second item","third item"];
  }

  console.log(a); //second item
  console.log(b); //third item
        

Just putting an extra comma helped us ignore the first value and assign the rest to a and b respectively. Can you write something similar to skip the last value ? Hint: It is allowed to keep an extra comma anywhere: in the start, in the end or even in the middle.

What if we expect more but get less out of a function? Hint: Anything not defined in JavaScript is "undefined". Let's see


  let [a,b,c,d] = getData(); // Notice the extra comma!

  function getData(){
      return ["first item","second item","third item"];
  }

  console.log(d); // undefined
  

We have seen examples for Array destructuring. Can we destructure an object returned from a function? Let's check it out:
    
      let {

            name:name,
            age:age,
            gender:gender,
            country:location

          } = getUserProfile(); 

        function getUserProfile(){
          return {
                    name:"Usman",
                    age:80,
                    gender:"Male",
                    country:"Pakistan"
                  };
          }

      console.log(name); // Usman
      console.log(location); // Pakistan
      

Things to notice in above example:

  • Read let statement like this:

    let {

    <property in returned object> : <name of the variable to assign to>

    } =

    <method returning object>()

    I found it a lil crazy when I learned it first, things to the left of collin (:) are property names of the object you are destructuring and not the new variable you are assigning the value to. This is important.

  • We can have any variable name assigned to any property. In line no.7, we assigned the property country to variable "location"

If you want to create variables exactly of the same name as properties present in the returned object, there is a shortcut! Let's figure that out:

    
    let {
          name,
          age,
          gender,
          country

        } = getUserProfile(); 

      function getUserProfile(){
        return {
                  name:"Usman",
                  age:80,
                  gender:"Male",
                  country:"Pakistan"
                };
        }

    console.log(name); // Usman
    console.log(age); // 80
    console.log(gender); // Male
    console.log(country); // Pakistan
      
   

Let's move to a slightly complex structure returned by out getUserProfile() function.

    
    let {
         
          name:name,
          age:age,
          gender:gender,
          location:{country:country},
          location:{city:city},
          location:{area:area}

        } = getUserProfile(); 

      function getUserProfile(){
        return {
                  name:"Usman",
                  age:80,
                  gender:"Male",
                  location:{
                        country:"Pakistan",
                        city:"Karachi",
                        area:"North"
                    }
                };
        }

    console.log(name); // Usman
    console.log(country); // Pakistan
    console.log(city); // Karachi
    console.log(area); // North
                  
               
          

getUserProfile() now returns a location property which is an embedded object i.e. an object within an object. We navigate to this embedded structure in lines 7,8 and 9. Read any of these like:

<Name of Property X > : { Name of property Y within X : Name of Variable to Assign Value of Y }

One last example we will take on destructuring is the when we want to pass in option objects to a method. We generally do that while writing a library or using a library/plugin and want to override defaults. Let's look at how we would do it without destructuring:

  

  let toaster = function(message, options){
      
      let area = options.width * options.length;
      return area;

  }

  let notification = toaster("A simple message for humanity", { width:100,length:100,
                                                              title:" Dummy Title"});

  console.log("Area of Notification :",notification); //10000

  
  

Doing it with destructuring, you don't have to look into options variable in the toaster() method and you don't even need to define a "area" variable. Let's do this:



    let toaster = function(message, {width,height,title}){
       return width * height;
    }

    let notification = 
        toaster("A simple message for humanity", { width:100,height:100,
                                                  title:" Dummy Title"});

    console.log("Area of Notification :",notification); //10000


That's the power of destructuring. What do you think?

3. Default Parameters

In JavaScript , we often use OR (||) expressions all throughout functions in order to set default values to variables. For example:

    

       function MemberService(url, level){

         this.url = url || "http://localhost:3000";
         this.level = level || "low";
         
         return {
           link: this.url,
           flag: this.level
         };
       }

      var memberServ = new MemberService("","high");

      console.log(memberServ.link); //http://localhost:3000
      console.log(memberServ.flag); //high
    
  

On line 5 and 6, we use the widely used JavaScript practice or ORing a default value while setting values to a variable. This makes sure we always have some value setup, be it the one user passed or a default value. Let's look at how we can write the same example but in ES6:

    
    function MemberService(url="http://localhost:3000", level="low"){
      
      return {
        link: url,
        flag: level
      };
    }

    var memberServ = new MemberService();

    console.log(memberServ.link); //http://localhost:3000
    console.log(memberServ.flag); //high
    
                

As you can see, ES6 has a more expressive syntax. It's intention revealing because as soon as you look at the function signature, you know what it is doing with the parameters passed to it. You don't even to look into the function body.

There is something else as well which is very important and we need to be careful about it. Look at the example we I used the ORing technique. ORing works when the user passes nothing, or "undefined", or empty string "" which means OR works with any kind of falsy value. That's not the case with the new default parameters. Let's change line 10 where I call my MemberService constructor. Try this out:

    
    . . .
    var memberServ = new MemberService("","");
    . . .
    
                
Our console.log at line 12 and 13 won't print anything. Why? because ES6 Default parameters only work on "undefined" value which is what is passed when we did not pass anything. Default parameters do not work on all kinds of falsy values. It only works on "undefined". Default parameters will work if, either you don't pass anything at all or you can explicitly pass "undefined". It won't work on any other falsy value like "" or null or false e.t.c.

One more important thing to notice here is either all parameters of a function need to have default parameters set or you can only define default parameter params once you have defined all regular paramets. For example, following function signature will break:

    
    . . .

    function MemberService(url="http://localhost:3000", level){

    }

    . . .


    //ERROR : Regular parameters cannot come after default parameters.
    
    
Aside: Thanks to `Daniel Bruhn` @ PayPal for pointing this out. Environments like Babel (and may be other environments ) allow you to have default parameters before regular parameters but in order to achieve that, you will have to explicitly pass `undefined` to default parameters to use default values. So the above MemberService constructor will be called like
MemberService(undefined,100);
which IMHO is not a good idea but YES its allowed in ES6. In other languages like C#, optional parameters are defined at the end of the parameter list, after any required parameters which makes more sense in ES6 as well.

Let's look at a few more examples:

      
      function add(x=10,y=20,z=30){
        return [x,y,z,x+y+z];
      }

      let [x,y,z,sum] = add(40,undefined);

      console.log(x); //40
      console.log(y); //20
      console.log(z); //30
      console.log(sum); //90
      
    

Oh did you enjoy the holy destructuring with a mix of default parameters? We have demonstrated the use of explicit "undefined" for y and not passing anything (which is implicit undefined) for z. Again, either all parameters should be implicit undefineds meaning that you don't pass anything to the function or implicit undefineds have to be after all regular params and explicit undefineds. You can not have an implicit undefined in the middle.

Functions are first class citizens in JavaScript. What does that mean? We discussed that at length in a previous post. The question is, can we pass functions as default parameters as well? Let's give it a try:

      
      function foo(something=myCrazyFunco()){
        return something;
      }

      function myCrazyFunco(){
        return "I am a crazy funco!";
      }

      console.log(foo()); //I am a crazy funco!

      
    

One thing to know here is the function myCrazyFunco has to be defined in an environment which is available to foo() before foo() is called. For example, in JavaScript we can and we do write functions within functions but anything within a function is evaluated after the function itself hence we can not have a default param pointing to a function declared inside itself. Check this example:

      
      function foo(something=myCrazyFunco()){
        return something;

         function myCrazyFunco(){
          return "I am a crazy funco!";
        }

      }

      console.log(foo()); //I am a crazy funco!

      //Uncaught ReferenceError: myCrazyFunco is not defined
      
    

Default params can be helpful in many number of ways. Let's take one last example before moving to our next topic.

      
           let somePlugin = function(url, {data = "some default data", flag = true}){
              return {
                      data:data,
                      flag:flag
                    };
          }

          let response = somePlugin("/data",{flag:false});

          console.log(response.data); //some default data 
          console.log(response.flag); //false
      
    

Shoot! Default parameters work to set default objects value as well! Aren't they amazing?

4. Rest Parameters

In JavaScript, it is allowed to pass as many or as little numbers of parameters to a function. There are 2 things passes to a function. One the parameters it accepts, and secondly, there is an invisible "arguments" thing passed to it. "arguments" object is created b JavaScript engine itself and passed to the function. Its an array-like object which have some properties of an array but its not actually an array. Over the years, JavaScript programmers have used "arguments" object when the number of parameters passed to a function can be of variable length. A quick example might help, let's write a function that returns result of multiplying all items passed to it.

      
    function multiply(){
      let result = 1;
      for(let i = 0;i < arguments.length;i++){
        result = result*arguments[i];
      }

      return result;

    }

    console.log(multiply(10,20,30)); //600
      
    

Cool stuff! Everything passed to the multiply function went into an array-like object "arguments" and we looped over it to fetch each item. What If Moment: What if we want to sort the items passed to it before we do anything? We can't sort "arguments" because it does not have sort() method like Arrays. We have a traditional solution of borrowing methods from array. If you want to read more on that, here is a detailed post I wrote about it. Let's look at the code:

 
   function someFunc(){
    
    var args = Array.prototype.slice.call(arguments,someFunc.length);
    args.sort();  
    console.log(args[0]);
    console.log(args[1]);
  }

  someFunc(90,70,95,40,50); //40,50
  
We converted an array-like object "arguments" to an actual Array on line 4 and then we are able to use array methods like .sort() on line.5. Line.4 shows the boilerplate code required to borrow methods of Array type to an Array-like type. Let's use the "Rest operators" to do the same. In code read rest parameters as dot dot dot (...)

 
   function multiply(...numArr){
      let result = 1;
      for(let i = 0;i < numArr.length;i++){
        result = result*numArr[i];
      }

      return result;

    }

    console.log(multiply(10,20,30)); //600

 
   function someFunc(...numArr){
    numArr.sort();
    console.log(numArr[0]);
    console.log(numArr[1]);
  }

  someFunc(90,70,95,40,50); //40,50
  
Check out line.16. We don't need any boilerplate code. Rest parameters are actual arrays.

How does it work? Everything passed to the function is placed in an array named "numArr". That's straightforward. How about mixing normal parameters with rest parameters, in doing so, we have to keep rest parameters in the end which makes sense. We will first pass our parameters explicitly and then rest of the values we pass will be placed in our rest parameters array. Let's look at this:

 
   function getData(id,...numArr){
      console.log("Getting data for ",id);   

      let result = 0;
      numArr.forEach(function(n){
        result = result + n;
      });

      return result;
    }

    console.log(getData("ABC",50,100,150,200)); 
    //Getting data for ABC
    //500
  
Isn't that nice? One quick question, what if we don't pass anything after "ABC" on line 13. numArr will be what? Will it be a "null" or "undefined" or anything that can break our code on line.6 , since we can not .forEach() over a null or an undefined value. Well, it turns out rest parameters also saves us from that. if ...numArr don't get anything, it will be an empty array [] and not a null/undefined. Try it out:
 
    console.log(getData("ABC")); 
    //Getting data for ABC
    //0
  

5. Spread Parameters

Spread parameters are similar to rest parameters in that they also use a dot dot dot (...) . Spread parameters as the name suggest spreads items of an array to individual parameters of a function. Quick example?

 
   let cuteFunc = function(x,y,z){
      return [x,y,z].join(' ');
   }

   let result = cuteFunc(...["hello","there","!"])
   console.log(result); //hello there !
  

"hello" went to x, "there" was assigned to y and "!" was given to z. Simple?! We don't necessarily need an inplace array all the time. Lets change that:

 
   let cuteFunc = function(x,y,z){
      return [x,y,z].join(' ');
   }

   let arr = ["this","is","cool","stuff"];
   let result = cuteFunc(...arr);
   console.log(result); //this is cool stuff
 

We can also use spread operators to concat items to an array to create new arrays and can also use it to push items to arrays. Lets look at few examples on concat and push with and without spread operators.

 
 
 //////////////////////////////////////// 
 //ES5 Way of doing things
 ////////////////////////////////////////
 
 //Concat examples
 var x = [10,20,30];
 x.concat(40,50); // 10,20,30,40,50    
 console.log(x); //10,20,30

 var y = [90,100];
 x.concat(y); //10,20,30,90,100
 console.log(x); //10,20,30

 //Push examples
 x.push(40);
 console.log(x); //10,20,30,40

 x.push(y); // [10,20,30,40,Array[2]];
 //Above is not the right way to push an array to an exisitng array.
 //It created a nested array and we somehow have to flatten it.

 //Instead, lets restore x and do it the right way
 x = [10,20,30];
 x.push.apply(x,y); // [10,20,30,90,100]

 /////////////////////////////////////////
 //ES6 Way of doing things
 ////////////////////////////////////////
 x = [10,20,30]; // just restoring, no ES6 yet!
 [40,50,...x]; // [40,50,10,20,30]  // No .concat() needed

 x.push(...y); // No .push.apply() needed
 console.log(x); //[10,20,30,90,100];


 

Remember destructuring? What if I want to destructure items from an array. Lets say I want to give the first 2 items to variables "a" and "b" and rest of the arguments to our variable "rest".

 
 //////////////////////////////////////// 
 //ES6 Way of doing things
 ////////////////////////////////////////

 var x = [1,2,3,4,5,6,7,8],
 a = x[0],
 b = x[1],
 rest = x.splice(2);

 console.log(a); //1
 console.log(b); //2
 console.log(rest); // [3,4,5,6,7,8];
 
Did you see how much code we had to write in line 7,8 and 9 just to do a simple thing? We can do it all in one line with ES6 spread operators!
 
 //////////////////////////////////////// 
 //ES5 Ways of doing things
 ////////////////////////////////////////

 var x = [1,2,3,4,5,6,7,8],
 [a,b,...rest] = x;

 console.log(a); //1
 console.log(b); //2
 console.log(rest); // [3,4,5,6,7,8];
 
We replaced line 7,8 and 9 to just one killer line , line#7 ! Talk about powerful destructuring and spread operators! One last use case that can occur very rarely but worth mentioning is composing new with apply. You are not allowed to do that in ES5 because internally apply makes a call to .call and not a constructor which was what new operator does. You can combine the two using by doing new over a type and then use spread operators while passing args to that type at the same time. We will skip any examples for this usecase.

6. Templates

In the past, we have used several templating engines to build templates in JavaScript. ES6 ships with built-in templates so you might want to avoid other templating overheads for basic scenarios. There are other use cases for templates in ES6 as well. The most basic of all of those is string concatenation. There are 2 ways we have done it over the years in JS. Either use a plus sign or do a hack of using array type's .join() method. Using simple plus operator creates a lot of stuff to be garbage collected because we know that strings are immutable and everytime you plus a string to a string it creates a new string and does not mutate the original ones. Let's look at old ways and how ES6 templating can help us here:

 
 //////////////////////////////////////// 
 //ES5ish Way (but using let ;) )
 ////////////////////////////////////////

 let address = "mycoolweb.com";
 let context = "/user";
 let id = 1000;

 var url1 = "http://" + address + context + "/" + id; 
 var url2 = ["http://",address,context,"/",id].join(''); // a good hack in old days!

 console.log(url1); // http://mycoolweb.com/user/1000
 console.log(url2); // http://mycoolweb.com/user/1000
 

How can ES6 templates help us here?

 
 let address = "mycoolweb.com";
 let context = "/user";
 let id = 1000;

 let url = `http://${address}/${context}/${id}`;

 console.log(url); // http://mycoolweb.com/user/1000
  

Isn't that cleaner? There are few things to notice here if you are new to ES6.

  • Did you notice the backticks ` for opening and closing the template on line 10? That's important. We can not use our normal single or double quotes here.
  • On same line 10., dollar $ sign are used as placeholders to variables named within them. For our example, it is important to understand that these variables should be present within the scope of where the template lives. In our example, protocol, address, context and id all existed in the same scope as the template. If any of the variables referenced in placeholders are not available, a ReferenceError is thrown. Also, ES6 templating engine just replaced placeholders with value of variable. There is no string concatenation going on here and we are not creating lots of immutable strings to be garbage collected.
  • We can have both, container with dollar and curly braces ${} as well as hard-coded like we have fixed `http://` part.

Another thing that we can do is valid calculations within template placeholders. Let's look at this example.

 
    
 function templateBuilder(x,y){
  return `Sum of ${x} and ${y} is ${x+y}`;
 }
 templateBuilder(10,20);  //Sum of 10 and 20 is 30
 

Let's discuss 2 more advance way of using templates before we wrap up. Starting with Tagged Template Literal. ES6 allows you to associate a tag with templates. A tag is a function that is invoked by the run time. Let's look at an example before we discuss the details.

  
  let x = 100,y=200,z=300;

  function tagBuilder(strings, ...values){
    console.log(strings[0]); // Sum:
    console.log(strings[1]); // ,Product:
    console.log(strings[2]); // 
    console.log(values[0]);// 600
    console.log(values[1]);// 6000000
    console.log(values[2]);// undefined
  }

  tagBuilder `Sum:${x+y+z},Product:${x*y*z}`;
  

We can name the tag function anything we like. We called it "tagBuilder". The first parameter to this function is called parsed template strings. These are the pieces of literal text in that template other than the place holders placed in an array. We called this array "strings" but again, you can name this parameter whatever you like.See line 5 & 6 for results. The second parameter to a tag function is a rest parameter and by now we know that it is an array as well. This array which we named "values" hold values of each placeholder in the template. Our first placeholder is ${x+y+z} and the second one is ${x*y*z}. "values" rest parameter array will contain the result of parsing each of these templates on each index. See line 8 and 9. I also included line 7 and 10 on purpose. Line 7 shows that the first literal text array will return nothing/empty string if we access an index which does not exist. Line 10 indicates that a "undefined" is thrown if we try to access an index not available on our parsed placeholder results array.

tag template functions are powerful. There are thousands of scenarios in which they can be helpful. You can use to HTML encode your template to prevent cross site attacks, you can take template and proper case or upper case them , may be a tag function to help with localization, may be a function to query data from within a template and much more. There is no limitation to what you can do with template tags. You can surely do a lot of useful stuff using this amazing feature of templates in ES6.

Conclusion

In this article we have seen a basic set of ECMA6 (ES6) features including let and const to define values, destructuring to swap and assign values, default and rest parameters, spread operators and last but not the least, a wonderful template syntax built right into ES6. These features overcome shortcomings in older JavaScript and also provide a syntactic sugar to speed up trivial tasks. I am convinced these basic set of features will improve our day to day JavaScript and will make it more expressive, secure and less error-prone.

Feel free to leave a comment, question, suggestion and corrections. Until next time, Happy learning!