JS Advanced
Telerik Academy Alpha

 

Front-End

 Table of contents

Closures and Scope

 Scope

  • The global scope is the scope of the web page

    • Or the Node.js app

  • Objects belong to the global scope if:

    • They are defined outside of a function scope

    • They are defined without var

      • Fixable with 'use strict'

function arrJoin(arr, separator) {
  separator = separator || "";
  arr = arr || [];
  arrString = "";
  for (var i = 0; i < arr.length; i += 1) {
    arrString += arr[i];
    if (i < arr.length - 1) arrString += separator;
  }
  return arrString;
}

 Scope

  • JavaScript (pre ES2015) does not have a block scope like other programming languages (C#, Java, C++)

    • and does not create a scope!

  • Yet, JavaScript has a function scope

    • Function expressions and declarations create scope

 Scope

if(true) { 
  var result = 5;
}
console.log(result); // logs 5

if(true) {
  (function(){ 
    var result = 5;
  })();
}
console.log(result); // ReferenceError

function logResult (){ 
  var result = 5;
}
if(true) { 
  logResult();
}
console.log(result);  // ReferenceError

 Scope ES2015

if(false) {
  var x = 5;
  let y = 6;
  const z = 7;
}

console.log(x); // prints undefined
console.log(y); // throws error - y is block scoped because of let
console.log(z); // throws error - z is block scoped because of const
  • ECMAScript 2015 introduces a new way to handle scopes:

    • The keywords 'let' and 'const'

    • let and const are much like var - creates a variable

    • But, they create a block scope

 Closure

function outer(x) {
  function inner(y) {
    return x + " " + y;
  }

  return inner;
}
  • Closures are a special kind of structure

    • They combine a function and the context of this function

  • inner() forms a closure. It holds a reference to x

 Closure

function outer(x) {
  function inner(y) {
    return x + " " + y;
  }

  return inner;
}
  • Closures are a special kind of structure

    • They combine a function and the context of this function

  • inner() forms a closure. It holds a reference to x

 Closure

  • To use a closure, simply define a function inside another function and expose it. To expose a function, return it or pass it to another function.

  • The inner function will have access to the variables in the outer function scope, even after the outer function has returned.

  • Among other things, closures are commonly used to give objects data privacy.

  • Data privacy is an essential property that helps us program to an interface, not an implementation.

 Closure

  • Languages such as C# provide the ability to declare methods private, meaning that they can only be called by other methods in the same class.

  • JavaScript does not provide a native way of doing this, but it is possible to emulate private methods using closures.

var counter = (function() {
  var privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }
  return {
    increment: function() {
      changeBy(1);
    },
    decrement: function() {
      changeBy(-1);
    },
    value: function() {
      return privateCounter;
    }
  };   
})();

 Closure

  • The lexical environment contains two private items: a variable called privateCounter and a function called changeBy.

  • Here, though, we create a single lexical environment that is shared by three functions: counter.increment, counter.decrement, and counter.value

    • Thanks to JavaScript's lexical scoping, they each have access to the privateCounter variable and changeBy function.

IIFE

 What is IIFE?

  • It's just an anonymous function that is executed right after it's created.

  • Immediately-invoked function expressions can be used to

    • avoid variable hoisting from within blocks

    • protect against polluting the global environment

    • simultaneously allow public access to methods while retaining privacy for variables defined within the function

(function () {
    // logic here
})();

 Why IIFE?

  • The primary reason to use an IIFE is to obtain data privacy.

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

    // Outputs: "bar"
    console.log(foo);
})();

// ReferenceError: foo is not defined
console.log(foo);

 Alternative of IIFE

  • Of course, you could explicitly name and then invoke a function to achieve the same ends.

function myImmediateFunction () {
    var foo = "bar";

    // Outputs: "bar"
    console.log(foo);
}

myImmediateFunction();

