JS Fundamentals

Joshua Lin (@konekoya)

Why this series?

  • For the upcoming training program
  • Design specifically for Backend Developers
  • For me, to learn JS really well
  • Introducing ES6

What we will be covering

  • JavaScript scope: including function scope and block scope
  • Hoisting
  • The famous Closure
  • "this" keyword
  • ES6 and beyond

Scope

Function scope

function sayHello() {
    // function body
}
try {
  throw "myException";
}
catch (e) {
  console.log(e);
}

console.log(e); // referenceError

*catch has block scope

Declaring JS variables 1/2

var num = 10;

When you declare variables without functions, they will end up in the global scope (window object in the browser)

We will cover ES6 const and let later

Declaring JS variables 2/2

function printNum() {
    var num = 10;
    console.log(num); // 10
}
  
// Invoking the function
printNum();
  

When declare variables inside function scope, they'll stay in the function scope. 

In-depth walk thru 1/2

var foo = "bar";

function bar() {
  var foo = "baz";
}

function baz(foo) {
  foo = "bam";
  bam = "yay";
}

bar();
baz();

Compilation and Excution

  • LHS and RHS
  • Scope lookup
  • Engine, Compiler​, and Scope 

In-depth walk thru 2/2

var foo = "bar";

function bar() {
  var foo = "baz";
  
  function baz(foo) {
    foo = "bam";
    bam = "yay";
  }
  baz();
}


bar();
foo; // ?
bam; // ?
baz(); // ?

Try to answer following questions

Scope Look-ups

var d = 24;

function foo(a) {

  var b = a * 2;

  function bar(c) {
    console.log(a, b, c, d);
  }

  bar(b * 3);
}

foo(2); // 2 4 12 24

Scope look-up stops once it finds the first match

IIFE pattern

(function() {
    var foo = "bar";
    
    function bar() {
      var foo = "baz";
      
      function baz(foo) {
        foo = "bam";
        var bam = "yay";
      }
      baz();
    }
})();

foo // ?

Immediately-Invoked Function Expression (IIFE)

const and let

Before ES6, this is what we got

var lang = 'JavaScript';
var version = 5;
const NAME = 'JavaScript';
const VERSION = 5;

let engine = 'V8'
let year = 2018;

Now, we have these two shiny additions 😎

const

// Should assign a value when declared
const x; // SyntaxError


const y = 10;

// Can only be declared once in a scope
var y = 50; // SyntaxError

// Cannot be reassigned
y = 20; // TypeError
  • Short for constants
  • Should assign a value when declared
  • Can only be declared once in a scope
  • Can't be reassigned

const gotcha

const arr = [1, 2];
console.log(arr); // [1, 2]


arr.push(20, 9) 
console.log(arr); // [1, 2, 20, 9]

WAT??  The value is not "immutable" 😱

We can fix it by using Object.freeze 🤔

const arr = [1, 2];
console.log(arr); // [1, 2]

const _arr = Object.freeze(arr);

// Uncaught TypeError
_arr.push(20, 9)
console.log(_arr); // [1, 2]

let

// Can be declare without initializing with a value
let x;
x = 10;
console.log(x); // 10


// Can't be redeclared
let y = 10;
let y = 50; // SyntaxError

// Can reassign a new value
y = 50;

console.log(y);
  • No need for initializing with a value
  • Can be reassigned
  • Can't be redeclared

Block Scoping

if (true) {
   var foo = 'foo';
   let bar = 'bar';
   const baz = 'baz';
}

console.log(foo); // ?
console.log(bar); // ?
console.log(baz); // ?
  • Both const and let create block scope
  • If, while, for and try catch 👈
// Yep, no matter how mmmmmmmany brackets that you have...
{{{{{var insane = 'yes, you are'}}}}}

console.log(insane) // yes, you are 👀
  • As we mentioned previously, var doesn't have block scope 😬😬😬

A simple {} will do 🎉

