Introduction

I believe that learning new things and evaluating the things we know is useful to keep us on track, thus avoiding the situation where we feel outdated. In this article, we will go through some basics of JavaScript. Enjoy!

1. Declarations

Think about the following code, and try to answer what gets logged (and why) without reading the explanation below.

// situation 1
console.log(person);
var person = 'John';

// situation 2
console.log(person);
let person = 'Phill';

// situation 3
console.log(person);
const person = 'Frank';

// situation 4
const person = 'Vanessa';
console.log(person);
person = 'Mike';
console.log(person);

// situation 5
var person = 'John';
let person = 'Mike';
console.log(person);

// situation 6
var person = 'John';
if (person) {
  let person = 'Mike';
  console.log(person);
}
console.log(person);
Explanation

Situation 1: The expected result here would be to see the text John written in the console, but surprisingly, we see undefined gets logged instead. Wonder why?

Well, here we can see the classic JavaScript in action. This behavior is called hoisting. Under the hood, the language splits the variable declaration and value assignment into two pieces. The variables are moved to the top, declared with the value set to undefined (hoisted), regardlessly of where they were initially declared by a developer. It looks something like this:

var person;
console.log(person);
person = 'John';

Situation 2: Here, the result will be a reference error.

Uncaught ReferenceError: Cannot access 'person' before initialization

The error text speaks for itself. Because we used the keyword let, our variable is hoisted but remained uninitialized, and the error gets thrown informing us that we are trying to access an uninitialized variable. The let keyword was introduced in ES6 enabling us to use block-scoped variables thus helping us prevent unintended behavior.

Situation 3: Here, we have the same error as in the Situation 2.

The difference is that we've used the keyword const, thus preventing re-assigning our variable after initialization. This keyword was also introduced in ES6.

Situation 4: In this case, we can see how the keyword const is useful and how it can save us from unintentionally re-assigning our variable. In our example, first, we will see Vanessa written in the console, and then a type error.

Uncaught TypeError: Assignment to constant variable

The usefulness of const variables grows exponentially with our codebase.

Situation 5: If a variable has already been defined inside a certain scope with the keyword var, trying to declare it again with the keyword let inside the same scope throws an error.

So, in our example, nothing will be logged and we will see a syntax error.

Uncaught SyntaxError: Identifier 'person' has already been declared

Situation 6: We have a function-scoped variable first and block-scoped variable second. In this case, it doesn't matter if they have the same name/identifier.

In the console, we should see Mike and John getting logged, in that order. Why?

Because the keyword let gives us the block-scoped variables, meaning that they only exist within the scope they are created, in this case within the if...else statement. The inner variable takes primate over the outer variable and this is the reason why we can use the same identifier.

2. Inheritance

Consider the following classes and try to answer what gets logged and why.

class Person {
  constructor() {
    this.sayHello = () => {
      return 'Hello';
    }
  }

  sayBye() {
    return 'Bye';
  }
}

class Student extends Person {
  sayHello() {
    return 'Hello from Student';
  }
}

const student = new Student();
console.log(student.sayHello());
Explanation

If you said Hello you were right!

Why: Each time we create a new Student instance, we set the sayHello property to it to be a function returning the string Hello. This is happening in the parent (Person) class constructor.

Classes are syntactical sugar in JavaScript, and under the hood, in our example, the sayHello method in the Student class is defined on the prototype chain. Considering that each time we create an instance of the Student class we set the sayHello property to that instance to be a function returning the string Hello, we never get to use the function defined on the prototype chain thus we will never see the message Hello from Student being logged.

3. Object Mutability

Consider the following situations and think of each section output:

// situation 1
const user = {
  name: 'John',
  surname: 'Doe'
}

user = {
  name: 'Mike'
}

console.log(user);

// situation 2
const user = {
  name: 'John',
  surname: 'Doe'
}

user.name = 'Mike';
console.log(user.name);

// situation 3
const user = {
  name: 'John',
  surname: 'Doe'
}

const anotherUser = user;
anotherUser.name = 'Mike';
console.log(user.name);

// situation 4
const user = {
  name: 'John',
  surname: 'Doe',
  address: {
    street: 'My Street'
  }
}

Object.freeze(user);

user.name = 'Mike';
user.address.street = 'My Different Street';
console.log(user.name);
console.log(user.address.street);
Explanation

