Front-end programming

ADVANCED JAVASCRIPT

execution context

A code example

We've already discussed a big part of the things that JavaScript does behind the scenes, but there's one important detail left to discuss: the creation of the execution context.

let two = 2;

function double(num){
    return num * x;
}

console.log(double(8)); // 16 

JavaScript engine

JavaScript engines consist of two main components: a call stack and a memory heap.

engine

call stack

When a function is executed in JavaScript, a new frame is created in the call stack. Each frame represents an Execution Context.

Global Execution context

When a script is first executed, the JavaScript engine creates a global Execution Context object and pushes it to the call stack.

 

 

 

 

 

 

 

All of the global code, i.e., code which is not inside any function or object, is executed inside the global execution context.

Execution context

It's possible to conceptually represent an EC as an object with three properties:

 

  • Activation Object
  • Scope Chain
  • This Binding

Activation object

When a function is called, the interpreter scans it for declarations and creates the activation object. This is when hoisting occurs.

 

The activation object is the object used to hold the properties that describe the environment and scope of an executing function.

function myFunction(num1, num2) {
    var num3 = 30;
    return num1 + num2 + num3;
}

myFunction(10, 20);
num1 10
num2 20
arguments {0: 10, 1: 20}
num3 30
Activation Object

Scope chain

The Scope Chain is a container of Activation Objects for the executing function lexical scope. Scope Chain has a reference to the activation objects of all the parents of the executing function.

 

In other words, it's what makes the lexical scope work.

const name = 'Dee';
const age = 25;
const city = 'Lisbon';


function getPersonInfo() {
  const name = 'Sarah';
  const age = 22;

  return `${name} is ${age} and lives in ${city}`;
}

console.log(getPersonInfo());

this

this

In JavaScript, this is a reference to the object that owns the currently executing code.  The implicit this argument is available inside functions.

 

The value of this is dynamically bound depending on how the function was called.

const myObj = {
    number: 42,
    action: function () {
        let number = 22;
        return this.number;
    },
};

console.log(myObj.action()); // What will be the output?

Global context

In the global context, i.e.,  outside any function, this refers to the global object.

 

When the execution environment is the browser, global object refers to the window object.

// BROWSER

var a = 'Pikachu';
let b = 'Bulbasaur';

console.log(this.a)
console.log(a);
console.log(this.b);
console.log(b);
// What will happen?
// NODE

var a = 'Pikachu';
let b = 'Bulbasaur';

console.log(this.a)
console.log(a);
console.log(this.b);
console.log(b);
// What will happen?

Function context

Inside a function, the value of this depends on how the function is being called.

function myFunc() {
    return this;
}

// BROWSER:
console.log(myFunc()); // window

// NODE:
console.log(myFunc()); // globalThis

let myObj = {
    action: myFunc
};

// BROWSER:
console.log(myObj.action()); // myObj

// NODE:
console.log(myObj.action()); // myObj

Live codinG

Implicit this binding

altering this binding

Using the methods bind, call and apply, we can explicitly control the this binding. 

let myObj = {
    description: 'myObj',
    action: whatsThis
};

let description = 'global';

function whatsThis() {
    console.log(this.description);
}

whatsThis(); // undefined as this refers to the global object
myObj.action(); // 'myObj'
whatsThis.call(myObj);  // 'myObj'
whatsThis.apply(myObj); // 'myObj'
let newFunc = whatsThis.bind(myObj);
newFunc(); // 'myObj'

Call/Apply/Bind

function printThisAndArguments() {
    console.log(this, arguments);
}

let myObj = {
    prop: 23
}

// CALL
printThisAndArguments.call(myObj, 'Grace', 'John'); // { prop: 23 } { '0': 'Grace', '1': 'John' }

// APPLY
printThisAndArguments.apply(myObj, ['Grace', 'John']); // { prop: 23 } { '0': 'Grace', '1': 'John' }

//BIND
const printMyObjAndArguments = printThisAndArguments.bind(myObj);
printMyObjAndArguments('Grace', 'John'); // { prop: 23 } { '0': 'Grace', '1': 'John' }

Arrow functions vs this

In arrow functions, this retains the value of the enclosing lexical context.

let description = 'global';

const whatsThis = () => {
    console.log(this.description);
}

let myObj = {
    description: 'myObj',
    action: whatsThis
};

whatsThis();
myObj.action();
whatsThis.call(myObj);
whatsThis.apply(myObj);