// This creates a new block scope, Awesome!
{
  let foo = 'bar';
  console.log(foo); // bar
}

console.log(foo); // ReferenceError
  • Using {} now creates a new block scope
  • let and const behave similarly
// When nesting, first come, first served
function foo() {

  {
    const bar = 'baz';
    console.log(bar);  // baz
  }

  console.log(bar); // ReferenceError

}

foo();

Let's fix your var 🛠

for (var i = 0; i < 10; i++) {
  console.log(i); // 0 ~ 9
}

console.log('Accessing here', i) // Accessing here 10

The variable i is now available in the upper scope

for (let i = 0; i < 10; i++) {
  console.log(i); // 0 ~ 9
}

console.log('Accessing here', i) // ReferenceError

With let keyword, the variable is only available in the for loop body  💯

Best practices My two cents

const API_KEY = 'google-map-123kjdsj41';
const API_ROOT = 'https://www.oodata.com.tw/api/v1';
const PI = 3.14;
const module = require('module'); // commonJS

Using const where possible, it is a signal that the identifier won’t be reassigned.

Best practices

let isActive = false;

// more code ...

isActive = true;



let user = null;

if (isReady) {
  user = {
    name: 'John',
    age: 20
  }
}

Using let anywhere else, it is a signal that the variable may be reassigned

Best practices

Never use var, it is now the weakest signal available in JavaScript land. It can be reassign, redeclared. The intent is not clear.  And plus the confusing hoisting mechanism ... 🤔

 

Handy sites for checking ES6 browser support

Questions?

Hoisting 😎

1. A general way of thinking about how execution contexts work in JavaScript. In order word, it not a real thing, but rather a metaphor.

 

2. Variables and function declarations are moved to the top of your code 🙃

 

Hoisting on MDN

Yep, it's unexpected...

Consider the following two examples 👀

a = 2;
var a;

console.log(a); // ?
console.log(a); // ?

var a = 2;

The metaphor

Variables are moved to the top of their scopes

( in this case - the global scope )

var a;
console.log(a);

a = 2;

But, in reality. It has two phases(compilation and execution)

A detour to functions

There're two ways to declare a function in JS. They almost work the same way. Yep, I mean almost...

// Function Declarations
function foo() {
  return `Hi, I'm foo!`;
}

// Function Expressions
var baz = function () {
  return `Hi, I'm baz!`;
}

But...

foo(); // ?

function foo() {
  console.log(`Hi, I'm foo!`);
}

baz(); // ?

var baz = function () {
  console.log(`Hi, I'm baz!`);
}

Declarations themselves are hoisted, but assignments, even assignments of function expressions, are not hoisted.

Temporal Dead Zone

Lets behave a bit differently

console.log(foo); // undefined
console.log(baz); // ?
console.log(bar); // ?

var foo = 10;
let baz = 20;
const bar = 30;

Best practices?

  • Normally, you won't use a variable before declaring it 😅 But do remember functions are hoisted as well. And many programers like to declare their functions at the bottom of their code( see examples below )
  • We can be tempted to look at "var a = 2;" as one statement, but the JavaScript Engine does not see it that way. It sees "var a" and "a = 2" as two separate statements, the first one a compiler-phase task, and the second one an execution-phase task.

Questions?

Closure 🤡

Closure is when a function is able to remember and access its lexical scope even when that function is executing outside its lexical scope.

Wait, wat??? 🙄🙄🙄

A closure example

function foo() {
  var a = 2;

  function bar() {
    console.log(a);
  }

  return bar;
}

var baz = foo();

baz(); // closure! 😄
  • bar() is executed outside of its declared lexical scope.
  • bar() still has a reference to that scope, and that reference is called closure. And therefore, the scope won't be GC.

Another closure example

function incremental() {
  var x = 1;
  return function () {
    console.log(x++)
  }
}

var foo = incremental();
foo(); // closure
foo(); // closure
  • Function can be returned directly
  • Variable x is initialized and given a value 1

Timer function

