Free eBook - Building Applications with Spring 5 and Vue.js 2

4.6 (9 reviews total)
By James J. Ye
  • A new free eBook every day on the latest in tech
  • 30 permanently free eBooks from our core tech library
  1. Modern Web Application Development - This Is a New Era

About this book

Building Applications with Spring 5 and Vue.js 2, with its practical approach, helps you become a full-stack web developer. As well as knowing how to write frontend and backend code, a developer has to tackle all problems encountered in the application development life cycle – starting from the simple idea of an application, to the UI and technical designs, and all the way to implementation, testing, production deployment, and monitoring.

With the help of this book, you'll get to grips with Spring 5 and Vue.js 2 as you learn how to develop a web application. From the initial structuring to full deployment, you’ll be guided at every step of developing a web application from scratch with Vue.js 2 and Spring 5. You’ll learn how to create different components of your application as you progress through each chapter, followed by exploring different tools in these frameworks to expedite your development cycle.

By the end of this book, you’ll have gained a complete understanding of the key design patterns and best practices that underpin professional full-stack web development.

Publication date:
October 2018
Publisher
Packt
Pages
590
ISBN
9781788836968

 

Chapter 1. Modern Web Application Development - This Is a New Era

The diversity of the modern web application development ecosystem is increasing at anastonishing rate. You can see new frameworks and libraries coming out almost every day. People seem to be happy with constantly reinventing the wheel. This could be daunting to new learners as the time it takes to master a new skill makes it sometimes difficult to keep pace with the rate with which technologies are evolving. And this has caused frequent complaints of making web development unnecessarily complicated, as there are many new libraries and frameworks to learn and a large number of new tools to familiarize yourself with. 

The anxiety associated with the feeling that you might not be able to keep up with the mainstream can be released once you have learned how to create a full-stack modern web application and master the skill sets that are required to build such an application. And, with this book, you will learn these skill sets and realize that being a full-stack developer is not only about knowing how to write frontend and backend code. In this book, we will create a real-world, full-stack web application and cover most of the skills that a full-stack developer would require. And once you have finished reading it, you will understand that languages and frameworks are just tools in your toolbox. You can replace them with other languages and frameworks and also build great full-stack applications.

To start our journey, we will begin with the basics. Once you have those basics imprinted in your mind, you can learn the frameworks more efficiently. In this chapter, we will cover the following topics:

  • An introduction to the full-stack web application we will build in this book
  • Learning JavaScript from a Java developer's viewpoint
  • Learning the features of ECMAScript 2015 that we will use in this book
 

Introduction