let newFunc = whatsThis.bind(myObj);
newFunc();

// What will happen?

Arrow functions vs this

let myObj = {
    action: function () {
        return () => {
            console.log(this);
        }
    }
};

myObj.action()();
// What about now?

The Arguments object

function upperCaseArguments() {

    return arguments.forEach(function (arg) {
        return arg.toUpperCase();
    });
}
// What will happen?

method borrowing

function upperCaseArguments() {
    var argsAsArray = Array.prototype.slice.call(arguments);

    return argsAsArray.map(function (arg) {
        return arg.toUpperCase();
    });
}

closures

Closures

A closure is the combination of a function and the lexical environment within which that function was declared.

 

In other words, a closure is created when a function "remembers" its enclosing lexical environment. 

function init() {
    const name = 'Anne';
    return function () {
        console.log(name);
    };
}

const displayName = init();
displayName();

practical Closures

Closures are useful because they let you associate data (the lexical environment) with a function that operates on that data. 

 

This concept has obvious ties to the OOP concept, allowing us to emulate the concept of classes with public and private members.

let secretMessageFactory = function (secretMessage, key) {

    let tries = 3;

    return {
        get: function (secretKey) {
            if (tries === 0) {
                return 'No more tries. Secret has been destroyed.';
            }

            if (key === secretKey) {
                return secretMessage;
            }

            tries--;
            return 'Wrong.';
        }
    };
};

let secret = secretMessageFactory('secret message', 1234);

console.log(secret.get(1234)); // secret message
console.log(secret.get()); // Wrong.
console.log(secret.get(1509)); // Wrong.
console.log(secret.get(1090)); // Wrong.
console.log(secret.get(1090)); // 'No more tries. Secret has been destroyed.'

Live coding

Another Module Example

the problem with closures

The creation of closures will sometimes produce unexpected results.

var funcs = [];

for (var i = 0; i < 3; i++) {
    funcs.push(function () {
        console.log(i);
    });
}

funcs[0]();
funcs[1]();
funcs[2]();

// What will happen?

fixing the problem

The code above works with an IIFE (Immediately Invoked Function Expression) that returns a new function with a different execution context where i has a distinct value.

var funcs = [];

