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:
- __proto__ - A property that stores a reference to an object's prototype. This syntax is deprecated and should be avoided.
- 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,740