If you're creating a résumé page for your personal website, it will most likely be sufficient to write it in vanilla JavaScript, HTML 5, and some creative CSS style sheets that make it look special and unique. And if you're creating the jobs page of your company's official website, doing it barehanded might work. But if you use a number of frameworks to style the page and to process logic and animations, you will find yourself in a much better position that you can still go home from work earlier than usual and enjoy a cup of coffee with friends at the weekend. And if you're building something likeMonster (https://www.monster.com/) I hope that you are not just carrying those favorite frameworks with you to the battlefield, and if you go barehanded, you wouldn't even make it there. With the level of complexity of such an application and the fact that you will need to keep improving or changing features because of the neverending stop-change requirements, you need to bring the tools for modern web application development with you.

Note

Sometimes, people use the term vanilla JavaScript to refer to using plain JavaScript without any additional libraries, such as jQuery, Lodash, and many more. That means that you write your code with only the APIs that the JavaScript language itself provides.

In this book, we will create a modern web application called TaskAgile, which is a Trello-like application for task management. Let's first get an overview of the technologies that we will use to build it.

For the frontend, we will use Vue.js 2 as our frontend application framework, Bootstrap 4 as the UI framework, and we will write our frontend in ES6, also known as ECMAScript 2015, or ES 2015, and then use Babel to compile it into ES5 code. We will use ESLint to check our JS code to make sure it follows all the rules we defined and use Flow (https://flow.org) for static type checking. We will use Jest to write our frontend unit testing's and use Nightwatch.js to run our end-to-end test cases. We will use webpack 4 to bundle all the dependencies and use npm to take care of the package management.

For the backend, we will use Spring Boot 2 to create a Spring 5 application. And we will use Hibernate 5 as our object-relational mapping (ORM) framework, and MySQL as our database. We will use Spring Security for authentication and authorization, and we will implement a real-time-update feature with Spring WebSocket. We will use Spring AMPQ for asynchronously processing background tasks and Spring Session for server-side session management.

Now, before we introduce Vue.js 2 and Spring 5, for readers who are not familiar with JavaScript, let's learn the basics of it, starting with the part that Java developers would easily get confused with. 

 

 

JavaScript from a Java developer's viewpoint


For readers who are new to JavaScript but who are familiar with the Java language, here are some differences between the two languages that may confuse you. And even though this section is written from a Java developer's perspective, if you're new to JavaScript, you will also find it informative.

Functions and methods

A function in JavaScript is quite different from a method in Java because it is actually an object created by theFunctionconstructor, which is a built-in object of the language. Yes, that's right.Functionitself is an object too. What is a method in JavaScript, then? When a function is a property of an object, it is a method. So, in JavaScript, a method is a function, but not all functions are methods.

Since a function is an object, it can also have properties and methods. To establish whether an object is a function or not, you can useinstanceofas follows:

var workout = function () {};
console.log(workout instanceof Function); // true

What is the difference between a function and other objects in JavaScript, apart from the fact that it is created by theFunctionconstructor? First of all, a function is callable, while other objects are not. Another difference is that a function has a prototype property while other objects don't. We will talk aboutprototypelater.

In JavaScript, you can use a function to create objects with new. In a case such as this, that function serves as a constructor. As a convention, when a function serves as a constructor, it should be capitalized. The following is a simple example of using a function as a User constructor. We will build the User constructor containing more details later:

function User () {
}
var user = new User();

Before we move on, let's see the different ways to create a function in JavaScript. Function declarations and function expressions are the most common ways to create functions. Other than that, you can usenew Function()to create a function. However, this is not recommended due to its poor performance as well as its readability. TheUserfunction in the preceding code snippet is a function declaration. Andworkout is a function expression. The way that a function is created and invoked will affect the execution context that its function body will point to. We will talk about it later.

Objects and classes

In Java, you create a class to represent a concept, for example, a User class. TheUserclass has a constructor, some fields, and methods. And you use its constructor to instantiate aUserobject. And every object in Java is an instance of the associated class that provides code sharing among its instances. You can extend theUserclass to create, for example, aTeamMemberclass.

In JavaScript, there are several ways to create an object:

  • TheObject()constructor method
  • The object literal method
  • The constructor function method
  • The Object.create()method
  • Thecreator function method
  • The ES6 class method

Let's look at each method one at a time.

TheObjectconstructor method looks like this:

// Call the Object constructor with new
var user = new Object();
user.name = 'Sunny';
user.interests = ['Traveling', 'Swimming'];
user.greeting = function () {
  console.log('Hi, I\'m ' + this.name + '.');
};
user.greeting(); // Hi, I'm Sunny.

TheObjectconstructor creates an object wrapper. This is not a recommended approach, even though it is valid in JavaScript. In practice, it is better to use an object literal instead, which makes the code compact.

The object literal method looks like this:

// Create a user with object literal
var user = {
name: 'Sunny',
interests: ['Traveling', 'Swimming'],
greeting: function () {
console.log('Hi, I\'m ' + this.name + '.');
}
}
user.greeting();// Hi, I'm Sunny.

The object literal is a compact syntax to create an object in JavaScript and it is the recommended way of creating an object overnew Object(). Starting from ES5, object literals also support getter and setter accessors, as can be seen here:

var user = {
get role() {
return 'Engineer';
}
} 
user.role;// Engineer

And if you try to assign a value torole, it will stay unchanged because there is no setter accessor defined for the role property.

The constructor function method looks like this:

// Create a constructor function
function User (name, interests) {
this.name = name;
this.interests = interests;
this.greeting = function () {
console.log('Hi, I\'m ' + this.name + '.');
}
}
// Call the constructor with new to create a user object
var user = new User('Sunny', ['Traveling', 'Swimming']);
user.greeting(); // Hi, I'm Sunny.

This syntax is very close to the one in Java. JavaScript is very tolerant, and you can omit the parenthesis when calling the constructor. However, this will not pass any arguments to the constructor, as can be seen here:

var user = new User;
console.log(user.name); // undefined

And again, even though this is valid in JavaScript, it is not recommended to omit the parenthesis.

TheObject.create()method looks like this:

// Use Object.create() method with the prototype of
// User constructor function created above
var user = Object.create(User.prototype, {
name: { value: 'Sunny' },
interests: { value: ['Traveling', 'Swimming']}
}); 
user.greeting(); // Uncaught TypeError: user.greeting() is not a //function

The reasongreeting()is not a function of the user object here is that theObject.create()method creates a new object with the constructor's prototype object. And the greetingfunction is not defined inUser.prototype, or passed in the second argument of Object.create(). To make the user be able to greet, we can either pass the greeting function in the second argument, or we can add it to theUserconstructor's prototype object. The difference is that the first approach only adds the greeting function to the current user object. If you created another user without passing in the greeting function, that user won't have greeting function. On the other hand, adding the function to the prototype object will add the greeting function to all the objects created by that constructor. Let's add it to the User prototype object:

// Add greeting to prototype object
User.prototype.greeting = function () {
console.log('Hi, I\'m ' + this.name + '.');
}
user.greeting(); // Hi, I'm Sunny.

Actually, using a prototype is how a superclass provides methods for subclasses to inherit in JavaScript. We will talk about that in detail later.

The creator function method looks like this:

// Use a creator function with an object as its return value
function createUser (name, interests) {
var user = {};
user.name = name;
user.interests = interests;
user.greeting = function () {
console.log('Hi, I\'m ' + this.name + '.');
};
return user;
}
// Call the creator function with parameters
var user = createUser('Sunny', ['Traveling', 'Swimming']);
user.greeting(); // Hi, I'm Sunny.

The creator function here is a factory method, similar to the static factory method that used to instantiate an object in Java. And it is merely a pattern because underneath it wraps the object creation details inside of the creator function.

The ES6 class method looks like this:

// Create User class
class User {
// Equivalent to User constructor function
constructor (name, interests) {
this.name = name;
this.interests = interests;
}
// Equivalent to User.prototype.greeting
greeting () {
console.log('Hi, I\'m ' + this.name + '.')
}
}
let user = new User('Sunny', ['Traveling', 'Swimming']);
user.greeting(); // Hi, I'm Sunny.

This is very close to the syntax in Java. Instead of using the class declaration, you can also use the class expression to create the class, as follows:

// Use class expression
let User = class {
constructor (name, interests) {
this.name = name;
this.interests = interests;
}
greeting () {
console.log('Hi, I\'m ' + this.name + '.')
} 
}

 

Even though it uses the same keyword, class, class in JavaScript is quite different from the class in Java. For example, there is no static class and no private class in JavaScript. We will talk more aboutclass in the ES6 section.

Objects, properties, and property attributes

In Java, once an object is created, there is (almost) no way to modify its methods during runtime. Java is not a dynamic language. In JavaScript, things are quite different. You can create an object and modify it easily during runtime, such as adding new properties and replacing a method. That's what a dynamic language can do. Actually, that is not the special part. The special part is thatObjectis a language type in JavaScript, like other language types that JavaScript has, which includesUndefined, Null, Boolean, String, Symbol, and Number. Any value in JavaScript is a value of those types.

Note

The undefined type has a single value,undefined. The null type has a single value,null. A Boolean has two values:trueandfalse.

In Java, an object has fields and methods. In JavaScript, an object is logically a collection of properties. A property has a name of the String type and a list of attributes. Attributes, in JavaScript, are used to define and explain the state of a property. There are two types of properties—data properties and access properties.

A data property has four attributes:

  • value, which can be of any JavaScript language type
  • writable, which defines whether a data property can be changed or not
  • enumerable, which defines whether a property can be enumerated by using a for-in statement
  • configurable, which defines whether a property can be deleted, changed to be an access property, changed to be not writable, or whether itsenumerableattribute can be modified

An access property also has four attributes:

  • get accessor, which can be a Function object or undefined
  • set accessor, which can be a Function object or undefined
  • enumerable, which defines whether a property can be enumerated by using a for-in statement
  • configurable, which defines whether a property can be deleted, be changed to be a data property, or whether its other attributes can be modified.

To access a property of an object, you can use dot notation or bracket notation. The dot notation acts the same as how it does in Java. The bracket notation, on the other hand, is quite interesting. In JavaScript, property names must be strings. If you try to use a non-string object as a property name with bracket notation, the object will be casted into a string via itstoString()method, as we can see here:

var obj = {};
obj['100'] = 'one hundred';
// Number 100 will be casted to '100'
console.log(obj[100]);// 'one hundred'
// Both foo and bar will be casted to string '[object Object]'
var foo = {prop: 'f'}, bar = {prop: 'b'};
obj[foo] = 'Foo'
console.log(obj[bar])// 'Foo'

In a nutshell, here is how an object appears, logically:

Figure 1.1: Object, properties, and property attributes

In JavaScript,you can useObject.definePropertyorObject.definePropertiesto modify the properties of an object. Here is how it works:

1.  function User (name, department) {
2.    var _department = department;
3.    var _name = name;
4.    Object.defineProperty(this, 'name', {
5.      value: _name,
6.      writable: true,
7.      enumerable: true,
8.      configurable: false
9.    }); 
10.   Object.defineProperty(this, 'department', {
11.     get: function () {
12.       console.log('Retrieving department');
13.       return _department;
14.     },
15.     set: function (newValue) {
16.       console.log('Updating department value to "' + newValue + '"');
17.       _department = newValue;
18.     },
19.     enumerable: true,
20.     configurable: true
21.   });
24.   Object.defineProperty(this, 'greeting', {
25.     value: function () {
26.       console.log('Hi, I\'m ' + _name + '.');
27.     },
28.     enumerable: false,
29.     configurable: false
30.   }); 
31. } 

As you can see from lines 4 to 9, we use Object.defineProperty to define name as a data property, and its actual data is stored in the internal property _name. In lines 10 to 21, we define department as an access property that has a get accessor and set accessor, and the actual value is kept in _department. In lines 24 to 30, we define greeting property as a data property and its value is a Function object:

32. var user = new User('Sunny', 'Engineering');
33. console.log(user.department);
34. user.department = 'Marketing'; 
35. user.greeting(); 
36. Object.defineProperty(user, 'name', { 
37.   enumerable: false
38. });
39. delete user.name;
40. delete user.department;
41. for (var prop in user) {
42.   console.log(prop);
43. }

In line 32, we create a user object using the User constructor function. In line 33, we access the department property. Since it is a get accessor, the getter function will be invoked and the message Retrieving department will show up in the console before the actual department value. In line 34, we assign a new value to the department property. Since we have defined the set accessor, the setter function will be invoked. In line 35, since the greeting property of user object is defined as a function, we will need to invoke it. In lines 36 to 38, we try to redefine the name property. However, since it is not configurable, JavaScript will throw an error with this. The same is true with regard to line 39, where we try to delete this property. The deletion of department property in line 40 will work since it is configurable. In lines 41 to 43, the only property that will show up in the console is the name property, because the department has been deleted and the greeting property is not enumerable.

Prototype and inheritance

As has been briefly mentioned previously, inheritance in JavaScript is archived by using prototypes of constructor functions. In JavaScript, a prototype is an object that provides shared properties for other objects. And only a function object has a prototype because only a function object is callable and can create other objects. In ES6, arrow functions don't have prototypes. We will discuss that later.

You can think of a function as a factory and its prototype is the specification of the products that the factory manufactures. Every time you call a function with thenewkeyword, you place an order for its product. And the factory will produce it according to how it is specified in the prototype.

Now, let's see how inheritance works in code. We will create another constructor function, calledTeamMemberand it will inherit properties fromUserand also override thegreeting()method and provide a new method calledwork(). Later, we will addeat()method toUserandmove()toObject.

Here is how it is implemented in ES5:

1.function User (name, interests) {
2.this.name = name;
3.this.interests = interests;
4.}
5.User.prototype.greeting = function () {
6.console.log('Hi, I\'m ' + this.name + '.');
7. }

In lines 1 to 4, we create a User constructor function. And what it really does is create a function object using the Function constructor. In JavaScript, you can check who created an object by using its constructor property, which references back to its creator, as follows:

console.log(User.constructor === Function);// true

And once the User constructor function is created, it has a prototype object. And a User prototype object itself is created by the User constructor function, as you can see in the following:

console.log(User.prototype.constructor === User); // true

And in JavaScript, after you create a user object using the User constructor function, that object will have a __proto__ property that references the User prototype object. You can see the link like this:

var user = new User();
console.log(user.__proto__ === User.prototype); // true 

This __proto__ reference serves as a link in the prototype chain. You will see what that means visually later.

Now back to the code. In lines 5 to 7, we create a greeting property on the User prototype. This will create a method that can be inherited by User subclasses. And, as we mentioned earlier, if you define the greeting method inside the User constructor function, subclasses won't see this greeting method. We will see the reason for this shortly:

8.  function TeamMember (name, interests, tasks) {
9.   User.call(this, name, interests);
10.  this.tasks = tasks;
11. }
12. TeamMember.prototype = Object.create(User.prototype);
13. TeamMember.prototype.greeting = function () {
14. console.log('I\'m ' + this.name + '. Welcome to the team!');
15. };
16. TeamMember.prototype.work = function () {
17. console.log('I\'m working on ' + this.tasks.length + ' tasks');
18. };

In lines 8 to 13, we create a TeamMember constructor function, and inside it, we invoke the User constructor function's call() method, which is inherited from the Function object to chain constructors, which is similar to invoking super() in a constructor of a Java class. One difference is that the call() method's first argument must be an object, which serves as the execution context. In our example, we use this as the execution context. Inside the call() method, thename and interests properties are initialized. And then, we add an additional property, tasks, to TeamMember.

In line 12, we use Object.create() to create a TeamMember prototype object using the User prototype object. In this way, objects created by the TeamMember constructor function will have the properties of the User prototype object and each team member object will have a __proto__ property that links to this TeamMember prototype.

In lines 13 to 15, we override the original greeting() method of the User prototype so that objects created by the TeamMember constructor function will have different behavior. This will not affect the User prototype object since they are essentially two different objects, even though these two prototype objects have the same constructor, as you can see in the following:

console.log(User.prototype === TeamMember.prototype);// false
console.log(User.prototype.constructor === TeamMember.prototype.constructor); // true

In lines 16 to 18, we add a new method, work()to the TeamMember prototype object. In this way, objects created by the TeamMember constructor function will have this additional behavior:

19. var member = new TeamMember('Sunny', ['Traveling'],
20. ['Buy three tickets','Book a hotel']);
21. member.greeting();// I'm Sunny. Welcome to the team!
22. member.work();// I'm working on 2 tasks
23
24. console.log(member instanceof TeamMember); // true
25. console.log(member instanceof User); // true
26. console.log(member instanceof Object); // true
27
28. User.prototype.eat = function () {
29. console.log('What will I have for lunch?');
30. };
31. member.eat(); // What will I have for lunch? 
32
33. // Add a method to the top
34. Object.prototype.move = function () {
35. console.log('Every object can move now');
36. };
37. member.move();// Every object can move now
38. var alien = {};
39. alien.move(); // Every object can move now
40. User.move();// Even the constructor function

In line 19, we create a member object using theTeamMemberconstructor function. Line 21shows that the member object can greet in a different way to objects created by theUserconstructor function. And line 22shows that the member object can work.

Lines 24 to 26 show that the member object is an instance of all its superclasses.

In lines 28 to 30, we add theeat()method to theUserprototype, and even though the member object is created before this, as you can see from line 31, it also inherits that method.

In line 34, we add themove()method to theObjectprototype, which might turn out to be a really bad idea since, as you can see in lines 37 to 40, every object can move now, even those constructor function objects.

We just create an inheritance chain starting from Object | User | TeamMember. The prototype link is the key to this chain. Here is how it appears:

Figure 1.2: Prototype-based inheritance

On the left-hand side are the constructor functions, and on the right-hand side are their corresponding prototypes. The bottom is themember object. As you can see, the member object's __proto__ property references the prototype object of TeamMember. And the __proto__ property of the TeamMember prototype object itself references the prototype object of User. And the __proto__ property of the User prototype object references the top level, which is the prototype object of ObjectTo verify the link, you can do something like this:

console.log(member.__proto__ === TeamMember.prototype); // true
console.log(TeamMember.prototype.__proto__ === User.prototype); // true
console.log(User.prototype.__proto__ === Object.prototype); // true

So, be really careful with the __proto__ property. If you, let's say, accidentally change this property to something else, the inheritance will break:

User.prototype.__proto__ = null;
member.move(); // Uncaught TypeError: member.move is not a function 
console.log(member instanceof Object); // false (Oops!)

It is recommended to useObject.prototype.isPrototypeof()to check the prototype chain:

TeamMember.prototype.isPrototypeOf(member); // true

With the inheritance relationship map showing in the preceding diagram, you can easily see how JavaScript resolves a property through the prototype chain. For example, when you access a member object's name property, JavaScript finds that it is on the object itself and will not go up the chain. And when you access themove()method, JavaScript will go up the chain and check whether the TeamMemberprototype has it and, since it doesn't, JavaScript will keep going up until it finds the method in the Objectprototype. You can use an object'shasOwnProperty()method to check whether that object has a property as its own instead of inherited through the prototype chain:

member.hasOwnProperty('name'); // true
member.hasOwnProperty('move'); // false

Scope and closure

Scope is about the accessibility of variables. In Java, basically, a set of curly brackets {} defines a scope, including class-level scope, method-level scope, and block-level scope. 

Let's take a look at the following example in Java:

1.public class User { 
2.  private String name; 
3.private List<String> interests; 
4.
5.public User (String name, List<String> interests) {
6.this.name = name;
7.this.interests = interests;
8.}
9. 
10. // Check if a user is interested in something
11. public boolean isInterestedIn(String something) {
12. boolean interested = false;
13. for (int i = 0; i < interests.size(); i++) {
14. if (interests.get(i).equals(something)) {
15. interested = true;
16. break;
17. } 
18. } 
19. return interested;
20. }
21. }

The name and interests properties are in the class-level scope and they are accessible anywhere within the class. Theinterested variable, defined in line 12, is in method-level scope, and it is only accessible within that method. Thei variable, in line 13, is defined within the for loop and it is block-level scope only accessible within the for loop block. In Java, the scope of the variables is static and can be determined by the compiler.

In JavaScript, the scope of the variables is much more flexible. There is global scope and function scope, and block scope with theletandconst keywords, which were introduced in ES6, which we will talk about later.

Let's look at the following JavaScript example:

1.function bookHotel (city) {
2.var availableHotel = 'None';
3.for (var i=0; i<hotels.length; i++) {
4.var hotel = hotels[i];
5.if (hotel.city === city && hotel.hasRoom) {
6.availableHotel = hotel.name;
7.break;
8.}
9.}
10. // i and hotel still accessible here
11. console.log('Checked ' + (i+1) + ' hotels');// Checked 2 hotels
12. console.log('Last checked ' + hotel.name);// Last checked Hotel B
13. {
14. function placeOrder() {
15. var totalAmount = 200;
16. console.log('Order placed to ' + availableHotel);
17. }
18. } 
19. placeOrder();
20. // Not accessible
21. // console.log(totalAmount);
22. return availableHotel;
23. }
24. var hotels = [{name: 'Hotel A', hasRoom: false, city: 'Sanya'},
{name: 'Hotel B', hasRoom: true, city: 'Sanya'}];
25. console.log(bookHotel('Sanya')); // Hotel B
26. // Not accessible
27. // console.log(availableHotel);

Thehotels variabledeclared in line 24,is in global scope and is accessible anywhere, such as inside thebookHotel()function, even though the variable is defined after the function. 

TheavailableHotelvariable declared in line 2 is in the scope of the bookHotel()function. It is a local variable and is not accessible outside of the function, as you can see from line 27. Inside its enclosing function, the availableHotelvariable is accessible anywhere, even the nestedplaceOrder() function, as you can see in line 16. This is called closure. A closure is formed when a function is nested inside another function. And no matter how deeply you have nested a function, it will still have access to its parent function's scope, and all the way to the top scope, which is global scope. ThetotalAmount variable, defined in line 15, is a local variable of theplaceOrder()function.

And in lines 3 and 4, we defined theiandhotel variables with thevar keyword. Even though it is in a for loop block, it is still accessible outside the block, as shown in lines 11 and 12. In ES6, you can use the let keyword to define i and hotel, which will put these two variables in for loop block scope. We will talk more about this later.

The this keyword

In Java,thisalways refers to the current object. It is solid. In JavaScript, this behaves differently. In short, this refers to the current execution context, which is an object. And the way that JavaScript runtime determines the current execution context is much more complex than in Java.

In JavaScript, there is an execution context stack, logically formed from active execution contexts. When control is transferred from one executable code to another, control enters the new executable code's execution context, which becomes the current execution context, or what is referred to as the running execution context. At the bottom of the stack is the global context, where everything begins, just like the main method in Java. The current execution context is always at the top of the stack.

What is the executable code? There are three types in JavaScript:

  • Global code, which is the code that runs from the place where a JavaScript program starts. In a browser, it is wherewindowlives. And when you open a browser console and type invar user = new User(), you are writing global code.
  • Eval code, which is a string value passed in as the argument of the built-ineval()function (do not use theeval()function unless you really know what you're doing).
  • Function code, which is the code parsed as the body of a function. However, it doesn't mean all the code written inside a function is function code.

Now, to understand this better, let's look at the following example:

1.function User (name) {
2.console.log('I\'m in "' + this.constructor.name + '" context.');
3.this.name = name;
4.this.speak = function () {
5.console.log(this.name + ' is speaking from "' +
6.this.constructor.name + '" context.');
7.var drink = function () {
8.console.log('Drinking in "' + this.constructor.name + '"');
9.} 
10. drink(); 
11. }; 
12. function ask() {
13. console.log('Asking from "' + 
14. this.constructor.name + '" context.');
15. console.log('Who am I? "'+ this.name + '"');
16. }
17. ask();
18. }
19. var name = 'Unknown';
20. var user = new User('Ted');
21. user.speak();

Note

Since an execution context is, in fact, an object, here we use its .constructor.name to see what the context is. And if you run the preceding code in the node command line, it will be Object instead of Window.

If you run the code from Chrome console, the output will be the following:

// I'm in "User" context.
// Asking from "Window" context.
// Who am I? "Unknown"
// Ted is speaking from "User" context.
// Drinking in "Window"

First, let's see which part is global code and which part is function code.TheUserfunction declaration, and lines 19 to 21, are global code.Lines2 to 17 are the function code of theUserfunction. Well, not exactly. Lines 5 to 10, except line 8, are the function code of thespeak()method. Line 8 is the function code of thedrink()function. Lines 13 and 14 are the function code of theask()function.

Before we review the output, let's revisit the two commonly used ways of creating a function—function declarations and function expressions. When the JavaScript engine sees a function declaration, it will create afunctionobject that is visible in the scope in which the function is declared. For example, line 1 declares theUserfunction, which is visible in global scope. Line 12 declares theask()function, which is visible inside the scope of theUserfunction. And line 4 is a function expression that creates thespeak()method. On the other hand, in line 7, we use a function expression to create adrinkvariable. It is different from the speak() method created in line 4. Even though it is also a function expression, thedrinkvariable is not a property of an object. It is simply visible inside thespeak()method.

In JavaScript, scope and execution context are two different concepts. Scope is about accessibility, while the execution context is about the ownership of running an executable code. Thespeak()method and theask()function are in the same scope, but they have different execution contexts. When theask()function is executed, as you can see from the output, it has global context and thenameproperty resolves to the valueUnknown, which is declared in global scope. And when thespeak()method is executed, it has theusercontext. As you can see from the output, its access to thenameproperty resolves toTed. This can be quite confusing to Java developers. So what happened behind the scenes?

Let's review the preceding example from the JavaScript engine's view.When the JavaScript engine executes line 20, it creates auserobject by calling theUserconstructor function. And it will go into the function body to instantiate the object. When the control flows from the global code to the function code, the execution context is changed to theuserobject. And that's why you seeI'm in "User" context.in the output. And during the instantiation, JavaScript engine will not execute the code inside thespeak()method because there is no invoking yet. It executes theask()function when it reaches line 17. At that time, the control flows from the function code of theUserconstructor function to theask()function. Because theask()function isn't a property of an object, nor it is invoked by theFunction.call()method, which we will talk about later, the global context becomes the execution context. And that's why you seeAsking from "Window" context.andWhere am I? "Unknown"in the output. After the instantiation of the user object, the JavaScript engine goes back to execute line 21 and invokes thespeak()method on theuserobject. Now, the control flows into thespeak()method and theuserobject becomes the execution context. And that's why you seeTed is speaking from "User" context.in the output. When the engine executes the drink()function, it resolves back to the global context as the execution context. And that is why you seeDrinking in "Window" context.in the output.

As mentioned earlier, the execution context is affected by the way a function is created as well as by how it is invoked. What does that mean?Let's change line 16 fromask()toask.call(this). And if you run the preceding example again from Chrome's console, you can see the following output:

...
Asking from "User" context.
Who am I? "Ted"
...

And if you type inuser.speak.apply({name: 'Jack'}) into the console, you will see something interesting, like this:

Jack is speaking from "Object" context.
Drinking in "Window" context.

Or, if you change line 17 toask.bind(this)(), you can see the answer to the question"Who am I?"is also"Ted"now.

So, what are thesecall(),apply(), andbind()methods? It seems that there are no definitions of them in the preceding example. As you might remember, every function is an object created by theFunctionobject. After typing in the following code into the console, you can see that thespeak()function inherits the properties fromFunction prototype, including thecall(),apply(), andbind()methods:

console.log(Function.prototype.isPrototypeOf(user.speak)); // true
user.speak.hasOwnProperty('apply');// false
user.speak.__proto__.hasOwnProperty('apply');// true

Thecall()method and theapply()method are similar. The difference between these two methods is that thecall()method accepts a list of arguments, while theapply()method accepts an array of arguments. Both methods take the first argument as the execution context of the function code. For example, in user.speak.apply({name: 'Jack'}), the {name: 'Jack'} object will be the execution context of the speak() method of user. You can think of thecall()andapply()methods as a way of switching execution context.

And thebind()method acts differently from the other two. What thebind()method does is create a new function that will be bound to the first argument that is passed in as the new function’s execution context. The new function will never change its execution context even if you usecall()orapply()to switch execution context. So, whatask.bind(this)()does is create a function and then execute it immediately. Besides executing it immediately, you can assign the new function to a variable or as a method of an object.

To wrap up, there are four ways to invoke a function:

  • Constructor function invoking:new User()
  • Direct function invoking:ask()
  • Method invoking:user.speak()
  • Switching context invoking:ask.call(this)orask.apply(this)

When we are talking about constructor function invoking, the presence of this inside the function body, except those instances that are wrapped by functions of the other three types of invoking, refers to the object that the constructor creates.

When we are talking about direct function invoking, the presence of this inside the function body, except those instances that are wrapped by functions of the other three types of invoking, refers to the global context.

When we are talking about method invoking, the presence of this inside the function body, except those instances that are wrapped by functions of the other three types of invoking, refers to the object that the method belongs to.

When we are talking about switching context invoking, the presence of this inside the function body, except those instances that are wrapped by functions of the other three types of invoking, refers to the object that passed in as the first argument of thecall()method.

Hoisting

This is another thing that Java developers usually easily get confused. Hoisting is a metaphor for the way that JavaScript interpreters will lift function declarations and variable declarations to the top of their containing scope. So, In JavaScript, you can see something that is obviously wrong and will definitely break the compilation if you write that in Java, but it is totally valid in JavaScript.

Let’s see an example:

1. travel = 'No plan';
2. var travel;
3. console.log(travel); // Is the output: undefined?
4.
5. function travel() {
6. console.log('Traveling');
7. }
8. travel(); // Is the output: Traveling?

What will the output be when the JavaScript engine executes line 3 and 8? It is notundefined, and notTraveling. Line 3 is "No plan" and line 8 is"Uncaught TypeError".

Here is what the JavaScript interpreter sees when it processes the preceding code:

1.// Function declaration moved to the top of the scope
2.function travel() {
3.console.log('Traveling');
4.}
5.// Variable declaration moved under function declaration
6.var travel;
7.travel = 'No plan';
8.
9.console.log(travel);// No plan
10. travel();// Uncaught TypeError: travel is not a function

JavaScript interpreter moves the function declarations up to the top, followed by variables declarations. Function expressions, for example,var travel = function(){}, are not lifted to the top as function declarations because they are also variable declarations.

Let's see another example:

1.function workout() { 
2.goToGym();// What will the output be?
3.var goToGym = function () {
4.console.log('Workout in Gym A');
5.}
6.return; 
7.function goToGym() {
8.console.log('Workout in Gym B');
9.}
10. }
11. workout();

What will the output be when line 2 is executed? It is"Workout in Gym B.". And here is what the interpreter sees when it processes the code:

1.function workout() {
2.function goToGym() {
3.console.log('Workout in Gym B');
4.}
5.var goToGym;
6.goToGym();
7.goToGym = function () {
8.console.log('Workout in Gym A');
9.}
10. return;
11. }
12. workout();

The interpreter moves the function declaration to the top of the scope and then the variable declaration, but not the assignment. So whengoToGym()is executed, the assignment expression to the new function hasn't happened yet.

To wrap up, before executing, JavaScript interpreters will move the function declarations, and then variable declarations, without assignment expressions, up to the top of the containing scope. And it is valid to put function declarations after the return statement.

 

 

ES6 basics


ES6 (short for ECMAScript 2015), is the sixth version of ECMAScript, which is a general-purpose, cross-platform, and vendor-neutral programming language. ECMAScript is defined in ECMA Standard (ECMA-262) by Ecma International. Most of the time, ECMAScript is more commonly known by the name JavaScript.

Understanding ES6 is the key to writing web applications using modern JavaScript. Owing to the scope of this book, we will only cover the basics of new featuresintroduced in ES6 here as you will see them in the rest of the book.

Block scoping, let, and const

As mentioned earlier, in ES6, you can useletto define variables or useconstto define constants, and they will have block-level scope. And in the same scope, you can not redefine a variable usinglet. Also, you cannot access a variable or a constant that is defined withletorconstbefore its declaration, since there is no variable hoisting withletorconst.

Let's see the followingworkout example:

1.function workout() {
2.let gym = 'Gym A';
3. 
4.const gymStatuses = {'Gym A': 'open', 'Gym B': 'closed'};
5.for (let gym in gymStatuses) {
6.console.log(gym + ' is ' + gymStatuses[gym]);
7.}
8.
9.{
10. const gym = 'Gym B';
11. console.log('Workout in ' + gym);
12. // The following will throw TypeError
13. // gym = 'Gym C';
14. }
15. 
16. console.log('Workout in ' + gym);
17. 
18. {
19. function gym () {
20. console.log('Workout in a separate gym');
21. }
22. gym();
23. }
24. 
25. if (gymStatuses[gym] == 'open') {
26. let exercises = ['Treadmill', 'Pushup', 'Spinning'];
27. }
28. // exercises are no longer accessible here
29. // console.log(exercises);
30. 
31. try {
32. let gym = 'Gym C'; 
33. console.log('Workout in ' + gym);
34. throw new Error('Gym is closed');
35. } catch (err) {
36. console.log(err);
37. let gym = 'Gym D';
38. console.log('Workout in ' + gym);
39. }
40. }
41. workout();

In line 2, we declare thegym variable, and it is visible in theworkout() function body. In line 5, we declare thegymvariable within the for loop block. It shadows thegymvariable declared in line 2 and is only accessible within that for loop block.

In lines 9 to 14, we declare a new scope using a block statement. The gymconstant declared in line 10 is only accessible within that scope. And as you can see in line 13, assigning a value to a constant will causeTypeError.

In line 16, thegymvariable is back to the one declared in line 2. In lines 18 to 23, we declare thegymfunction and it is only accessible within that block.

In line 26, we define the exercisesvariable within theifblock. And as you can see from line 29, it is no longer accessible outside theifblock.

In lines 31 to 39, we declare a try-catch block. As you can see in lines 32 and 37, the try block and catch block are in different scopes.

To wrap up, usingletandconst, we can archive block-level scope with for loop blocks, if blocks, try-catch blocks, and block statements, as well as switch blocks.

Classes

ES2015 introduces classes, which is primarily a syntactical sugar over prototype-based inheritance. With the class syntax, you can create constructors, extends from a superclass, and create static methods, as well as getters and setters.

Let's see the following example that uses the class syntax to implementUser, andTeamMember:

1.class User {
2.constructor(name, interests) {
3.this.name = name;
4.this.interests = interests;
5.}
6.greeting () {
7.console.log('Hi, I\'m ' + this.name + '.');
8.}
9.get interestsCount () {
10. return this.interests ? this.interests.length : 0;
11. }
12. }

In lines 1 to 12, we define class Userwhich accepts two arguments via its constructor. It has a greeting() method and aninterestsCount getter:

13. class TeamMember extends User {
14. constructor(name, interests) {
15. super(name, interests);
16. this._tasks = [];
17. this._welcomeText = 'Welcome to the team!';
18. }
19. greeting () {
20. console.log('I\' m ' + this.name + '. ' + this._welcomeText);
21. }
22. work () {
23. console.log('I\' m working on ' + this._tasks.length + ' 
        tasks.')
24. }
25. set tasks (tasks) {
26. let acceptedTasks = [];
27. if (tasks.length > TeamMember.maxTasksCapacity()) {
28. acceptedTasks = tasks.slice(0, 
          TeamMember.maxTasksCapacity());
29. console.log('It\'s over max capacity. Can only take two.');
30. } else {
31. acceptedTasks = tasks;
32. } 
33. this._tasks = this._tasks.concat(acceptedTasks);
34. }
35. static maxTasksCapacity () {
36. return 2;
37. }
38. }

In lines 13 to 38, we create a TeamMember class to extend from User. In its constructor, it calls the constructor of the User with super to instantiate the properties of name and interests. We also define two additional properties, _tasks and _welcomeText. The preceding underscore suggests that they are regarded as private properties and should not be changed directly from outside. However, nothing is private in JavaScript. You can still access these properties, for example, member._tasks, and member._welcomeText.

We override the greeting() method of user in line 20 and add a newwork() method in line 22. In lines 25 to 34, we define a setter tasks, inside which we access the maxTasksCapacity() static method of TeamMember:

39. let member = new TeamMember('Sunny', ['Traveling']);
40. member.greeting(); // I' m Sunny. Welcome to the team!
41. member.tasks = ['Buy three tickets', 'Book a hotel', 'Rent a car'];
// It's over max capacity. Can only take two.
42. member.work(); // I' m working on 2 tasks.
43. console.log(member.interestsCount); // 1
44. member.interestsCount = 2;// This won’t save the change
45. console.log(member.interestsCount); // 1
46. console.log(member.tasks); // undefined

As you can see, in lines 39 to 43, the member object has all the features of the User class and TeamMember, working as expected. In lines 44 to 45, we try to make changes tomember.interestsCount, but it won't work because there is no setter defined. And line 46 shows that accessingmember.tasks results in undefined because we didn't define a getter for it.

Note

You cannot use member.constructor to access the constructor of the TeamMember defined in line 14. It is for accessing thememberobject’s constructor function, in this case,TeamMember.

And now let's see how to add a new method, eat(), to the Userclass:

User.prototype.eat = function () {
console.log('What will I have for lunch?');
};
member.eat();// What will I have for lunch?

You still need to add it to the prototype object ofUser. If you add it directly toUseras follows, you will getTypeError:

User.sleep = function () {
console.log('Go to sleep');
};
member.sleep();// Uncaught TypeError: member.sleep is not a function
User.sleep();// Go to sleep

It is because as a result of writing in this way that you addsleepas a property of theUserclass itself or, more precisely, the Userconstructor function itself. And you might have already noticed thatsleepbecomes a static method of theUserclass. When using the class syntax, when you define a method, behind the scene, JavaScript adds it to its prototype object, and when you define a static method, JavaScript adds it to the constructor function:

console.log(User.prototype.hasOwnProperty('eat'));// true
console.log(User.hasOwnProperty('sleep'));// true

Enhanced object literals

In ES6, object literals support setting prototypes, shorthand assignments, defining methods, making super calls, and computing properties with expressions.

Let's see the following example, which creates anadvisorobject with a TeamMemberobject as its prototype:

1.const advice = 'Stay hungry. Stay foolish.';
2. 
3.let advisor = {
4.__proto__: new TeamMember('Adam', ['Consulting']), 
5.advice,
6.greeting () {
7.super.greeting();
8.console.log(this.advice); 
9.},
10.[advice.split('.')[0]]: 'Always learn more'
11. };

Line 4, assigning the object of TeamMember to theadvisorobject's__proto__property makesadvisoran instance ofTeamMember:

console.log(TeamMember.prototype.isPrototypeOf(advisor));// true
console.log(advisor instanceof TeamMember);// true

Line 5 is a shorthand assignment ofadvice:advice. Line 7 is creating thegreeting()method of TeamMember, inside which it will invoke the greeting method of TeamMember:

advisor.greeting(); // I' m Adam. Welcome to the team!
// Stay hungry. Stay foolish.

In line 10, theStay hungryproperty is calculated with bracket notation. And to access this property, in this case, because the property name contains a space, you need to use bracket notation, like this—advisor['Stay hungry'].

Arrow functions

ES6 introduces arrow functions as a function shorthand, using=>syntax. Arrow functions support statement block bodies as well as expression bodies. When using an expression body, the expression's result is the value that the function returns.

An arrow syntax begins with function arguments, then the arrow=>, and then the function body. Let's look at the following different variations of arrow functions. The examples are written in both ES5 syntax and ES6 arrow function syntax:

const fruits = [{name: 'apple', price: 100}, {name: 'orange', price: 80}, {name: 'banana', price: 120}];

// Variation 1
// When no arguments, an empty set of parentheses is required
var countFruits = () => fruits.length;
// equivalent to ES5
var countFruits = function () {
return fruits.length;
}; 

// Variation 2
// When there is one argument, parentheses can be omitted.
// The expression value is the return value of the function.
fruits.filter(fruit => fruit.price > 100);
// equivalent to ES5
fruits.filter(function(fruit) {
return fruit.price > 100;
});

// Variation 3
// The function returns an object literal, it needs to be wrapped
// by parentheses.
var inventory = fruits.map(fruit => ({name: fruit.name, storage: 1}));
// equivalent to ES5
var inventory = fruits.map(function (fruit) {
return {name: fruit.name, storage: 1};
});

// Variation 4
// When the function has statements body and it needs to return a 
// result, the return statement is required
var inventory = fruits.map(fruit => {
console.log('Checking ' + fruit.name + ' storage');
return {name: fruit.name, storage: 1};
});
// equivalent to ES5
var inventory = fruits.map(function (fruit) {
console.log('Checking ' + fruit.name + ' storage');
return {name: fruit.name, storage: 1};
});

There is an additional note regarding variation 3. When an arrow function uses curly brackets, its function body needs to be a statement or statements:

var sum = (a, b) => { return a + b };
sum(1, 2); // 3

The sum function won't work as expected when it is written like this:

var sum = (a, b) => { a + b };
sum(1, 2);// undefined
// Using expression will work
var sum = (a, b) => a + b;
sum(1, 2);// 3

Arrow functions have a shorter syntax and also many other important differences compared with ES5 function. Let's go through some of these differences one by one.

No lexical this

An arrow function does not have its ownthis. Unlike an ES5 function, that will create a separate execution context of its own, an arrow function uses surrounding execution context. Let's see the following shopping cart example:

1.var shoppingCart = {
2.items: ['Apple', 'Orange'],
3.inventory: {Apple: 1, Orange: 0},
4.checkout () {
5.this.items.forEach(item => {
6.if (!this.inventory[item]) {
7.console.log('Item ' + item + ' has sold out.');
8.} 
9.}) 
10. }
11. }
12. shoppingCart.checkout();
13. 
14. // equivalent to ES5
15. var shoppingCart = {
16. items: ['Apple', 'Orange'],
17. inventory: {Apple: 1, Orange: 0},
18. checkout: function () {
19. // Reassign context and use closure to make it 
20. // visible to the callback passed to forEach
21. var that = this
22. this.items.forEach(function(item){
23. if (!that.inventory[item]) {
24. console.log('Item ' + item + ' has sold out.');
25. } 
26. }) 
27. }
28. }
29. shoppingCart.checkout();

In line 6,this refers to theshoppingCartobject itself, even it is inside the callback of theArray.prototype.forEach()method. As you can see in line 21, with the ES5 version, you need to use closure to keep the execution context available to the callback function.

And because an arrow function does not have a separate execution context, when it is invoked with Function.prototype.call(),Function.prototype.apply(), orFunction.prototype.bind()method, the execution context that passed in as the first argument will be ignored. Let's take a look at an example:

1. var name = 'Unknown';
2. var greeting = () => {
3. console.log('Hi, I\'m ' + this.name); 
4. };
5. greeting.call({name: 'Sunny'});// I'm Unknown
6. greeting.apply({name: 'Tod'}); // I'm Unknown
7. var newGreeting = greeting.bind({name: 'James'});
8. newGreeting(); // I'm Unknown

As you can see from line 3, in an arrow function,thisalways resolves to its surrounding execution context. Thecall(),apply(), orbind() method has no effect on its execution context.

Note

Unlike ES5 functions, arrow functions do not have their ownargumentsobject. Thearguments object is a reference to the surrounding function'sargumentsobject.

Because arrow functions use its surrounding execution context, they are not suitable for defining methods of objects. 

Let's see the following shopping cart example, which uses an arrow function for the checkout:

1.var shoppingCart = {
2.items: ['Apple', 'Orange'],
3.inventory: {Apple: 1, Orange: 0},
4.checkout: () => {
5.this.items.forEach(item => {
6.if (!this.inventory[item]) {
7.console.log('Item ' + item + ' has sold out.');
8.} 
9.}) 
10. }
11. }
12. shoppingCart.checkout();

In line 4, we changecheckoutto an arrow function. And because an arrow function uses its surrounding execution context,thisin line 5 no longer references the shoppingCartobject and it will throwUncaught TypeError: Cannot read property 'forEach' of undefined.

The preceding shopping cart example is written with object literals. Arrow functions do not work well when defining object methods using a prototype object either. Let's see the following example:

1.class User {
2.constructor(name) {
3.this.name = name;
4.}
5.}
6.User.prototype.swim = () => {
7.console.log(this.name + ' is swimming');
8.};
9.var user = new User();
10. console.log(user.swim()); //is swimming

As you can see from the output, in line 7this does not reference theuserobject. In this example, it references the global context.

No prototype object

Arrow functions do not have prototype objects, hence, they are not constructor functions. And they cannot be invoked with anewoperator. An error will be thrown if you try that. Here's an example:

const WorkoutPlan = () => {};
// Uncaught TypeError: WorkoutPlan is not a constructor
let workoutPlan = new WorkoutPlan(); 
console.log(WorkoutPlan.prototype);// undefined

Default parameter value

In ES6, you can define the default values of a function's parameters. This is quite a useful improvement because the equivalent implementation in ES5 is not only tedious but also decreases the readability of the code.

Let's see an example here:

const shoppingCart = [];
function addToCart(item, size = 1) {
shoppingCart.push({item: item, count: size});
}
addToCart('Apple'); // size is 1
addToCart('Orange', 2); // size is 2

In this example, we give the parameter size a default value, 1. And let's see how we can archive the same thing in ES5. Here is an equivalent of the addToCartfunction in ES5:

function addToCart(item, size) {
size = (typeof size !== 'undefined') ? size : 1;
shoppingCart.push({item: item, count: size});
}

As you can see, using the ES6 default parameter can improve the readability of the code and make the code easier to maintain.

Rest parameters

In ES5, inside a function body, you can use theargumentsobject to iterate the parameters of the function. In ES6, you can use rest parameters syntax to define an indefinite number of arguments as an array.

Let's see the following example:

1.// Using arguments in ES5
2.function workout(exercise1) {
3.var todos = Array.prototype.slice.call(arguments, 
      workout.length);
4.console.log('Start from ' + exercise1);
5.console.log(todos.length + ' more to do');
6.}
7.// equivalent to rest parameters in ES6
8.function workout(exercise1, ...todos) {
9.console.log('Start from ' + exercise1);// Start from 
    //Treadmill
10. console.log(todos.length + ' more to do'); // 2 more to do
11. console.log('Args length: ' + workout.length); // Args length: 1
11. }
12. workout('Treadmill', 'Pushup', 'Spinning');

In line 8, we define a rest parametertodos. It is prefixed with three dots and is the last named parameter of theworkout()function. To archive this in ES5, as you can see in line 3, we need to slice theargumentsobject. And in line 11, you can see that the rest parametertodos does not affect the length of the argument in the workout ()function.

Spread syntax

In ES6, when the three dot notation (...) is used in a function declaration, it defines a rest parameter; when it is used with an array, it spreads the array's elements. You can pass each element of the array to a function in this way. You can also use it in array literals.

Let's see the following example:

1. let urgentTasks = ['Buy three tickets'];
2. let normalTasks = ['Book a hotel', 'Rent a car'];
3. let allTasks = [...urgentTasks, ...normalTasks];
4. 
5. ((first, second) => {
6. console.log('Working on ' + first + ' and ' + second)
7. })(...allTasks);

In line 3, we use spread syntax to expand theurgentTasksarray and thenormalTasksarray. And in line 7, we use spread syntax to expand theallTasksarray and pass each element of it as arguments of the function. And the firstargument has the valueBuy three ticketswhile thesecondargument has the valueBook a hotel.

Destructuring assignment

In ES6, you can use the destructuring assignment to unpack elements in an array, characters in a string, or properties in an object and assign them to distinct variables using syntax similar to array literals and object literals. You can do this when declaring variables, assigning variables, or assigning function parameters.

Object destructuring

First of all, let's see an example of object destructuring:

1. let user = {name:'Sunny', interests:['Traveling', 'Swimming']};
2. let {name, interests, tasks} = user;
3. console.log(name); // Sunny
4. console.log(interests);// ["Traveling", "Swimming"]
5. console.log(tasks);// undefined

As you can see, thenameandinterestsvariables defined in line 2 pick up the values of the properties with the same name in theuserobject. And thetasksvariable doesn't have a matching property in theuserobject. Its value remains as undefined. You can avoid this by giving it a default value, like this:

let {name, interests, tasks=[]} = user;
console.log(tasks)// []

Another thing you can do with object destructuring is that you can choose a different variable name. In the following example, we pick the value of the name property of theuserobject and assign it to thefirstName variable:

let {name: firstName} = user;
console.log(firstName)// Sunny

Array destructuring

Array destructuring is similar to object destructuring. Instead of using curly brackets, array destructuring uses brackets to do the destructuring. Here is an example of array destructuring:

let [first, second] = ['Traveling', 'Swimming', 'Shopping'];
console.log(first); // Traveling
console.log(second);// Swimming

You can also skip variables and only pick the one that you need, like the following:

let [,,third, fourth] = ['Traveling', 'Swimming', 'Shopping'];
console.log(third); // Shopping
console.log(fourth);// undefined

As you can see, we skip the first two variables and only require the third and the fourth. However, in our case, thefourth variable doesn't match any elements in the array and its value remains asundefined. Also, you can give it a default value, like this:

let [,,third, fourth = ''] = ['Traveling', 'Swimming', 'Shopping'];
console.log(fourth);// an empty string

Nested destructuring

Similar to using object literals and array literals to create complex nested data structures with a terse syntax, you can use a destructuring assignment to pick up variables in a deeply nested data structure.

Let's see the following example, in which we only need the user's second interest:

1. let user = {name:'Sunny', interests:['Traveling', 'Swimming']};
2. let {interests:[,second]} = user;
3. console.log(second);// Swimming
4. console.log(interests); // ReferenceError

In line 2, even though we putinterestsin the destructuring assignment, JavaScript doesn't really declare it. As you can see in line 4, accessing it will raiseReferenceError. What happens here is that JavaScript uses the part on left side of the colon (:), in this case,interests, to extract the value of the property of the same name, and uses the part on the right side to do further destructuring assignments. If you want to extract the interests property, as demonstrated previously, you need to write it in like this: let {interests} = user;.

Here is another example in which the name property of the second element in an array is destructured:

const fruits = [{name:'Apple', price:100},{name:'Orange', price:80}];
let [,{name:secondFruitName}] = fruits;
console.log(secondFruitName); // Orange

Rest elements

You can use the same syntax of the rest parameters in the destructuring assignment to put the remainder of the elements of an array into another array. Here is an example:

let [first, ...others] = ['Traveling', 'Swimming', 'Shopping'];
console.log(others); // ["Swimming", "Shopping"]

As you can see, the second and third items of the array have been copied into the others variable. We can use this syntax to copy an array. However, this is only a shallow clone. When the elements of the array are objects, changes to an object's property of the copied array will be seen in the original array because essentially, the elements of both arrays reference the same object. Here is an example:

1. const fruits = [{name:'Apple', price:100},{name:'Orange', price:80}];
2. let [...myFruits] = fruits;
3. console.log(myFruits[0].name);// Apple
4. myFruits.push({name:'Banana', price:90});
5. console.log(myFruits.length); // 3
6. console.log(fruits.length); // 2
7. myFruits[0].price = 110;
8. console.log(fruits[0].price); // 110

As you can see in line 2, we use the destructuring assignment syntax to copy the fruitsarray into the myFruits array. And adding a new item to the copied array doesn't affect the original array, as you can see in lines 4 to 6. However, changing the value of the price property from the copied array will be also seen in the original array.

Function parameters destructuring

You can apply a destructuring assignment to function parameters as well. Let's see the following example:

1. function workout({gym}, times) {
2. console.log('Workout in ' + gym + ' for ' + times + ' times');
3. }
4. let thisWeek = {gym: 'Gym A'};
5. workout(thisWeek, 2); // Workout in Gym A for 2 times

As you can see, in line 1, we use object destructuring syntax to extract the gym variable from the first argument of the workout() function. In this way, the argument passed to the workout() function cannot be null or undefined. Otherwise, TypeError will be thrown. You can pass a number, a string, an array, or a function to the workout() function and JavaScript won't complain about it, although you will get undefined as a value for the gym variable.

Let's look at another example, in which we will perform a further destructuring of a destructured variable:

1. function workout({gym, todos}) {
2. let [first] = todos;
3. console.log('Start ' + first + ' in ' + gym);
4. }
5. let today = {gym: 'Gym A', todos: ['Treadmill']};
6. workout(today); // Start Treadmill in Gym A
7. workout({gym: 'Gym B'}) // throw TypeError

In line 1, we do a parameter destructuring of the first argument, and in line 2 we do a further destructuring of the todos variable. In this way, the argument passed to the workout() function must have a todos property and its value is an array. Otherwise, TypeError will be thrown, as you can see in line 7. This is because, in line 2, JavaScript cannot do destructuring on undefinedornull. We can improve this by giving todos a default value, as follows:

1. function workout({gym, todos=['Treadmill']}) {
2. let [first] = todos;
3. console.log('Start ' + first + ' in ' + gym);
4. }
5. workout({gym: 'Gym A'});// Start Treadmill in Gym A
6. workout();                // throw TypeError

As you can see, in line 1, we only give todos a default value and we have to call the workout() function with a parameter. Calling it without any parameters, as in line 6, will still throw an error. It is because JavaScript still cannot do destructuring on undefined to get a value for the gym variable. And if you try to assign a default value to gym itself, such as workout({gym='', ...), it won't work. You need to assign the entire parameter destructuring a default value, like this:

function workout({gym='', todos=['Treadmill']} = {}) {
  ...
}

Template literals

Template literals provide the ability to embed expressions in string literals and support multiple lines. The syntax is to use the back-tick (`) character to enclose the string instead of single quotes or double quotes. Here is an example:

let user = {
name: 'Ted',
greeting () {
console.log(`Hello, I'm ${this.name}.`);
}
};
user.greeting();// Hello, I'm Ted.

As you can see, inside the template literals, you can access the execution context via this by using the syntax ${...}. Here is another example with multiple lines:

let greeting = `Hello, I'm ${user.name}.
Welcome to the team!`;
console.log(greeting);// Hello, I'm Ted.
// Welcome to the team!

One caveat is that all the whitespaces inside the back-tick characters are part of the output. So, if you indent the second line as follows, the output wouldn't look good:

let greeting = `Hello, I'm ${user.name}.
Welcome to the team!`;
console.log(greeting); // Hello, I'm Ted.
//Welcome to the team! 

Modules

In ES6, JavaScript provides language-level support for modules. It usesexportandimportto organize modules and create a static module structure. That means you can determine imports and exports at compile time. Another important feature of ES6's module is that imports and exports must be at the top level. You cannot nest them inside blocks such as if andtry/catch.

Note

Besides static declarations of imports and exports, there is a proposal to use theimport()operator to programmatically load modules. The proposal is, at the time of writing, at stage 3 of the TC39 process. You can checkout the details at https://github.com/tc39/proposal-dynamic-import.

To create a module, all you need to do is to put your JavaScript code into a .js file. You can choose to use tools such as Babel (http://babeljs.io) to compile ES6 code into ES5, together with tools such as webpack (https://webpack.js.org) to bundle the code together. Or, another way to use the module files is to use <script type="module"> to load them into browsers. 

Export

Inside a module, you can choose to not export anything. Or, you can export primitive values, functions, classes, and objects. There are two types of exports—named exports and default exports. You can have multiple named exports in the same module but only a single default export in that module.

In the following examples, we will create auser.jsmodule that exports theUserclass, atasks.jsmodule that tracks the count of total completed tasks, and aroles.jsmodule that exports role constants.

Let's have a look atuser.js file:

1. export default class User {
2. constructor (name, role) {
3. this.name = name;
4. this.role = role;
5. }
6. };

In this module, we export theUserclass inline as the default export by placing the keywordsexportanddefaultin front of it. Instead of declaring an export inline, you can declare theUser class first and then export it at the bottom, or anywhere that is at the top level in the module, even before theUserclass.

Let's have a look atroles.js file:

1. const DEFAULT_ROLE = 'User';
2. const ADMIN = 'Admin';
3. export {DEFAULT_ROLE as USER, ADMIN};

In this module, we create two constants and then export them using named exports in a list by wrapping them in curly brackets. Yes, in curly brackets. Don't think of them as exporting an object. And as you can see in line 3, we can rename things during export. We can also do the rename withimport too. We will cover that shortly.

Let's have a look at tasks.js file:

1. console.log('Inside tasks module');
2. export default function completeTask(user) {
2. console.log(`${user.name} completed a task`);
3. completedCount++;
4. }
5. // Keep track of the count of completed task
6. export let completedCount = 0;

In this module, in line 2, we have a default export of thecompleteTaskfunction and a named export of acompletedCount variable in line 6.

Import

Now, let's create a moduleapp.jsto import the modules we just created.

Let's have a look at app.js file:

1.import User from './user.js'; 
2.import * as Roles from './roles.js';
3.import completeTask from './tasks.js';
4.import {completedCount} from './tasks.js';
5. 
6.let user = new User('Ted', Roles.USER); 
7.completeTask(user); 
8.console.log(`Total completed ${completedCount}`);
9.// completedCount++; 
10. // Only to show that you can change imported object.
11. // NOT a good practice to do it though.
12. User.prototype.walk = function () {
13. console.log(`${this.name} walks`);
14. };
15. user.walk();

In line 1, we use default import to import theUserclass from theuser.jsmodule. You can use a different name other than User here, for example,import AppUser from './user.js'. default import doesn't have to match the name used in the default export.

In line 2, we use namespace import to import theroles.jsmodule and named itRoles. And as you can see from line 6, we access the named exports of the roles.jsmodule using the dot notation.

In line 3, we use default import to import thecompleteTask function from thetasks.jsmodule. And in line 4, we use named import to importcompletedCountfrom the same module again. Because ES6 modules are singletons, even if we import it twice here, the code of thetasks.jsmodule is only evaluated once. You will see only oneInside tasks modulein the output when we run it. You can put default import and named import together. The following is equivalent to the preceding lines 3 and 4:

import completeTask, {completedCount} from './tasks.js';

You can rename a named import in case it collides with other local names in your module. For example, you can renamecompletedCounttototalCompletedTaskslike this:

import {completedCount as totalCompletedTasks} from './tasks.js';

Just like function declarations, imports are hoisted. So, if we put line 1 after line 6 like this, it still works. However, it is not a recommended way to organize your imports. It is better to put all your imports at the top of the module so that you can see the dependencies at a glance:

let user = new User('Ted', Roles.USER); 
import User from './user.js'; 

Continue with the app.js module. In line 7, we invoke thecompleteTask()function and it increases thecompletedCountinside thetasks.jsmodule. Since it is exported, you can see the updated value ofcompletedCountin another module, as shown in line 8.

Line 9 is commented out. We were trying to change thecompletedCountdirectly, which didn't work. And if you uncomment it, when we run the example later, you will seeTypeError, saying that you cannot modify a constant. Wait. completedCountis defined withletinside thetasks.jsmodule; it is not a constant. So what happened here?

Import declarations have two purposes. One, which is obvious, is to tell the JavaScript engine what modules need to be imported. The second is to tell JavaScript what names those exports of other modules should be. And JavaScript will create constants with those names, meaning you cannot reassign them.

However, it doesn't mean that you cannot change things that are imported. As you can see from lines 12 to 15, we add thewalk()method to theUserclass prototype. And you can see from the output, which will be shown later, that theuserobject created in line 6 has thewalk()method right away.

Now, let's load theapp.jsmodule in an HTML page and run it inside Chrome.

Here is theindex.htmlfile:

1. <!DOCTYPE html>
2. <html>
3. <body>
4. <script type="module" src="./app.js"></script>
5. <script>console.log('A embedded script');</script>
6. </body>
7. </html>

In line 4, we loadapp.jsas a module into the browser with<script type="module">, which is specified in HTML and has thedeferattribute by default, meaning the browser will execute the module after it finishes parsing the DOM. You will see in the output that line 5, which is script code, will be executed before the code insideapp.js.

Here are all the files in this example:

/app.js
/index.html
/roles.js
/tasks.js
/user.js

You need to run it from an HTTP server such as NGINX. Openingindex.htmldirectly as a file in Chrome won't work because of the CORS (short for Cross-Origin Resource Sharing) policy, which we will talk about in another chapter.

Note

If you need to spin up a simple HTTP server real quick, you can use http-server, which requires zero configuration and can be started with a single command. First of all, you need to have Node.js installed and then run npm install http-server -g. Once the installation completes, switch to the folder that contains the example code, run http-server -p 3000, and then open http://localhost:3000 in Chrome.

You will need to go to Chrome's Developer Tools to see the output, which will be similar to the following:

A embedded script
Inside tasks module
Ted completed a task
Total completed 1
Ted walks

As you can see from the output, the browser defers the execution of the module's code, while the script code is executed immediately, and thetasks.jsmodule is only evaluated once.

Starting from ES6, there are two types in JavaScript—scripts and modules. Unlike scripts code, where you need to put'use strict';at the top of a file to render the code in strict mode, modules code is automatically in strict mode. And top-level variables of a module are local to that module unless you useexportto make them available to the outside. And, at the top level of a module,thisrefers toundefined. In browsers, you can still access a windowobject inside a module.

Promises

Promises are another option in addition to callbacks, events for asynchronous programming in JavaScript. Before ES6, libraries such as bluebird (http://bluebirdjs.com) provided promises compatible with the Promises/A+ spec. 

A promise represents the eventual result of an asynchronous operation, as described in the Promises/A+ spec. The result would be a successful completion or a failure. And it provides methods such as .then(), and.catch()for chaining multiple asynchronous operations together that would make the code similar to synchronous code that is easy to follow.

Note

The features of ES6 promises are a subset of those provided by libraries such as bluebird. In this book, the promises we use are those defined in the ES6 language spec unless otherwise specified.

Let's look at an example in which we will get a list of projects from the server and then get tasks of those projects from the server in a separate API call. And then we will render it. The implementation here is a simplified version for demonstrating the differences between using callbacks and promises. We usesetTimeoutto stimulate an asynchronous operation.

First of all, let's see the version that uses callbacks:

1.function getProjects(callback) {
2.// Use setTimeout to stimulate calling server API
3.setTimeout(() => {
4.callback([{id:1, name:'Project A'},{id:2, name:'Project B'}]);
5.}, 100);
6.}
7.function getTasks(projects, callback) { 
8.// Use setTimeout to stimulate calling server API
9.setTimeout(() => {
10. // Return tasks of specified projects
11. callback([{id: 1, projectId: 1, title: 'Task A'}, 
12. {id: 2, projectId: 2, title: 'Task B'}]);
13. }, 100); 
14. }
15. function render({projects, tasks}) {
16. console.log(`Render ${projects.length} projects and 
      ${tasks.length} tasks`);
17. }
18. getProjects((projects) => {
19. getTasks(projects, (tasks) => {
20. render({projects, tasks});
21. });
22. });

As you can see in lines 18 to 22, we use callbacks to organize asynchronous calls. And even though the code here is greatly simplified, you can still see that the getProjects(),getTasks(), andrender() methods are nested, creating a pyramid of doom or callback hell.

Now, let's see the version that uses promises:

1.function getProjects() {
2.return new Promise((resolve, reject) => {
3.setTimeout(() => {
4.resolve([{id:1, name:'Project A'},{id:2, name:'Project B'}]);
5.}, 100);
6.}); 
7.}
8.function getTasks(projects) {
9.return new Promise((resolve, reject) => {
10. setTimeout(() => {
11. resolve({projects, 
12.tasks:['Buy three tickets', 'Book a hotel']});
13. }, 100);
14. });
15. }
16. function render({projects, tasks}) { 
17. console.log(`Render ${projects.length} projects and ${tasks.length} tasks`);
18. }
19. getProjects()
20. .then(getTasks)
21. .then(render)
22. .catch((error) => {
23. // handle error
24. });

In lines 1 to 15, in thegetProjects()andgetTasks()method, we wrap up asynchronous operations inside aPromiseobject that is returned immediately. ThePromiseconstructor function takes a function as its parameter. This function is called an executor function, which is executed immediately with two arguments, aresolvefunction and arejectfunction. These two functions are provided by thePromiseimplementation. When the asynchronous operation completes, you call theresolvefunction with the result of the operation or no result at all. And when the operation fails, you can use the rejectfunction to reject the promise. Inside the executor function, if any error is thrown, the promise will be rejected too.

A promise is in one of these three states:

  • Pending: The initial state of a promise
  • Fulfilled: The state when the operation has completed successfully
  • Rejected: The state when the operation didn't complete successfully due to an error or any other reason

You cannot get the state of a promise programmatically. Instead, you can use the.then()method of the promise to take action when the state changes to fulfilled, and use the .catch() method to react when the state changed to rejected or an error are thrown during the operation.

The .then()method of a promise object takes two functions as its parameters. The first function in the argument is called when the promise is fulfilled. So it is usually referenced asonFulfilledand the second one is called when the promise is rejected, and it is usually referenced asonRejected. The.then()method will also return a promise object. As you can see in lines 19 to 21, we can use.then()to chain all the operations. The.catch()method in line 22 is actually a syntax sugar of.then(undefined, onRejected). Here, we put it as the last one in the chain to catch all the rejects and errors. You can also add.then()after.catch()to perform further operations.

The ES6 Promisealso provides the .all(iterable)method to aggregate the results of multiple promises and the .race(iterable)method to return a promise that fulfills or rejects as soon as one of the promises in the iterable fulfills or rejects.

Another two methods that ES6 Promise provides are the .resolve(value)method and the .reject(reason) method. The .resolve(value) method returns aPromise object. When thevalueis a promise, the returned promise will adopt its eventual state. That is when you call the.then()method of the returned promise; theonFulfilledhandler will get the result of thevaluepromise. When thevalueis not a promise, the returned promise is in a fulfilled state and its result is a value. The.reject(reason)method returns a promise that is in a rejected state with thereasonpassed in to indicate why it is rejected.

As you might have noticed, promises do not help you write less code, but they do help you to improve your code's readability by providing a better way of organizing code flow.

 

Summary


In this chapter, you learned the differences between the JavaScript language and the Java language. Keep these differences in mind. They can help you to avoid pitfalls when you write JavaScript code.

You also learned the basics of ES6. ES6 mastery is considered to be one of the basic skill sets that a web developer should have. You can write less code and also better code with ES6.

In the next chapter, you will learn the fundamental concepts of Vue.js 2, and you will be able to understand how Vue.js 2 works internally and become a master of Vue.js.

About the Author

  • James J. Ye

    James J. Ye is an experienced software engineer and architect with a particular interest in full-stack engineering. James works at 6Connex Inc. as VP of Engineering. He manages the offshore engineering team in Suzhou, China. The team is responsible for all the engineering of the Virtual Experience SaaS platform. He has been using Spring since version 2.5.x and Vue.js since version 1.0. He also likes Angular, React, TypeScript, and Python.

    Browse publications by this author

Latest Reviews

(9 reviews total)
The book is well written and interesting.
Unkomplizierter Bestellvorgang, hervorragendes Buch
Good book, lots of infos.

Recommended For You

Vue.js 3 Cookbook

Explore the new features of Vue.js 3 and discover best practices for building fault-tolerant and professional frontend web applications

By Heitor Ramon Ribeiro