// We need to create a new execution context, closed over each value of i
for (var i = 0; i < 3; i++) {
    funcs.push((function (i) {
        return function () {
            console.log(i);
        };
    })(i));

funcs[0](); // outputs 0
funcs[1](); // outputs 1
funcs[2](); // outputs 2

EXERCISE

Pass The Tests

Object Oriented Javascript

Constructors

JavaScript uses special functions called constructor functions to define and initialise objects and their features.

// MANUAL CREATION
function createNewPerson(name) {
    const obj = {
        name,
        greeting: function () {
            console.log('Hi! I\'m ' + this.name + '.');
        }
    };
    return obj;
}

const person = createNewPerson('Paul');
// CONSTRUCTOR FUNCTION
function Person(name) {
    this.name = name;
    this.greeting = function () {
        console.log('Hi! I\'m ' + this.name + '.');
    }
}

const person = new Person('Paul');

A constructor function is JavaScript's version of a class. It returns nothing, simply defining common properties and methods to all objects of a given type.

Constructors - a few considerations

Constructor functions usually start with a capital letter. This isn't a syntax requirement; it's a convention used to make constructor functions easier to recognise in code.

 

The new keyword is used to tell the interpreter that we want to create a new object instance.

 

In the previous example, every time we called the Person constructor, we created a new greeting function. This isn't ideal behaviour, and we'll learn how to solve it later.

the object constructor

Generic objects also have a constructor, which generates an empty object.

// CREATING AN EMPTY OBJECT AND ADDING PROPERTIES
const person = new Object(); // {}

person.name = 'Paul';
person.age = 54;

// PASSING AN OBJECT LITERAL AS ARGUMENT
let person1 = new Object({
  name: 'Paul',
  age: 54
});

inheritance

Prototype based language

JavaScript is a prototype-based language.

 

Prototypes are the mechanism by which JS objects inherit features from one another.

prototype chain

 To provide inheritance, objects can have a prototype object, which acts as a template object that it inherits methods and properties from.

An object's prototype object may also have a prototype object, which it inherits methods and properties from, and so on. This is often referred to as a prototype chain.

prototypes

An objects prototype is accessible via two ways:

 

  1. __proto__ - A property that stores a reference to an object's prototype. This syntax is deprecated and should be avoided.
  2. Object.getPrototypeOf(obj) - the currently acceptable syntax.

prototype property

Constructor functions also have access to a prototype property.

 

This property, despite its name, does NOT represent the function's prototype.

 

In a constructor function, the prototype property represents the blueprint for instances created from said function.

prototypal inheritance

Prototypal Inheritance is based on a delegation mechanism in which non-existent properties are looked up in prototype objects.

function Person(first, last, age) {

    this.name = {
        first,
        last
    };
    this.age = age;
    this.breathe = function () {
        console.log('*breathing noises*');
    }
}

const arnold = new Person('Arnold', 'Schwarzenegger', 74);

arnold.breathe(); // available from the instance itself
console.log(arnold.valueOf()); // where does valueOf come from?

Live coding

Prototype Chain

common behaviour

Remember this problem?

 

Every time we create a new Person object, we're creating a new breathe() function. Considering that every Person object breathes the same way, there mus be a better way to do it.

function Person(first, last, age) {

    this.name = {
        first,
        last
    };
    this.age = age;
    this.breathe = function () {
        console.log('*breathing noises*');
    }
}

adding prototype functionality

function Person(first, last, age) {

    this.name = {
        first,
        last
    };
    this.age = age;
}

Person.prototype.breathe = function () {
    console.log('*breathing noises*');
};

const arnold = new Person('Arnold', 'Schwarzenegger', 74);
arnold.breathe(); // available from the instance's prototype

 Note that methods and properties are not copied from one object to another in the prototype chain. They are accessed by walking up the chain.

subclassing

function AwesomePerson(first, last, age, occupation) {
    Person.call(this, first, last, age); // chain Person and AwesomePerson constructors
    this.occupation = occupation;
}

// Make AwesomePerson inherit from Person by overriding Person prototype object
AwesomePerson.prototype = Object.create(Person.prototype);

// Recreate constructor property destroyed in previous line
Object.defineProperty(AwesomePerson.prototype, 'constructor', {
    value: AwesomePerson,
    enumerable: false, // so that it does not appear in 'for in' loop
    writable: true 
});

const arnold = new AwesomePerson('Arnold', 'Schwarzenegger', 74, 'be awesome');
arnold.breathe();

Subclassing can be achieved in pseudo-classical inheritance
by chaining and linking constructors together.

subclassing

AwesomePerson.prototype.doTheAwesome = function () {
    console.log('doing the awesome, in an awesome kind of way.');
}

const arnold = new Person('Arnold', 'Schwarzenegger', 74, 'be awesome');
arnold.doTheAwesome(); // ERROR

classes

Classes

ECMAScript 2015 introduces class syntax to JavaScript as a way to write reusable classes using easier, cleaner syntax, which is more similar to Java classes.

 

It is important to notice that, under the hood, classes still work with prototypal inheritance. Classes are just syntactic sugar.

Classes

class Person {
  
    constructor(first, last, age) {
        this.name = {
            first,
            last
        };
        this.age = age;
    }

    breathe() {
        console.log('*breathing noises*');
    };
}

The constructor() method defines the constructor function that represents our Person class. There can ONLY be one.

 

breathe() is a class method.

inheritance with classes

class AwesomePerson extends Person {
    constructor(first, last, age, occupation) {
        super(first, last, age); // 'this' is initialized by calling the parent constructor.
        this.occupation = occupation;
    }

    doTheAwesome() {
        console.log('doing the awesome, in an awesome kind of way.');
    }
}

getters and setters

class Person {
    constructor(first, last, age) {
        this.name = {
            first,
            last
        };
        this.age = age;
    }

    get fullName() {
        return `${this.name.first} ${this.name.last}`; // Use of template literals, a way of creating a string with an embedded expression. They support multi-line.
    }
}

const arnold = new AwesomePerson('Arnold', 'Schwarzenegger', 74, 'be awesome');
console.log(arnold.fullName);

Calling a getter or a setter in JavaScript looks just like accessing a normal property. The difference is you can have extra logic running on the background.

getters and setters

class Person {
  
    (...)

    set fullName(newName) {
        if (newName.split(' ').length !== 2) {
            console.log('First and last name are required. No less, no more.');
            return;
        }

        this.name.first = newName.split(' ')[0];
        this.name.last = newName.split(' ')[1];
    }
}

const arnold = new AwesomePerson('Arnold', 'Schwarzenegger', 74, 'be awesome');
console.log(arnold.fullName); // Arnold Schwarzenegger
arnold.fullName = 'Sylvester'; // First and last name are required. No less, no more.
console.log(arnold.fullName); // Arnold Schwarzenegger

Static methods and properties

class Person {
  
    constructor(first, last, age) {
        this.name = {
            first,
            last
        };
        this.age = age;
    }

    static greet() {
        console.log('Hi!');
    }
}

console.log(Person.greet());

The static keyword defines a static method or property for a class.

Static members are called without instantiating their class and cannot be called through a class instance. 

Classes and hoisting

An important difference between function declarations and class declarations is that function declarations are hoisted and class declarations are not.

 

This means that, in order to access a class (wether to create an instance or subclass) we need to have declared the class first.

const arnold = new AwesomePerson('Arnold', 'Schwarzenegger', 74, 'be awesome'); // ERROR

class AwesomePerson {
}

Private field declarations

class Person {
  
    #name; // private fields can only be declared up-front in a field declaration
    
    constructor(first, last, age) {
        this.#name = {
            first,
            last
        };
        this.age = age;
    }

    get fullName() {
        return `${this.#name.first} ${this.#name.last}`;
    }
    
    (...)
}