// ReferenceError: foo is not defined
console.log(foo);

 Alternative of IIFE - downsides

  • It unnecessarily takes up a name in the global namespace, increasing the possibility of name collisions.

  • The intentions of this code aren't as self-documenting as an IIFE.

  • Because it is named and isn't self-documenting it might accidentally be invoked more than once.

 IIFE Parameters

  • You can easily pass arguments into the IIFE as well.

    • Try it in the browser

var foo = "foo";

(function (innerFoo) {
    // Outputs: "foo"
    console.log(innerFoo);
})(foo);

Modules

 Before Modules

  • JavaScript has existed since 1995 and to this day no browser supports modules natively.

  • A library such as jQuery adds $ to the global scope or window.

    • window.$ = function() { ... };
    • We include a script to a library and use the global objects it exposes.

      • <script src="jquery.js"></script>
        <script>
        $(function() { ... });
        </script>

 Module patterns in ES5

  • IIFE (Immediately Invoked Function Expressions)

    • Encapsulate code complexity inside IIFE so we don't have to understand what the IIFE code does

    • Define variables inside the IIFE so they don't pollute the global scope (var statements inside the IIFE remain within the IIFE's closure)

    • But they don't provide a mechanism for dependency management

 Module patterns in ES5

  • Revealing Module pattern

    • The Revealing Module pattern is similar to an IIFE, but we assign the return value to a variable:

// Expose module as global variable
var singleton = function(){

  // Inner logic
  function sayHello(){
    console.log('Hello');
  }

  // Expose API
  return {
    sayHello: sayHello
  }
}()

 Module patterns in ES5

  • The Revealing Module pattern offers similar benefits as an IIFE, but again does not offer a mechanism for dependency management.

  • As JavaScript evolved, many more different syntaxes were invented for defining modules, each with their own benefits and downsides.

  • We call them module formats.

 What is a module?

  • A module is a reusable piece of code that encapsulates implementation details and exposes a public API so it can be easily loaded and used by other code.

  • Technically we can write code without modules.

  • Before EcmaScript 6 or ES2015, JavaScript did not have an official syntax to define modules. Therefore, developers came up with various formats to define modules in JavaScript.

 Modules

  • Asynchronous Module Definition (AMD)

    • used in browsers and uses a define function to define modules

  • CommonJS

    • format is used in Node.js and uses require and module.exports to define dependencies and modules

  • Universal Module Definition (UMD)

    •  can be used both in the browser and in Node.js

  • ES6 module format

    • import and export keywords

 Why modules

  • In JavaScript, modules should ideally allow us to:

    • abstract code: to delegate functionality to specialised libraries so that we don't have to understand the complexity of their actual implementation

    • encapsulate code: to hide code inside the module if we don't want the code to be changed

    • reuse code: to avoid writing the same code over and over again

    • manage dependencies: to easily change dependencies without rewriting our code

 The Future

  • Libraries export objects in a common module format (ES6 modules)

    • export default function $() { ... }
  • We import a module into a local scope and use it.
    • import $ from 'jquery';
      
      $(function() { ... });

 The Future

  • No globals required

  • Source order independence

  • Access to npm

  • No need to namespace your own application code

  • Dynamically load modules at any time as required

Prototype

 Prototype chain

  • When it comes to inheritance, JavaScript only has one construct: objects.

  • Each object has a private property (referred to as [[Prototype]] ) which holds a link to another object called its prototype.

  • That prototype object has a prototype of its own, and so on until an object is reached with null as its prototype. By definition, null has no prototype, and acts as the final link in this prototype chain

 Prototype chain

  • JavaScript objects are dynamic "bags" of properties (referred to as own properties). 

  • When trying to access a property of an object

    • the property will not only be sought on the object but on

      • the prototype of the object

      • the prototype of the prototype

      • and so on until either a property with a matching name is found or the end of the prototype chain is reached

 Prototypes

  • Since ECMAScript 2015, the [[Prototype]] is accessed using the accessors Object.getPrototypeOf() and Object.setPrototypeOf()

  • This is equivalent to the JavaScript property __proto__ which is non-standard but de-facto implemented by many browsers

 Prototypes

  • When a function is created in JavaScript, JavaScript engine adds a prototype property to the function

    • This prototype property is an object (called as prototype object) has a constructor property by default

    • constructor property points back to the function on which prototype object is a property

 Prototypes

  • When an object is created in JavaScript, JavaScript engine adds a __proto__ property to the newly created object

  • __proto__ points to the prototype object of the constructor function

 Prototype chain

  • prototype is a property of a Function object. It is the prototype of objects constructed by that function

  • __proto__ is internal property of an object, pointing to its prototype.

    • Current standards provide an equivalent Object.getPrototypeOf(O) method