Situation 1: Here, as we learned in the previous section, we are trying to re-assign a const variable that is not permitted, therefore, we will get a type error

The result in our console will be the following text:

Uncaught TypeError: Assignment to constant variable

Situation 2: In this scenario, we have different behavior even though we are modifying the variable declared with the keyword const. The difference is that we are changing the object property and not its reference, and this is allowed on const object variables.

The result in the console should be the word Mike.

Situation 3: By assigning the user to anotherUser variable we are sharing the reference, or memory location if you like, between them. In other words, both of them will point to the same object in memory, therefore, changing the property on one object will reflect the change on the other.

The result in the console should be Mike.

Situation 4: Here, we are using Object.freeze method to provide the functionality that was lacking in the previous scenario (situation 3). By using this method, we can freeze our object thus not allowing for its property values to change. But there is a catch. It will only do a shallow freeze, meaning that it will not protect the deep property updates. This is the reason why we are able to mutate the street property while the name property will remain unchanged.

The output in the console should be the words John and My Different Street, in that order.

4. Arrow Function

What will be logged and why after running the following snippet:

const student = {
  school: 'My School',
  fullName: 'John Doe',
  printName: () => {
    console.log(this.fullName);
  },
  printSchool: function () {
    console.log(this.school);
  }
};

student.printName();
student.printSchool();
Explanation

The output in our console will be undefined and My School, in that order.

If you are coming from the old school, you will probably be familiar with the following syntax:

var me = this;
// or
var self = this;

// ...
// ...
// somewhere deep...
// me.doSomething();

You can think of me or self variable as the parent scope accessible for every nested function created within.

When using arrow functions, this is done for us and we no longer need to store the this reference in order to have access to it somewhere deeper in our code. Arrow functions do not bind their own this, instead, they inherit the one from the parent scope and this is the reason why we have undefined logged after invoking the printName function.

5. Destructuring

Check out the destructuring below and think about what will be logged. Is the given syntax allowed or an error will be thrown?

const rawUser = {
   name: 'John',
   surname: 'Doe',
   email: 'john@doe.com',
   displayName: 'SuperCoolJohn',
   joined: '2016-05-05',
   image: 'path-to-the-image',
   followers: 45
}

let user = {}, userDetails = {};
({ name: user.name, surname: user.surname, ...userDetails } = rawUser);

console.log(user);
console.log(userDetails); 
Explanation

Although a bit out of the box, the syntax above is allowed and it doesn't throw an error! Pretty neat, right?

Most of us are not accustomed to the right side of expression looking like that...I mean, only the left side should contain dot syntax...or not? :)

All jokes to the side, the syntax above is powerful and enables us to easily split any object into two more specific ones as shown in the example above.

The console output is:

// {name: "John", surname: "Doe"}
// {email: "john@doe.com", displayName: "SuperCoolJohn", joined: "2016-05-05", image: "path-to-the-image", followers: 45}

6. Async/Await

What will be logged after the following immediate function gets invoked?

(async () => {
  let result = 'Some Data';

  let promise = new Promise((resolve, reject) => {
    setTimeout(() => resolve('Some data retrieved from the server'), 2000);
  });

  result = await promise;
  console.log(result);
})();
Explanation

If you said Some data retrieved from the server after 2 seconds, you were right!

The code is paused until the promise gets resolved. After two seconds, it continues and logs the given text. This means that the JavaScript engine will literally wait until the asynchronous operation is completed. The async/await approach is, let's say, syntactic sugar to get the promise result. Some might say, a more readable way than promise.then.

7. The Return Statement

const multiplyByTwo = (x) => {
    return
    {
        result: x * 2
    };
}
console.log(multiplyByTwo(2));  
Explanation

If you said {result: 4}, well, you were wrong. The output is undefined. But don't be so harsh on yourself, it bugged me too, considering that I write C# as well and this is not a problem there.

The code above will return undefined due to Automatic Semicolon Insertion which says that no line terminator is allowed between the return keyword and the expression

The solution would be to fix our function and write it in one of the following ways:

const multiplyByTwo = (x) => {
    return {
        result: x * 2
    };
}

or

const multiplyByTwo = (x) => {
  return (
    {
      result: x * 2
    }
  );
}

Conclusion

That's it for this session. I hope you liked it and if so, please consider supporting me by buying me a coffee and subscribe here or follow me on twitter to stay tuned.