const person = new Person('Martha', 'Sully');
console.log(person.name); // undefined

Default constructors

class Person {

    constructor(first, last, age) {
        this.name = {
            first,
            last
        };
        this.age = age;
    }

    (...)
}

class Student extends Person {
  
    constructor(...args) { // default constructor for subclasses, that will be created in case we don't declare it
        super(...args);
    }
};

The syntax above uses the spread operator.

Spread syntax can be used when all elements from an object or array need to be included in a list of some kind.

modules

javascript programs

JavaScript programs started off as small scripting tasks, providing a bit of interactivity to web pages where needed.

 

However, nowadays we have complete applications being run in browsers with a lot of JavaScript, as well as JavaScript being used in other contexts.

 

With that came the need to think about mechanisms for splitting JavaScript programs into separate modules that could be imported when/where needed.

modules in node.js

Node.js has supported modules for a long time.

 

There are a number of JavaScript libraries and frameworks that enable module usage:

  • CommonJS
  • RequireJS
  • Webpack
  • Babel 

 

Modern browsers have started to support module functionality natively.

Modules

As of ES6, JavaScript supports a native module format.

 

An ES6 module is a file containing JS code. There’s no special module keyword; a module mostly reads just like a script, except for two differences:

 

  • ES6 modules are automatically strict-mode code

  • We can use import and export in modules

Export

Everything declared inside a module is local to the module, by default.

 

If we want something declared in a module to be public, so that other modules can use it, we must export that feature.

// EXPORT KEYWORD
export class Person {
    (...)
}
 
// DEFAULT EXPORTS (one per module; when importing, name can be customized)
export default Person;
    
// EXPORT LISTS
export {
    Person, 
    AwesomePerson
};

import

Import is used to pull items from a module into another script.

// IMPORT ALL FROM file.js
import * as PersonModule from 'file.js';

// IMPORT Person AND AwesomePerson FROM file.js
import { Person, AwesomePerson } from 'file.js';

// IMPORT DEFAULT FROM file.js
import bla from 'file.js';

Using ES6 modules

Scripts that use modules must be loaded by setting a type="module" attribute in the <script> tag, if the execution environment is a browser.

 

 

If the execution environment is Node.js, then "type": "module" should be added to the package.json file.

<script type="module" src="./file.js"></script>

package manager

Dependencies

A dependency is a third-party bit of software that solves a problem for us.

 

A web project may have any number of dependencies, ranging from zero to many, and our dependencies might include sub-dependencies that we didn't explicitly install.

package manager

A package manager is a system that will manage our project's dependencies.

 

It will provide a method to install new dependencies, manage where packages are stored on our file system, and offer capabilities for us to publish our own packages.

 

npm is the default package manager for Node.js, although there are others (like Yarn, for example).

npm

npm (short for Node Package Manager) is a popular package manager among JavaScript developers. It is automatically installed whenever we install Node.js on our system.

 

npm consists of three components:

  • Website
  • Command Line Interface
  • Registry,  a public database of JS software

Live coding

Using npm

Live coding

npm with dependencies: Jest

More on package managers

Here

Advanced JS

By Soraia Veríssimo

Advanced JS

  • 1,553