ES2015

 Block-Scoped Constructs

  • let is a new var which allows developers to scope the variable to the blocks. We define blocks by the curly braces. In ES5, the blocks did NOTHING to the vars as seen here

function f() {
  {
    let x;
    {
      // okay, block scoped name
      const x = "sneaky";
      // error, const
      x = "foo";
    }
    // error, already declared in block
    let x = "inner";
  }
}

 Multi-line Strings and Template Literals

  • Multi-line strings - simply use the backticks

let text = 'Telerik Academy' + '\n' + ' Alpha'

// the ES6 way
let text = `Telerik Academy
 Alpha`
  • Way to output variables in the string mixed with some text

let text = 'Telerik Academy ' + initiativeName;

// the ES6 way
let text = `Telerik Academy ${initiativeName}`;

 Default Parameters in ES6

  • In ES5, you would write something like the code below, which uses the logical OR (||):

var getAccounts = function(limit) {
  var limit = limit || 10
  ...
}

var link = function (height, color, url) {
    var height = height || 50
    var color = color || 'red'
    var url = url || 'http://telerikacademy.com'
    ...
}

 Default Parameters in ES6

  • In ES6, we can put the default values right in the signature of the functions like so

var getAccounts = function(limit = 10) {
  ...
}
var link = function(
    height = 50,
    color = 'red',
    url = 'http://telerikacademy.com') {
  ...
}

 Rest operator

  • The arguments object is an Array-like object corresponding to the arguments passed to a function.

    • The arguments object is not an Array.

  • There is a better way in ES6 to access an indefinite number of arguments as an array

function(url, options, ...callbacks) {
  var callback1 = callbacks[0]
  var callback2 = callbacks[2]
  // ...
}

 Spread operator

  • In ES5, if you wanted to use an array as an argument to a function, you would have to use the apply() function

    • ​Now in ES6, we can use the spread parameters which look similar to the rest parameters in syntax as they use ellipses

function request(url, options, callback) { 
  // ...
}

var requestArgs = ['http://telerikacademy.com', {...}, function(){...}]

request(...requestArgs)

 Arrows

  • Arrows are a function shorthand using the => syntax 

    • Unlike functions, arrows share the same lexical this as their surrounding code

// Expression bodies
var odds = evens.map(v => v + 1);
var nums = evens.map((v, i) => v + i);
var pairs = evens.map(v => ({even: v, odd: v + 1}));
// Lexical this
var bob = {
  _name: "Bob",
  _friends: [],
  printFriends() {
    this._friends.forEach(f =>
      console.log(this._name + " knows " + f));
  }
}

 For…of

  • Here is a problem with ES5: when we want to iterate over objects using its keys, we need to extract those keys first with Object.keys()

    • is similar to for…in except that that for…in iterates over keys and for…of over values

for (let key in books){
  console.log(books[key])
}

for (let book of books){
  console.log(book)
}

 Modules

  • ES2015 supports modules

    • A way to write JavaScript in different files

      • Each file has its own scope (not the global)

      • Each file decides what to export from its module

    • Export the objects you want from a module:

export var port = 3000
export function getAccounts(url) {
  ...
}

import {port, getAccounts} from 'module'
console.log(port) // 3000
or
import * as service from 'module'
console.log(service.port) // 3000

Questions?

[FE] JS Advanced

By telerikacademy

[FE] JS Advanced

  • 1,114