function wait(message) {

  setTimeout(function timer() {
    console.log(message);
  }, 1000);

}

wait("Hello, closure!");  // closure
  • A thousand milliseconds after we have executed wait(..), that inner function timer still has closure over that scope.

Partial application

function addX(x) {
  return function (n) {
    return n + x;
  };
}
const addThree = addX(3);

console.log(addThree(4)); // 7
console.log(addThree(10)); // 13
console.log(addThree(37)); // 40

In essence, addX is a function factory — it creates functions which can add a specific value to their argument.

Module pattern

function CoolModule() {
  var something = "cool";
  var another = [1, 2, 3];

  function doSomething() {
    console.log(something);
  }

  function doAnother() {
    console.log(another.join(" ! "));
  }

  return {
    doSomething: doSomething,
    doAnother: doAnother
  };
}

var foo = CoolModule();

foo.doSomething(); // cool
foo.doAnother(); // 1 ! 2 ! 3

A variation

var foo = (function CoolModule() {
  var something = "cool";
  var another = [1, 2, 3];

  function doSomething() {
    console.log(something);
  }

  function doAnother() {
    console.log(another.join(" ! "));
  }

  return {
    doSomething: doSomething,
    doAnother: doAnother
  };
})();

foo.doSomething(); // cool
foo.doAnother(); // 1 ! 2 ! 3

I use this pattern in My own website 😎😎😎

TL;DR 📝

  • Closure is when a function can remember and access its lexical scope even when it's invoked outside its lexical scope.
  • Practical use cases for closure: currying and partial application, emulating private methods with closures like the Module pattern.
  •  All functions in JavaScript are closures.

Further reading 📚

Questions?

Types 💩

Let's watch a short video before jumping into our talk today.

 

https://www.destroyallsoftware.com/talks/wat

Dynamic typing

JavaScript is a loosely typed or a dynamic language. Variables in JavaScript are not directly associated with any particular value type, and any variable can be assigned (and re-assigned) values of all types:

var foo = 42; // foo is now a number
foo = 'bar'; // foo is now a string
foo = true;  // foo is now a boolean   

Seven built-in types

  • undefined
  • boolean
  • number
  • string
  • symbol
  • null
  • object 👈

Six data types that are primitives except objects   

Primitives? 🤔

A primitive (primitive value, primitive data type) is data that is not an object and has no methods. In JavaScript, there are 6 primitive data types: string, number, boolean, null, undefined, symbol (new in ECMAScript 2015).

 

All primitives are immutable, i.e., they cannot be altered. It is important not to confuse a primitive itself with a variable assigned a primitive value. The variable may be reassigned a new value, but the existing value can not be changed in the ways that objects, arrays, and functions can be altered.

Typeof operator

Use typeof operator to identify different types

console.log(typeof foo);  // undefined
console.log(typeof true);  // boolean
console.log(typeof 123);   // number
console.log(typeof "foo");  // string
console.log(typeof { a: 1 });  // object

console.log(typeof null);  // object... wat?

Wait... why typeof null returns an object? 😬

 

Okay, it's a bug 🐛. And won't be fixed anyway...

How to check null then?

Using triple equals to check the null type:

// Use triple equals to check null type
const NULL = null;
console.log(NULL === null);  // true 😎

// or

if (NULL === null) {
  // do something here...
}

Or use lodash.isNull to check it, although they're doing the exact same thing behind the scene

https://github.com/lodash/lodash/blob/4.17.10/lodash.js#L11948

Typeof gotcha

When using typeof operator, the returning values are always string

console.log(typeof foo === undefined);  // false
console.log(typeof foo === 'undefined');  // true
console.log(typeof (typeof true));  // string

Undefined and undeclared

Typeof operator returns undefined no matter the given value is declared or not

let x;

console.log(typeof x); // undefined
console.log(typeof y); // undefined

Let's go thru some more examples

Further reading 📚

Questions?

Thank you 😃

Made with Slides.com