HOWTO.javascript - raynaldmo/HOWTO GitHub Wiki

Table of contents generated with markdown-toc

JavaScript Overview

A complete JavaScript implementation is made up of

  • The core (ECMAScript)
  • Document Object Model (DOM)
  • Browser Object Model (BOM)
  1. ECMAScript/ECM-262 describes the JavaScript language
  • syntax
  • data types
  • statements
  • keywords
  • reserved words
  • operators
  • objects
  1. DOM
  • DOM is an API for manipulating web page contents
  • Each part/element of a page is a type of node
  • With the DOM API, page elements/nodes can be removed, added, replaced or modified
  1. BOM
  • BOM is an interface for manipulating browser components (windows, frames etc.)
  • Pop-up new browser windows
  • Move/resize/close windows
  • Implements:
    • navigator object (provides detailed info about browser)
    • location object (provides detailed info about page loaded in browser)
    • screen object (provides info about screen resolution)
    • custom objects such as XMLHttpRequest object (used for AJAX)

References

Variables

  • Variables are used to store values and must be declared before they can be used
  • Variables are declared with let (or older var) or const keyword
  • Variables may contain two different types of data
    • Primitive value (number, string, boolean, symbol, undefined, null)
    • Reference value (object)
  • Primitive values are simple atomic pieces of data, while reference values are objects that may be made up of multiple values

Variable Naming

  • Variable names can start with any upper or lower-case letter an underscore, _, or dollar character, $ and are case sensitive
  • Variables can also contain numbers, but cannot start with them

const keyword

  • Use const keyword to declare a variable that won't be reassigned to another value
const name = 'Ray';
name = 'Roy;  // Uncaught TypeError: Assignment to constant variable

let keyword

  • Use let keyword to declare a variable that might be reassigned to another value
  • Primitive values are copied by value
let num1 = 100;
let num2 = num1;
num1++    // 101
num2      // 100

  • Reference values are copied by reference
// ex 1
const obj = { x : 100 };
const obj1 = obj;
obj.x = 100;
obj1.x    // 100

// ex 2
const arr = [1, 'two', 3];
const arr1 = arr;
arr[3] = 'four';
arr;        // [1, "two", 3, "four"]
arr1;       // [1, "two", 3, "four"]
  • Note that all variables (including primitive variables) can be treated as an object
  • This means both Primitive data types and Reference types (objects) have properties and methods
// ex 1
// 4000.32 primitive value (number) is treated as object
// JavaScript converts primitive value to object (Number)

(4000.32).toLocaleString()  // '4,000.32'

Variable Scope (Part 1)

  • Scope refers to where a variable is accessible by a program
  • There are basically two variable scopes
    • Global scope
    • Local scope

Global Scope

  • Any variable declared outside of a block (or function) is said to have global scope
  • This means it is accessible everywhere in a program

Local Scope

  • A new feature added by ES6 is that blocks can be used to create local scope for a variable
  • Variable must be declared using let or const
let x = 100;  // global variable x

// Define block scope using curly braces
// x is a new variable with local scope
{ let x = 200; console.log(x); }  // 200
console.log(x)                    // 100
  • If let or const isn't used inside block scope, variable has global scope
let x = 100;  // global variable x

// x refers to global variable x
{ x = 200; console.log(x) };  // 200
console.log(x);               // 200

let vs var keyword

  • Previous to JavaScript ES6, variables were declared using the keyword var
  • Once a variable is declared using let it can't be re-declared using let or var
  • Once a variable is declared using var it can be re-declared using var but not using let
  • With respect to the above const behaves that same as let
let x = 100;
let x = 100;   // Uncaught SyntaxError: Identifier 'x' has already been declared
var x = 100;   // Uncaught SyntaxError: Identifier 'x' has already been declared
const x = 100; // Uncaught SyntaxError: Identifier 'x' has already been declared

var z = 100;
var z = 100; // ok
let z = 100; // Uncaught SyntaxError: Identifier 'z' has already been declared
  • Variables declared using let (and const) have block scope
  • Variables declared using var (or no keyword) have no block scope, only global scope and function scope
  • If a code block is inside a function, then var becomes a function-level variable
// ex 1
let s = 'string 1';   // global variable s
// global variable s1
{ var s1 = 'string 2'; console.log(s1); }  // 'string 2', var declarations are "visible" through blocks

// Access global variable s
console.log(s);                            // 'string 1'
// Access global variable s1
console.log(s1);                           // 'string 2'

// ex 2
let s = 'string 1';   // global variable s
// local variable s1
{ let s1 = 'string 2'; console.log(s1); }  // 'string 2'

// Access global variable s
console.log(s);                      // 'string 1'
// global variable s1 doesn't exist
console.log(s1);                     // Uncaught ReferenceError: s1 is not defined

// ex 3
for (var i = 0; i < 10; ++i) {}
console.log(i); // 10      // i has global scope

for (let i = 0; i < 20; ++i) {}
console.log(i); // 10      // global i value is displayed, i in for loop has local scope

// ex 4
// j has local scope
for (let j = 0; j < 10; ++j) {}
console.log(j);            // ReferenceError: j is not defined

// ex 5
function sayHello() {
  if (true) {
    var phrase = "Hello"; // phrase is a function level variable
  }
  console.log(phrase); // "Hello"
}

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

Data Types (Part 1)

  • In JavaScript all data types are objects or have an object representation
  • JavaScript has seven primitive data types and 1 complex data type
  • Primitive types - hold a single value
    • string (UTF-16) - for strings, Javascript doesn't have a char type
    • number - used for both integer and floating point numbers
    • bigint - for integer numbers of arbitrary length
    • boolean - for logical values true/false
    • undefined - a type with a single value undefined, meaning "not assigned"
    • null - a type with a single value null, meaning "empty" or "does not exist"
    • symbol - used to implement unique keys in objects
  • Complex type (object)
    • Array [] - can hold multiple values
    • Object {} - can hold multiple values
    • Function function() {}
  • Because of loose typing, there needs to be a way to determine data type
  • Use typeof operator to determine a data type
typeof undefined;    // 'undefined'
typeof null;         // 'object'
typeof 'fruit';      // 'string'
typeof 100;          // 'number'
typeof true;         // 'boolean'
typeof { x: 1, s: 'orange' };          // 'object'
typeof [ 1, 'two', { o: 'object' } ] ; // 'object'

String Type

  • A string in JavaScript is a sequence of zero or more 16-bit unicode (UTF-16) characters/values
  • JavaScript has no separate type for a single character
  • Length of a string is number of 16-bit values
// ex 1
let s = 'fruit';

// Use length property to determine string length
// Note that s can be treated as an object though it's defined as
// a primitive type - a string literal
s.length;   // 5

// ex 2
// Properties can also be accessed using square bracket notation
s['length'];  // 5
  • Access string characters - use index notation or use s.at()
// ex 1
const s = 'This is a string';
s[0];             // 'T'
s.at(0)           // 'T'
s[s.length - 1];  // 'g'
  • Iterate over string characters using for..of
for (let char of "Hello") {
  console.log(char); // H,e,l,l,o (char becomes "H", then "e", then "l" etc)
}
  • Strings are immutable - Once a string is created, its value can't be changed
  • To change a string, the original value is destroyed and a new string is created
// ex 1
// Create string literal
let s = "This is a string";

// Individual characters are read-only
s[0] = 't'; 
s;     // 'This is a string'

// Original string, s, is destroyed and new string, s, is created to hold
// contents
s += "... add more";
s;     // 'This is a string... add more'

// ex 2
let s1 = 'Hi';
s1 = 'h' + s[1]; // replace the string
s1; // 'hi'
  • Strings can be created using single quotes ', double quotes " or backticks
// ex 1
const cStr = 'It is a string';
const cStr1 = "It's a string";
const cStr1 = `It's a string`;
const cStr2 = 'It\'s a string';
  • The backslash \ is used to escape special characters in strings
    • Single quote marks \'
    • Double quote marks \"
    • End of line \n
    • Carriage return \r
    • Tab \t
    • Backlash \\
// ex 2     
const bs = 'This is a backslash \\'
console.log(bs);  // "This is a backslash \"
  • Backticks allow a string to span multiple lines
// ex 3
let guestList = `Guests:
 * John
 * Pete
 * Mary
`;
alert(guestList); // a list of guests, multiple lines
  • Strings can also be created using the String() constructor but returns an object
// ex 1
let sObject = new String('This is a string');
typeof sObject;                 // object
console.log(sObject.valueOf()); // 'This is a string'
  • Use String() or toString() method to convert numbers, booleans, objects to a string
  • Also can use string concatenator + to do string conversion
// ex 1
let age = 54;
age.toString();   // '54'

ageString = String(age); // ageString == '54';

// ex 2
let status = false;
String(status);  // "false"
status = true;
String(status); // "true"

// ex 3
let age = 54;
let s = 'My age is ' + age;
console.log(age);   // 'My age is 54'

String Properties and Methods

let name = 'Ray';
name.length;         // 3
name.toUpperCase();  // "RAY"
name.toLowerCase();  // 'ray'
name.charAt(2);      // 'y'
name.indexOf('a');   // 0
name.indexOf('Ra');   // 0
name.indexOf('a');   // 1
name.indexOf('w');   // -1
name.lastIndexOf('a'); // 1
name.concat(' Williams'); // "Ray Williams"

Searching in a String

  • Use str.indexOf(substr, pos)
  • (Also can use str.lastIndexOf(substr, pos) to search from end of string)
// ex 1
let str = 'Widget with id';
str.indexOf('Widget'); // 0, because 'Widget' is found at the beginning
str.indexOf('widget'; // -1, not found, the search is case-sensitive
str.indexOf("id") ); // 1

// ex 2
// Find all occurences of target
let str = "As sly as a fox, as strong as an ox";
let target = "as";
let pos = -1;
while ((pos = str.indexOf(target, pos + 1)) != -1) {
  alert( pos );
}
  • Use str.includes(substr, pos) to return true/false
"Widget with id".includes("Widget"); // true
"Hello".includes("Bye"); // false
"Widget".includes("id"); // true
"Widget".includes("id", 3); // false
  • Can also use str.StartsWith() and str.endsWith()
"Widget".startsWith("Wid"); // true, "Widget" starts with "Wid"
"Widget".endsWith("get"); // true, "Widget" ends with "get"

Getting a Substring

  • Use str.slice(start [, end])
  • (Note: str.substring() and str.substr() are also available)
// ex 1
let str = "stringify";
str.slice(0, 5); // 'strin', the substring from 0 to 5 (not including 5)
str.slice(0, 1); // 's', from 0 to 1, but not including 1, so only character at 0

// ex 2
let str = "stringify";
str.slice(2); // 'ringify', from the 2nd position till the end

// ex 3
let str = "stringify";
// start at the 4th position from the right, end at the 1st from the right
str.slice(-4, -1); // 'gif'

// ex 4
let ucFirst = str =>  str[0].toUpperCase() + str.slice(1); 
ucFirst('rayford'); // "Rayford"

Comparing Strings

  • String characters are compared by their numeric code
  • str.codePointAt(pos) and str.fromCodePoint(code) for numeric code conversions
  • All lowercase letters go after uppercase letters because their codes are greater
'a' > 'Z' // true
'Z' > 'A'; // true
'Glow' > 'Glee'; // true
'Bee' > 'Be'; // true
  • Use str.localeCompare(str2) to compare strings factoring in language rules

Template Literals

  • Template literals are a special type of strings that were introduced in ES6
  • Template literals use the back-tick character `
`It's a template literal!`
  • Use of Template literals allows interpolation in strings
const name = `me`
let s = `Hello it's ${name}!`  // Hello it's me!

// Hello it's me - it's been such a long, long time
s = s.concat(` - it's been such a long, long time`);

Number Type

  • Number type uses IEEE-754 format to represent both integers and floating point values (represented as 64-bit floating point)
  • JavaScript doesn't distinguish between integers and floating point numbers
let i = 42;
let f = 3.14159;

typeof i;  // number
typeof f;  // number

Infinity

  • Infinity is a special error value representing any number that is too big for JavaScript to deal with
  • The biggest number allowed is 1.7976931348623157e+308
typeof Infinity;     // 'number'
console.log(1e308);  // 1e+308
console.log(2e308);  // Infinity
console.log(4/0);    // Infinity

NaN

  • NaN is a special error value that is short for "Not a Number"
  • It's used when an operation is attempted and the result isn’t numerical
typeof NaN;                // 'number'
console.log('hello' * 5);  // NaN
NaN === NaN; /// false, this value is not normal as it doesn't even equal itself!
  • isNaN(val) converts its argument to a number and then tests it for being NaN
isNaN(1);       // false
isNaN('1');     // false
isNaN(NaN);     // true
isNaN(null);    // false
isNaN(undefined); // true
isNaN('blue');  // true
isNaN(3.14159); // false
  • Number.isNaN(val) checks whether val belongs to the number type, and if so, tests it for being NaN
Number.isNaN(1);       // false
Number.isNaN('1');     // false
Number.isNaN(NaN);     // true
Number.isNaN(null);    // false
Number.isNaN(undefined); // false
Number.isNaN('blue');   // false, compare to isNaN('blue')
Number.isNaN(3.14159); // false
  • isFinite(val) converts its argument to a number and then tests it for not being NaN/Infinity/-Infinity
isFinite(100);    // true
isFinite(1/0);    // false
isFinite(Infinity);    // false
isFinite(NaN);    // false
isFinite(100.0);  // true
isFinite('this is a string');  // false
  • Number.isFinite(val) checks whether its argument belongs to the number type,
    and if so, tests it for not being NaN/Infinity/-Infinity
Number.isFinite(100);    // true
Number.isFinite(1/0);    // false
Number.isFinite(Infinity);    // false
Number.isFinite(NaN);    // false
Number.isFinite(100.0);  // true
Number.isFinite('this is a string');  // false
  • Use Number.isInteger() method to test for an integer
  • Numbers can also be created using the Number() constructor but returns an object
// ex 1
let nObj = new Number(100);
typeof nObj;                       // object
console.log(nObj.valueOf());       // 100
Number.isInteger(nObj.valueOf());  // true

Rounding Errors

  • Because of rounding errors avoid testing for specific floating point values
(0.1 + 0.2) == 0.3 // false , value is actually 0.30000000000000004
  • The following will result in an infinite loop. i will never exactly equal 10
let i = 0;
while (i != 10) {
  i += 0.2;
}
  • Use toFixed() as a workaround
let sum = 0.1 + 0.2;
+sum.toFixed(2); // 0.30

parseInt() and parseFloat()

  • Use parseInt(), parseFloat() or Number() to convert non-numeric values into numbers
let x = parseInt('1 is a number');  // x == 1
typeof x;         // 'number'

let n = parseInt('not a number'); // n ==  NaN
isNaN(n);   // true

n = Number('42'); // n == 42
n = Number('this is a string'); // n == NaN
  • Unary + operator can also do string to number conversion
let str = '70';
typeof str;             // 'string'
let num = +str;
typeof num == 'number'; // true

Boolean Type

  • Boolean type has two values, true or false
let on_off = true;
  • To Convert to Boolean type use Boolean() constructor
let message = 'I am a message';
let bool = Boolean(message);  // bool == true;

Type conversion to Boolean

if (message) {  // message is automatically converted to Boolean type
  //
}

Undefined Type

  • If a variable is declared, but not assigned or never declared then its type and value is undefined
  • A variable can also be set to undefined but normally a variable is set to null to assign an
    "empty" or unknown value
let x;
// let y;
let z = undefined;
typeof x == 'undefined';  // true, x never assigned a value
typeof y == 'undefined';  // true, y never declared
typeof z == 'undefined';  // true, z set to undefined

Null Type

  • Null type has only one value, null
  • null usually represents an empty object pointer
  • When defining a variable meant to hold an object but value hasn't been assigned, set it to null
  • In general, values tend to be set to undefined by JavaScript whereas values are usually set to null manually (explicitly) by the programmer
let obj = null;
typeof obj;   // 'object';
typeof null;  // 'object';

Falsy Values

  • In JavaScript the following values are considered to be false
    • false
    • 0 or -0
    • "" or '' // empty string
    • `` // empty template literal
    • NaN
    • null
    • undefined

Objects (Part 1)

  • An object is an un-ordered collection of property / key values
  • An object is not an ordered list like an Array, Set or Map
    • Don't rely on the properties being in a certain order
    • Properties don’t always appear in the order they were entered
  • Maps strings (properties / keys) to values
  • Object properties / keys are strings
// ex1
let obj = {
  1 : 'integer can be a key',
  n : 100,
  s : 'string',
  a : [1,2,"three"],
  o : { x : 1000, f : function() { console.log(this.x) }},
  f1() { console.log('This is shorthand syntax for an object method') }
};

1, n, s, a , o are properties / keys
obj.1       // 'integer can be key'
obj.n       // 100
obj.s       // 'string'
obj.a[2]    // 'three'
obj.o.f()   // 1000

for (const p in obj) {
  console.log(`p = ${p} typeof p = ${typeof p}`);
}

// All object keys / properties are strings
p = 1 typeof p = string
p = n typeof p = string
p = s typeof p = string
p = a typeof p = string
p = o typeof p = string
p = f1 typeof p = string

// Same as above loop without Template Literal
for (p in obj) {
    console.log('p = ' + p , 'typeof p = ' + typeof p);
}

// Print object values
for (const k in obj) {
    console.log(obj[k]);
}

'integer can be key'
100
string
(3) [1, 2, 3]
{x: 100, f: ƒ}
  • In addition to its own set of properties, Objects also inherit properties of another object called it's Prototype
  • This is known as Prototypal Inheritance - See section on JavaScript OOP

Symbols

  • Symbol Type
  • Symbols were introduced as a new primitive value in ES6
  • Use Symbol() constructor to create a symbol
  • Every value returned from Symbol() is unique and immutable
  • The purpose of a Symbol is to guarantee unique identifiers for object properties - This avoids naming collisions
// ex 1
const sym1 = Symbol();
const sym2 = Symbol();
sym1;           // Symbol()
typeof sym1;    // 'symbol'

// create object using symbols
// below uses Computed Properties syntax
let obj = { [sym1] : 'Symbol 1', [sym2] : 'Symbol 2' };
obj;    // {Symbol(): "Symbol 1", Symbol(): "Symbol 2"}

obj[sym1];  // 'Symbol 1'
obj[sym2];  // 'Symbol 2'

obj.sym1;   // undefined
obj.sym2;   // undefined

// sym1 and sym2 keys are guaranteed to be unique
sym1 === sym2;   // false

// ex 2
// with non-unique property names, undesired behavior can occur
// below name value is over-written
let obj1 = { name: "Name 1", name: "Name 2" };
obj1;    // {name: "Name 2"}

  • Two variables can point to the same symbol using the for() method
// ex 1
const sym1 = Symbol.for('shared symbol');
const sym2 = Symbol.for('shared symbol');

sym1 === sym2  // true

Operators

Comparison Operators ==, ===

Soft Equality ==

  • If data type is non-numeric, it's converted to a numeric value
    • Number() is used for the conversion
" " == 0;            // true  (strange!)
" " == "0";          // false
false == "0";        // true
"1" == true;         // true
"2" == true;         // false
"true" == true;      // false
null == undefined;   // true

Hard / Strict Equality ===

  • Tests for equality but only returns true if and only if values are the same data type
" " === 0;            // false
" " === "0";          // false
false === "0";        // false
"1" === true;         // false
"2" === true;         // false
"true" === true;      // false
null === undefined;   // false

// NaN is the only value in JavaScript that is not equal to itself
NaN === NaN           // false - this is JavaScript language quirk
  • Always use hard equality when testing if two values are equal

Inequality !=, !==

  • Work in a similar way to the soft and hard equality operators

Relational <, >, <=, >= Operators

  • These operate the same as Soft Equality / Inequality operators so type conversion is performed

Increment / Decrement Operators ++, --

  • Works with any data type (Number, String, Boolean, Object)
  • Non-numeric operand is converted to a number or NaN
  • If operand is an object, define valueOf() function for custom handling
// ex 1
let str = '1';
++str;    // 2
str = '1 is a number';
++str;   // NaN

// ex 2
let b = false;
++b;    // 1
--b;    // 0
--b;    // -1

// ex 3
let obj = {x : 1, valueOf : function() { this.x++; return x;} };
++obj;  // 2
++obj;  // 3

Unary Operator +, -

  • Performs numeric (string to number) conversion
// ex 1
let s = '01';
+s;     // 1, same as parseInt(s) or Number(s);
-s;     // -1

Logical Operators !, &&, ||

Logical NOT (!)

  • Can be applied to any type. Always returns a Boolean value
!false      // true
!"blue"     // false
!0          // true
!NaN        // true
!""         // true
!12345      // false
!{}         // false
  • Use to convert a value to its Boolean equivalent
var b = !!"blue";   // typeof b == 'boolean', b == true
var b = !!0;        // typeof b == 'boolean', b == false
var b = !!NaN;      // typeof b == 'boolean', b == false
var b = !!"";       // typeof b == 'boolean', b == false
var b = !!12345;    // typeof b == 'boolean', b == true

Logical AND (&&)

  • Can be used with any operand type
  • Used as 'short circuit' operator
  • If first value evaluates to false, second value is never evaluated
  • If first value evaluates to true, second value is evaluated
  • Overall, AND returns the first falsy value or the last value if none were found
let found = false;
let res = found && undeclaredVariable;  // res == false

found = true;
res = found && undeclaredVariable;  // throws reference error

res = "" && {};   // res == "" (null string)
res = {} && "";   // res == "" (null string)

res = "I am a string" && {};    // res == {}
res = {} && "I am a string";    // res == "I am a string"

Logical OR (||)

  • Also can be used as 'short-circuit' operator
  • If first operand evaluates to false, second operand is evaluated
  • If first operand evaluates to true, second operand is never evaluated
  • Overall, OR returns the first truthy value or the last value if none were found
// ex 1
let someObj;

// Assign obj to empty object if someObj is null or undefined
// someObj must be declared, otherwise reference error is thrown
let obj = someObj || {};  // obj == {}

someObj = { x : 1 };
let obj = someObj || {};  
obj;     // { x : 1 }

Null Coelescing Operator ??

  • The result of a ?? b is:

    • if a is not null or undefined, then a,
    • otherwise b
  • ?? returns the first argument if it’s not null/undefined. Otherwise, the second one.

  • We can rewrite result = a ?? b using the operators that we already know, like this

result = (a !== null && a !== undefined) ? a : b;

Bitwise Operators ~, &, | ^

  • Bitwise operators work with operands as 32-bit integers
  • JavaScript converts values used into a 32-bit integer
  • Operation is performed and result is displayed as decimal value
// ex TBD

Type Conversion / Coercion

  • When an operator or a statement expects a value of a certain type but a different type is given, JavaScript does automatic type conversion

+ operator (String Concatenation)

  • When one operand is a string and the other is not, the other operand is converted to a string and the result is concatenated
  • In any other case (neither operand is a string), the operands are converted to numbers and the results are added
let s = "The answer is " + 42;  // "This answer is 42"
let s1 = 3 + 4 + "5";           // "75"
let x = 3 + 5;                  // 8
let y = 1 + true;               // 2
let z = null + false;           // 0
z = null + true ;               // 1
z = undefined + true;           // NaN

Arithmetic -, *, /, %, ++, -- Operators

  • Non-numeric operands are converted to numbers or NaN

Comparison Operator ==

  • Non-numeric operands are converted to numbers or NaN
"0" == true;  // converts to 0 == 1, comparison is false
"3" == 3;     // converts to 3 == 3, comparison is true
"this is a string" == 3; // converts "this is a string" to NaN, comparison is false

Relational <, >, <=, >= Operators

  • Non-numeric operands are converted to numbers or NaN
true < 2;     // converts to 1 < 2, true
true < null;  // converts to 1 < 0, false
"3" < 4;      // converts to 3 < 4, true
'this is a string' < 1 // false
'this is a string' > 1 // false

&& , || logical operators

  • No type conversion is performed
let a = 0 || "4";  // a ==  "4"
let b = 0 && "4";  // b == 0

Functions (Part 1)

  • A function is a block of code that is defined once but may be executed, or invoked, any number of times
  • In JavaScript functions are objects
  • This means you can set properties and methods on functions - just like other objects
  • JavaScript functions can be
    • Treated as values
    • Assigned to variables
    • Stored in properties of objects
    • Stored as elements of an array
    • Passed as arguments to functions
    • Returned from functions
  • Function definitions can be nested within other functions, and have access to any variables that were in scope where the function was defined
    • This means that JavaScript functions are closures
  • A Constructor is a function (using the new keyword) that creates objects
  • All functions return a value
    • If this is not explicitly stated, the function will return undefined

Defining Functions

  • Four ways to define a function
    • Function Declaration
    • Function Expression
    • Function Constructor
    • Arrow Function

Function Declaration

// ex 1 square a number
function square(x) {
  return x * x;
}

// ex 2
// Compute the distance between Cartesian points (x1,y1) and (x2,y2).
function distance(x1, y1, x2, y2) {
    let dx = x2 - x1;
    let dy = y2 - y1;
    return Math.sqrt(dx*dx + dy*dy);
}

// ex 3 - recursive function
function factorial(x) {
    if (x <= 1) return 1;
    return x * factorial(x-1);
}

// ex 4 - nested function
function hypotenuse(a, b) {
  function square(x) {
    return x *x;
  }
  // square() is a closure - it has access to
  // variables a, b when it (square() function) is invoked at a later time
  return Math.sqrt(square(a) + square(b));
}

// invoke hypotenuse
hypotenuse(3,4) // 5

Function Expression

  • Function is assigned to a variable
  • Function can be Anonymous Function or Named Function (NFE)
  • Also see Named Function Expression
// ex 1
// Anonymous function 
let square = function(x) {
  return x * x;
};

square(5);  // 25

// Named function
// In this instance naming the function provides no benefit
let square1 = function sq(x) {
  return x * x;
}
sq(5);       // Uncaught ReferenceError: sq is not defined
square1(5);  // 25

Function Constructor

  • A function can also be declared using the constructor Function()
  • The body of the function is entered as a string
  • Functions created this way are also created in the global scope, regardless of where they are actually declared
const square = Function('x', 'return x * x');

Arrow Function (=>)

  • Introduced in ES6
  • Basic syntax
let func = (arg1, arg2, ..., argN) => expression;

// Equivalent to
let func = function(arg1, arg2, ..., argN) {
  return expression;
};
  • Arrow function advantages:
    • Much less verbose than normal function expression
    • Single parameter doesn't need parentheses
    • Single line body doesn't need braces
    • return keyword isn’t required if single line body
  • Arrow functions are always anonymous, so if you want to refer to them, you must assign them to a variable
  • Arrow functions are mostly used for short, anonymous functions
  • Though they provide succinct syntax, Arrow functions have these limitations
    • Do not have their "own" this (Arrow Functions)
      • If this is referenced, it's taken from outside context / function
    • Do not have arguments
    • Can’t be called with new
    • Don’t have super method
    • Cannot be used as a constructor
  • Function parameters come before the => and the main body of the function comes
    after the =>
// ex 1
// single parameter, value of x*x implicitly returned, no return keyword needed
const square = x => x*x; // parameter is x, body is x*x
let res = square(2);
res;     // 4

// Same as
const square = function(x) { return x*x; };

// ex 2
// multiple parameters, value of x + y implicitly returned, no return keyword needed
const add = (x,y) => x + y;

let res = add(2,3);
res;    // 5

// Same as
const add = function(x,y) { return x + y; };

// ex 3
// multiple parameters, use explicit return keyword
const add1 = (x,y) => { let val = x + y; return val; }

let res = add(2,3);
res;    // 5

// ex 4
// no parameters - use empty parentheses
let printName = () => console.log('My name is Ray');

printName();    // 'My name is Ray'

// ex 5
// Multi-line function require braces and explicit return statement
const tax = (salary) => {
  let taxable = salary - 8000;
  const lowerRate = 0.25 * taxable;
  taxable = taxable - 20000;
  const higherRate = 0.4 * taxable;
  return lowerRate + higherRate;
}

Invoking a Function

  • Functions can be invoked in four ways
    • As a function
    • As a a method
    • As a constructor
    • Indirectly through call() or apply() method

As a Function

let square = function(x) {
  return x * x;
};
let r = square(10);         // r == 100

As a Method

// ex 1
let obj = { x : 100, f : function() { return 2 * this.x; }};
obj.f();    // 200

// ex 2
// shorthand notation
let obj = { x : 100, f() { return 2 * this.x; }};
obj.f();    // 200

As a Constructor using new

function F(first_name, last_name) {
  // "this" object is implicitly created with "new"
  this.name = `${first_name} ${last_name}`;
  // return "this" object (implicitly)
}

let obj = new F('Sam', 'Smith');
obj.name;   // 'Sam Smith'

// this won't work - new keyword is needed to set "this" as calling
// object
let obj = F('Sam', 'Smith');
obj1.name;   Uncaught TypeError: Cannot read property 'name' of undefined

Argument Passing

  • In JavaScript all function arguments are passed by value
  • If an argument is an object (reference variable), the effect is the same as passing by reference since JavaScript store objects in heap memory
  • Function parameters are set when the function is defined
  • When a function is invoked, it is provided with arguments

Passing an argument by value

  • Passing an argument by value means value of variable(s) is/are copied into function local variable(s)
// ex 1
function f(name) {    // name is a function parameter
  // name is primitive type, local copy is changed, passed in value is
  // unchanged
  name = 'Ray';
}

let name = 'James';
f(name);            // name is function argument
console.log(name);  // 'James'

Passing an argument by reference

  • Passing an argument by reference means memory location of variable(s) is/are copied into function local variable(s)
// ex 1
function f(obj) {
  obj.name = 'Jewel';
}

let obj = { name : 'Sam' };
console.log(obj.name);  // 'Sam'
f(obj);
console.log(obj.name);  // 'Jewel'

arguments variable

  • Every function has a special variable called arguments
  • arguments is an array like object that contains every argument passed to the function when it is invoked
  • arguments has length property but because it's not a true array doesn't have normal array methods like forEach(), join() and slice()
// ex 1
function arguments() {
    return arguments;
}

let result = arguments('hello', NaN);
Array.isArray(result);    // false
typeof result;            // 'object'
JSON.stringify(result);   // "{"0":"hello","1":null}

result.length             // 2
result[0];                // 'hello'
result[1];                // NaN

Default Parameters

  • ES6 introduced a clean way to specify default parameters for a function
// ex 1
function printName(name='No name') {  // default value is 'No name'
  console.log(`I am ${name}`);
}

printName();         // 'I am No name'
printName('Ray');    // 'I am Ray'

// ex 2
// Default parameters should always come after non-default parameters

function discount(price, discount=10) {
  return price * (100 - discount) / 100;
}

// 10% discount
discount(20);      // 18

// 50% discount
discount(20, 50);  // 10

Closures

  • A Closure is a function that remembers variables (and their values) that were in scope when the function was defined
  • The function has access to these variables when the function is later called (even if called in a different scope)
  • Closures are a necessary mechanism for a language like JavaScript that supports "first-class" functions
  • Since functions are passed around as values, for this to be useful functions need to be able to access variables that were in scope when the function is invoked
  • Event handlers are closures
// ex 1
// clicks is remembered by event callback
let clicks = 0;
btn. document.querySelector(#btn);
btn.addEventListener('click', function() {
    console.log(++clicks);
});
  • Inner functions form closures
// ex 1
function f() {
  let str = "I am a string";
  function f1() {
    console.log(str); // str is in f1()'s lexical scope
  }

  f2(f1);
}

function f2(fx) {
  fx();   // fx() is a closure, "remembers" str variable
}

f();   // "I am a string"

// ex 2
function f() {
  let str = "I am a string";
  return function() {
    console.log(str);   // anonymous function is a closure, "captures" str;
  }
}

function f1() {
  f()();
}

f1();   // "I am a string"
  • Closure with setTimeout()
// ex 1
function f() {
  let str = "I am a string"
  setTimeout(function() {
    console.log(str)
  }, 1000);
}

f();    // After 1 second, prints "I am a string"

// ex 2 (loops)
function f() {
  for (let i = 1; i <= 5; i++) {
    setTimeout(function() {
      console.log(`i : ${i}`);
    }, i * 1000);
  }
}

f();  // prints 6 five times

// Fix: Use IIFE to create scope

function f() {
  for (var i = 1; i <= 5; i++) {
    (function(idx) {        // use IIFE to capture i
      setTimeout(function() {
        console.log("i : " + idx);
      }, i * 1000);
    })(i);
  }
}

f();  // prints 1, 2, 3, 4, 5

// ex 3
let app = (function() {
  let v = { str : "I am a string" };
  return { k : v }; // return object with key = k, value = v
})();

console.dir(app);         // { k: { str: 'I am a string' } }
console.dir(app.k);       // { str: 'I am a string' }
console.dir(app.k.str);   // "I am a string"

// Above is not a closure, since no function execution is involved

Callbacks (Part 1)

  • A function that is passed as an argument to another is known as a callback
// ex 1
function checkUser(id, enablePremium, premium = false) {
  // ...
  if (typeof enablePremium === 'function') {
    if (premium === true) {
      enablePremium(id);
    }
  }
}

function enablePremium(id) {
   console.log(`User ${id} is premium user`);
}

checkUser(1, enablePremium, true);  // 'User 1 is premium user'

Sorting Arrays with a Callback

  • Use sort() method with callback to determine sort order
// ex1
let arr = [1,3,12,5,23,18,7];
arr.sort();              // 1,12,18,23,3,5,7
arr.sort(function(a,b) {
  return a - b
});                      // 1,3,5,7,12,18,23         

arr.sort(function(a,b) {
  return b - a
});                      // 23,18,12,7,5,3,1

Array Iterators

  • Arrays have a number of methods that utilize callbacks to make them more flexible
  • forEach()
// Use Arrow function for callback
colors.forEach( (color,index) =>
    console.log(`Color at position ${index}  is ${color}`) );
  • map()
// ex 1
let res = [1,2,3].map( x => 2 * x);
res;  // [2,4,6]

// ex 2
let res = ['red','green','blue'].map( color => `<p> ${color.toUpperCase()}</p>` );
res;    // ['<p>RED</p>', '<p>GREEN</p>', '<p>BLUE</p>']

// ex 3
let colors = ['red', 'green', 'blue'];
colors.map((color, index, array) => `${index} is ${color}. ${array.length} items in total.`);

Rest Parameters and Spread Syntax

  • Rest parameters and spread syntax
  • Rest parameters are used to create functions that accept any number of arguments
  • The Spread syntax is used to pass an array to functions that normally require a list of many arguments
  • When we see ... in the code, it is either rest parameters or the spread syntax
  • When ... is at the end of function parameters, it’s "rest parameters” and gathers the rest of the list of arguments into an array
// ex 1 - rest parameters in a function
function sumAll(...nums) { // nums is the name for the array
  let sum = 0;
  for (let num of nums) sum += arg;
  return sum;
}

sumAll(1); // 1
sumAll(1, 2); // 3
sumAll(1, 2, 3); // 6

  • When ... occurs in a function call or alike, it’s called a “spread syntax” and expands an array into a list
// ex 1 - spread syntax
let arr = [3, 5, 1];
Math.max(...arr); // 5 (spread turns array into a list of arguments)

let str = "Hello";
[...str]; // H,e,l,l,o

let obj = { a: 1, b: 2, c: 3 };
let obj1 = { ...obj }; // spread the object into a list of parameters
                       // then return the result in a new object

Strict Mode

  • ECMAScript 5 introduced a strict mode that produces more exceptions and warnings and prohibits the use of some deprecated features
  • Enable strict mode by adding the string 'use strict'; as the first line in a file or function
  • Strict mode can apply to entire script or to a function
  • Reference Strict Mode

Invoke Strict Mode

  • The recommended way to invoke strict mode is to place all your code into an Immediately Invoked Function Expression (IIFE)
// ex 1
(function() {
    'use strict';
    // All your code goes here
})();
  • Note: By default Chrome and Firefox JavaScript console launch in non-strict mode
  • Adding 'use strict'; statement doesn't work
  • In browser JS console, use IIFE to run and/or test strict mode
(function() {
    // code
}());

  • Invoke Strict Mode for entire file
// ex 1
app.js :

'use strict';
....
  • Invoke Strict Mode for a function
// ex 1
function f1() {
  'use strict';
  ...
  function nested() {
    // Also in strict mode
  }
  ...
}

function f2() {
  // Not in strict mode
}
  • Invoke Strict Mode in module or library
// ex 3 
let param = 100;
const app = (function(p) { // IIFE
  'use strict';

  function init() {
    console.log('init -> ' + p);
  }

  function f1() {
    //
  }
  ...

  return {init : init, f1 : f1 };
})(window.param);

app.init(); // 'init -> 100'
app.f1();

Check for Strict Mode

  • Use IIFE
  • Inside a function declaration, this is undefined
  • In browser, this is equal to window object
  • In node, this is equal to global object
(function() {
  'use strict';
   return this === undefined;
}());     // true

(function() {
    return this === undefined;
}());    // false

Strict Mode Effects

  • Using strict mode prevents un-intentional creation of new global variables if variable name is mistyped
// ex 1
'use strict';
let msg1 = "I am message 1";

// meant to type msg1, but since in strict mode will generate reference error
// if not in strict mode, msg11 global variable would have been created
msg11 = "New message 1";
  • let, const or var keyword must be used in all variable declarations
// ex 1
// non-strict mode
(function() {
  n = 100;
  console.log(n);
}());    // 100

// strict mode
(function() {
  'use strict';
  n = 100;
  console.log(n);
}());    // Uncaught ReferenceError: n is not defined

// ex 2
// Best practice is declare all variables with let or const
(function() {
  let n = 100;
  const c = 'message';
  console.log(n);
}());   // 100
  • Un-declared variables inside functions end up in global scope
// ex 1
// non-strict mode
(function() {
  function f() {
    msg2 = 'msg2';     // msg2 is un-declared
  }
  f();
  console.log(msg2);   // msg2 is in 'global' scope
}());       // 'msg2'

// strict mode
(function() {
 'use strict'; 
 function f() {
    var msg2 = 'msg2';  // msg2 is local to f()
 }
 f();
 console.log(msg2);    // ReferenceError: msg2 is not defined
}());

// ex 2
// using let or const automatically makes msg2 local to function
// specifying strict mode is un-necessary
(function() { 
  function f() {
    let msg2 = 'msg2';  // msg2 is local to f()
  }
  f();
  console.log(msg2);    // ReferenceError: msg2 is not defined
}());
  • this keyword coercion
// ex 1
// In strict mode, this === undefined.
(function() {
  'use strict';
  return this === undefined;
}());     // true

// In non-strict mode, this === window object
(function() {
    return this === window;
}());    // true
  • Deleting variable or function throws an error
// ex 1
// non strict mode
(function() {
  let x = 100;
  delete x;        // silently fails
  console.log(x); 
}());    // 100

// strict mode
(function() {
  'use strict';
  let x = 100;
  delete x;     
  console.log(x); 
}());    // Uncaught SyntaxError: Delete of an unqualified identifier in strict mode

  • Duplicate property names in objects are not allowed
'use strict';
var obj { x : 100, x : 'string x' };  // throws error
  • Function parameters must be unique
'use strict';
function f(a, a, b) { // throws syntax error
  //
}

Variable Scope (Part 2)

  • JavaScript supports global and starting with ES6, block level scope

Ways to Create New Variable Scope

  • Curly braces with let or const keyword
// ex 1
if (true) {
    let a;
}
console.log(a); // ReferenceError: a is not defined

while (true) {
    const b;
}
console.log(b); // ReferenceError: b is not defined

function foo() {
    let c;
}
console.log(c); // ReferenceError: c is not defined
                // This should be unsurprising, as 
                // a var declaration would also throw an Error

// standalone block
{
    const d;
}
console.log(d); // ReferenceError: d is not defined
  • Use let or const in loops
// ex 2
// using var makes variables global
for (var i=0; i<3; i++) {
  console.log(i);
}
console.log(i);   // 3

for (let i=0; i<3; i++) {
  console.log(i);
}
console.log(i);   // ReferenceError: i is not defined
  • Use IIFE
// ex 1 - parentheses around the function
let fruit = 'grapes';
(function() {
  var fruit = 'banana';   // 'let' also works
  console.log(fruit);     // 'banana';
})(); 

fruit;   // 'grapes'

// ex 2 - Other ways to create an IIFE
(function() {
  // Parentheses around the whole thing
}());

!function() {
 // Bitwise NOT operator starts the expression
}();

+function() {
  Unary plus starts the expression
}();
  • Create a Namespace
// ex 1
const ns = (function() {
  // ... bunch of variables, function declarations etc.
  ...
  function init () { console.log('init');}
  return { init : init, x : 1000 , s : 'string', o : { z : 2 } };
})();

ns.init();    // 'init'
ns.x          // 1000
ns.s          // 'string'
ns.o.z        // 2
  • Catch Block TBD

Lexical Scoping

  • In JavaScript variables are lexically scoped - i.e. compile time scoping
  • This means that the scope of variable is defined by its location within the source code
// ex 1
function init() {
  let name = 'Bugs Bunny';    // name is lexically scoped
  function displayName() {    // displayName() is a closure
    console.log(name);
  }
  displayName();
}

init();         // calls displayName(), 'Bugs Bunny';
displayName();  // ReferenceError: displayName is not defined

Function Hoisting

  • JavaScript source is actually compiled but no executable is generated
  • Compiler makes at least two passes through code
  • Hoisting is the JavaScript interpreter’s action of moving all variable and function declarations to the top of the current scope, regardless of where they are defined
  • Function declarations are "hoisted" (moved) to top during compilation phase
    • Means that functions don't have to declared / defined before being used
  • Function expressions are not hoisted
// ex 1
// ... more code
console.log(sum(10,10));  // 20

// ... more code
function sum(num1, num2) {
  return num1 + num2;
}

// ... more code

// code after compilation
// ... more code
function sum(num1, num2) {  // function declaration is hoisted
  return num1 + num2;
}

// ... more code
console.log(sum(10,10));    // 20
// ex 2
// ... more code
console.log(sum(10,10));

// ... more code
var sum = function (num1, num2) {  // function expression is not hoisted
  return num1 + num2;
};
...

// code after compilation
// ... more code
var sum;                          // variable is hoisted
console.log(sum(10,10));          // throws ReferenceError, sum is undefined
sum = function (num1, num2) {     // function expressions are not hoisted
  return num1 + num2;
};

Variable Hoisting

  • Variables declared with var are hoisted to top during compile stage
  • Best practice is to use const and let to declare variables at the beginning of a block - making hoisting unnecessary
// ex 1
// ... more code
function f() {
  console.log(a);
  console.log(b);
  var a = b;
  var b = 2;
  console.log(b);
  console.log(a);
}
// ... more code

// code after compilation
// ... more code
function f() {
  var a;            // variable declaration hoisted to top
  var b;            // ditto
  console.log(a);   // 'undefined'
  console.log(b);   // 'undefined'
  a = b;
  b = 2;
  console.log(b);   // 2
  console.log(a);   // 'undefined'
}
// ... more code
  • Function declarations are hoisted/moved to top first, then variables
// ex 1
// ... more code
var a = b();
var c = d();

// .. more code
console.log(a);
console.log(c);

// .. more code
function b() {  // function declaration
  return 2;
}

var d = function() {  // function expression
  return b();
}
// ... more code

// code after compilation
// ... more code
function b() {
  return 2;
}

// ... more code
var a;
var c;
var d:
a = b();
c = d();            // throws ReferenceErr, d is undefined
console.log(a);     // 2
console.log(c);     // 'undefined'

d = function() {
  return b();
}
// ... more code
  • Best practice to prevent problems due to hoisting
    • Use function declarations - not function expressions
    • Define function expressions (and function declarations) before invoking them

Data Types (Part 2)

Array Type

  • An array is an ordered collection of values
  • Use square brackets and index to access array elements
  • If an element in an array is empty, undefined is returned

Creating Arrays

// ex 1
// Create array literal
let empty = [];
empty[0]         // undefined
empty[42]        // undefined

// ex 2
const primes = [2, 3, 5, 7, 11];
primes[0];    // 2
primes[1];    // 3
let misc = [ 1.1, true, "a" ];

// ex 3
// Create array using Array Constructor
let arr = new Array();
let arr1 = new Array(10);  // all elements are undefined

// ex 4
// Multidimensional array
let arr = [ [0,'zero'], [1,'one'], [2,'two'] ];
arr[0][0];     // 0
arr[0][1];     // 'zero'
arr[2][0];     // 2
arr[1][1];     // 'one'
  • Arrays inherit methods and properties from Array.prototype
let arr = [];
arr.length;      // 0
arr.push(1,'two', 'three');
arr.reverse();   // 'three', 'two', 1
arr.length;      // 3

  • In JavaScript, Arrays are a specialized kind of object
  • Array indexes are really just property names that happen to be integers
  • The square brackets used to access array elements work just like the square brackets used to access object properties
typeof [];      // object
typeof Array()  // object
typeof Array;   // function

let obj = {};
obj[1] = "one";   // Index it with an integer
obj.valueOf();    // {1: 'one'}
  • What is special about arrays is that when you use property names that are non-negative integers less than 2^32, the array automatically maintains the value of the length property for you

Adding/Deleting Array Elements

// ex 1
const arr = [];         // Start with an empty array
arr.push("zero")        // Add a value at the end.  arr = ["zero"]
arr.push("one", "two")  // Add two more values.  arr = ["zero", "one", "two"]

// ex 2
const arr = [1,2,3];
arr.length;      // 3
arr;             // [1, 2, 3]
delete arr[1];   // a now has no element at index 1

// length is still 3 - deleted value is replaced by undefined
arr.length;      // 3
arr[1];          // undefined

// Use in operator to check for value in array
1 in arr         // => false: no array index 1 is defined
arr.length       // => 3: delete does not affect array length

Array Copy / Clone

  • By default arrays are copied by reference
// ex1
let arr = [1,2,3,4,5];
arr;      // [1, 2, 3, 4, 5]

let arr1 = arr;
arr1;     // [1, 2, 3, 4, 5]
arr[0] = 'one';

// arr and arr1 point to the same array
arr;      // ['one', 2, 3, 4, 5]
arr1;     // ['one', 2, 3, 4, 5]
  • To copy an array by value use arr.slice()`` or _Spread syntax_ [...]```
let arr = [1,2,3,4,5];

let arr1 = arr.slice(0);
arr1: // [1,2,3,4,5]

// Spread syntax works too !
// let arr1 = [ ...arr ];
arr1;     // [1, 2, 3, 4, 5]
arr[0] = 'one';

// arr and arr1 are different arrays
arr;      // ['one', 2, 3, 4, 5]
arr1;     // [ 1, 2, 3, 4, 5]

Searching in an Array

  • arr.indexOf(), arr.lastIndexOf(), arr.includes()
let fruits = ['apple', 'orange', 'grapes', 'pear', 'cherries', 'apple'];
fruits.indexOf("grapes"); // 2
fruits.includes("grapes"); // true
fruits.indexOf("plum"); // -1
fruits.indexOf("apple"); // 0 
fruits.lastIndexOf("apple"); // 5
  • arr.find(), arr.findIndex(), arr.findLastIndex()
let result = arr.find(function(item, index, array) {
  // if true is returned, item is returned and iteration is stopped
  // for falsy scenario returns undefined
});

// ex 1
let users = [
  {id: 1, name: "Ray"},
  {id: 2, name: "Pete"},
  {id: 3, name: "Mary"},
  {id: 4, name: "Ray"}
];

let user = users.find(item => item.id == 1); // arrow function in use
// array elements (item) are objects, item = users[0], item = users[1] etc.
// returns object that with id = 1;
user; // {id:1, name: "Ray"}

// ex 2
let userIdx = users.findIndex(item => item.id === 1);  // 0

// ex 3
let lastIdx = users.findLastIndex(item => item.id === 1); // 3
  • arr.filter()
  • Works like arr.find() but can return multiple values (in an array)
let results = arr.filter(function(item, index, array) {
  // if true item is pushed to results and the iteration continues
  // returns empty array if nothing found
});

// ex 1
let users = [
  {id: 1, name: "Ray"},
  {id: 2, name: "Pete"},
  {id: 3, name: "Mary"},
  {id: 4, name: "Ray"}
];
// returns array of the first two users
let someUsers = users.filter(item => item.id < 3); // {id: 1, name: "Ray"} and   {id: 2, name: "Pete"}

Transforming Arrays

  • Methods that transform and reorder an array
  • Use arr.map(), arr.sort(), arr.reverse(), arr.split(), arr.join()
let result = arr.map(function(item, index, array) {
  // returns the new value instead of item
});

// ex 1
let fruits = ['apple', 'orange', 'grapes', 'pear', 'cherries', 'apple'];
// replace each array item (string) with its length
let itemLen = fruits.map(item => item.length); // [ 5, 6, 6, 4, 8, 5 ]

// ex 2
let arr = [ 1, 2, 15 ];
// Comparison function is only required to return a positive number to say “greater” 
// and a negative number to say “less
arr.sort(function(a, b) { return a - b; }); // 1, 2, 15

// ex 3
// Use arrow function
arr.sort( (a, b) => a - b ); // 1, 2, 15

// ex 4
camelize(str)
camelize("background-color") // 'backgroundColor';
camelize("list-style-image") // 'listStyleImage';
camelize("-webkit-transition") // 'WebkitTransition';

function camelize(str) {
  return str
    .split('-') // splits 'my-long-word' into array ['my', 'long', 'word']
    .map(
      // capitalizes first letters of all array items except the first one
      // converts ['my', 'long', 'word'] into ['my', 'Long', 'Word']
      (word, index) => index == 0 ? word : word[0].toUpperCase() + word.slice(1)
    )
    .join(''); // joins ['my', 'Long', 'Word'] into 'myLongWord'
}

Common Array Methods

  • arr.at()
let arr = [1, 'two', 3, 'four'];
arr.at(-1); // 'four'
arr.at(0); // 1
arr.at(1); // 'two'
  • arr.join()
arr = [1, 'two', 3, 'four'];
// Convert array to string
arr.join();       // '1, two, 3, four'
arr.join(' & ')   // "1 & two & 3 & four"
  • arr.slice()
arr = [1, 'two', 3, 'four'];
// Slice an array
arr.slice(1,3)   // ['two', 3]
  • arr.splice() method is a Swiss army knife for arrays - It can do everything: insert, remove and replace elements
arr = [1, 'two', 3, 'four'];
// Array splice
// Inserts and / or removes array items at a particular position
arr.splice(3,1, 'number 4');  // [1, 'two', 3, 'number four']

// Remove array item
arr.splice(3,1);  // [1, 'two', 3]
  • arr.reverse()
arr = [1, 'two', 3, 'four'];
// Reverse array
arr.reverse();   // [3, 'two', 1]
  • Array.from(obj [, mapFn, thisArg])
// Use to convert array-like variable to real array
function f() {
  return Array.from(arguments);
}

f(1, 2, 3);  // [ 1, 2, 3 ]

Array Iteration

  • Use standard for loop
let arr = [1,2,3,4,5];
for (let i=0, max = arr.length; i < max; i++) {
  console.log(arr[i]);
}
  • ES6 added the for-of loop
let arr = [1,2,3,4,5];
for (const val of arr) {   // Note that val has local scope
   console.log(val);  // 1, 2, 3, 4, 5
}
val;      // Uncaught ReferenceError: val is not defined
  • Technically can also use for-in loop (since an array is an object) but is slower
    and may iterate over non-number keys - should not use
let arr = [1,2,3,4,5];
for (const key in arr) {   // Note that key has local scope
   console.log(arr[key]);  // 1, 2, 3, 4, 5
}
  • Use Array.forEach() method
  • Allows to run a function for every element of the array
arr.forEach(function(item, index, array) {
  // ... do something with an item
});
// ex 1
// print array values
let arr = [1,2,3,4,5];

arr.forEach(function(item, index, array) {
  console.log(item); // 1,2,3,4,5
)};

// ex 2
// Compute the sum of the array elements
let sum = 0;

arr.forEach(function(item) { 
  sum += item; }
);
sum   // 15

Test For Array

Array.isArray([])     // true
Array.isArray({})     // false

Spread Syntax ...

  • Spread Syntax
  • Spread Syntax was introduced in ES6
  • When used with array, must be used inside array - otherwise syntax error
  • Use to copy an array by value
// ex 1
let arr = [1,2,3,4,5];
let arr1 = ...arr;      // Syntax error
let arr1 = [ ...arr];
  • Use to concatenate / merge arrays
// ex 2
let arr = [1, 2, 3];
let arr1 = [ ...arr, ...[ 4,5,6] ];
arr1;          // [1, 2, 3, 4, 5, 6]
  • Use to concatenate objects
// ex 3
const o1 = {a: 'a', b: 'b'};
const o2 = {c: 'c', ...o1};
o2;            // {a: 'a', b: 'b', c: 'c'}
  • Use in function call to expands an Iterable into a list of arguments
// ex 1
let arr = [3, 5, 1];
Math.max(...arr) ); // 5 (spread turns array into a list of arguments)

// ex 2
let arr1 = [1, -2, 3, 4];
let arr2 = [8, 3, -8, 1];

Math.max(1, ...arr1, 2, ...arr2, 25); // 25

Iterables

  • Iterable objects are a generalization of arrays
  • Iterables allow us to make any object useable in a for..of loop
  • Arrays and strings are "built-in" iterables, plain objects aren't
// ex 1
for (let char of "test") {
  console.log(char); // t, then e, then s, then t
}

for (let item of [1,2,3]) {
  console.log(item); // 1, then 2, then 3
}

for (let item of {x:1, s: 'string'}) { // TypeError: ({x:1, s:"string"}) is not iterable
  console.log(item); 
}

// use for-in for objects
let obj = {x:1, s: 'string'};
for (let key in obj) {
  console.log(obj[key]); // 1, 'string' 
}

  • In summary, Iterables are objects that implement the Symbol.iterator method
  • Array-likes are objects that have indexes and length, so they look like arrays
  • Use Array.from() to convert an iterable or array-like value to a real array
// ex 1
let arrayLike = {
  0: "Hello",
  1: "World",
  length: 2
};
let arr = Array.from(arrayLike);
arr.pop(); // "World"

Set Type

  • Introduced in ES6
  • Set Object
  • A Set is a special type collection – “set of values” (without keys), where each value may occur only once
  • The Set object lets you store unique values of any data type - primitive or object
  • Useful way to keep track of data without having to check for duplication

Methods and Properties

  • new Set([iterable]) – creates the set, with optional iterable (e.g. array) of values for initialization.
  • set.add(value) – adds a value (does nothing if value exists), returns the set itself.
  • set.delete(value) – removes the value, returns true if value existed at the moment of the call, otherwise false.
  • set.has(value) – returns true if the value exists in the set, otherwise false.
  • set.clear() – removes everything from the set.
  • set.size – is the elements count.
// ex 1
let set = new Set();

let user1 = { name: "Ray" };
let user2 = { name: "Roy" };
let user3 = { name: "William" };

// visits, some users come multiple times
set.add(user1);
set.add(user2);
set.add(user3);
set.add(user1);
set.add(user2);

// set keeps only unique values
set.size; // 3

for (let user of set) {
  user.name; // 'Ray', 'Roy', 'William'
}

// ex 2
// Iteration over Set
let set = new Set(["oranges", "apples", "bananas"]);
for (let value of set) alert(value);

// the same with forEach:
set.forEach((value, valueAgain, set) => {
  alert(value);
});

// ex 3
// Filter unique array members
let fruits = ["apple", "orange", "grapes", "apple",
  "cherries", "cherries", "orange",
];
let unique = fruits => Array.from(new Set(fruits));
unique(fruits); // ['apple', 'orange', 'grapes', 'cherries']

Map Type

  • Introduced in ES6
  • Map Object
  • A Map is used to keep a list of key and value pairs
  • Similar to hashes or dictionaries in other programming languages
  • Maps are similar to JavaScript objects but have some important differences:
    • Objects are limited to using strings for key values - Maps can use any data type as a key
    • Unlike objects, Maps don't use Inheritance (via Prototype Chaining) and are solely focused on the storage and retrieval of key-value pairs

Methods and Properties

  • new Map() – creates the map.
  • map.set(key, value) – stores the value by the key.
  • map.get(key) – returns the value by the key, undefined if key doesn’t exist in map.
  • map.has(key) – returns true if the key exists, false otherwise.
  • map.delete(key) – removes the element (the key/value pair) by the key.
  • map.clear() – removes everything from the map.
  • map.size – returns the current element count
// ex 1
let map = new Map();
typeof map; // "object"

map.set('1', 'str1');   // a string key
map.set(1, 'num1');     // a numeric key
map.set(true, 'bool1'); // a boolean key

// Regular object would convert keys to string
// Map keeps the type, so these two are different
map.get(1); // 'num1'
map.get('1'); // 'str1'
map.size; // 3

// ex 2
// Maps can use objects as keys
let user1 = { name: "Ray" };
// for every user, let's store their visits count
let visitsCountMap = new Map();

// user1 is the key for the map
visitsCountMap.set(user1, 123);
visitsCountMap.get(user1); // 123

// ex 3
// array of [key, value] pairs
let map = new Map([
  ['1',  'str1'],
  [1,    'num1'],
  [true, 'bool1']
]);
map.get(1); // 'num1'

alert( map.get('1') ); // str1

Map Iteration

  • For looping over a map, there are 3 methods:
  • map.keys() – returns an Iterable for keys,
  • map.values() – returns an Iterable for values,
  • map.entries() – returns an Iterable for entries [key, value], it’s used by default in for..of
let recipeMap = new Map([
  ['cucumber', 500],
  ['tomatoes', 350],
  ['onion',    50]
]);

// iterate over keys (vegetables)
for (let vegetable of recipeMap.keys()) {
  alert(vegetable); // cucumber, tomatoes, onion
}

// iterate over values (amounts)
for (let amount of recipeMap.values()) {
  alert(amount); // 500, 350, 50
}

// iterate over [key, value] entries
for (let entry of recipeMap) { // the same as of recipeMap.entries()
  alert(entry); // cucumber,500 (and so on)
}

Destructuring Assignment

  • Destructuring assignment
  • Introduced in ES6
  • Destructuring works on any Iterable (Array, String, Set, Map)
  • It takes values out of the Iterable and assigns them as individual values
  • Works similarly to PHP list() construct
// ex 1
let [x, s, o] = [1, 'a string', {}];
typeof x;   // number
x;          // 1
typeof s;   // string
s;          // 'a string'
typeof o;   // object
o;          // {}

// ex 2
let [firstName, lastName] = "Test Tester".split(' ');
firstName; // "Test"
lastName;  // "Tester'
// Same as
let name = "Test Tester".split(' ');
firstName = name[0], lastName = name[1];

// ex 3
let [a, b, c] = "abc"; // ["a", "b", "c"]
let [one, two, three] = new Set([1, 2, 3]);
// one == 1, two == 2, three == 3

// ex 4
let user = {
  name: "Billie",
  age: 45
};

// loop over the keys-and-values
for (let [key, value] of Object.entries(user)) {
  console.log(`${key}:${value}`); // name:Billie, then age:45
}

// ex 5
// Using spread operator ...
]et [name1, name2, ...names] = ["June", "Kay", "Ray", "William"];
// names is an array of items, starting from the 3rd one
names[0]; // "Ray"
names[1]); // "William"
names.length); // 2

Object Destructuring

  • The destructuring assignment also works with objects
let options = {
  title: "Menu",
  width: 100,
  height: 200
};

let {title, width, height} = options;
title;  // Menu
width;  // 100
height; // 200

// property order doesn't matter - the following also works
let {height, width, title } = options;
title;  // Menu
width;  // 100
height; // 200

Smart Function Parameters

// we pass object to function
let options = {
  title: "My menu",
  items: ["Item1", "Item2"]
};

// ...and it immediately expands it to variables
function showMenu({title = "Untitled", width = 200, height = 100, items = []}) {
  // title, items – taken from options,
  // width, height – defaults used
  console.log( `${title} ${width} ${height}` ); // My Menu 200 100
  consoe.log( items ); // Item1, Item2
}

showMenu(options);

Objects (Part 2)

  • Fundamental operations that can be performed on objects
    • Create
    • Query and Set
    • Delete
    • Test
    • Iteration

Object Creation (Part I)

Object Literal

  • Inherits properties from Object.prototype
// ex 1
let obj = {};
const user = { name :  "", registered : false }

// ex 2 
// Shorthand syntax introduced in ES6
// Use when object properties and values have same name

// long way - property and value have same name
let user = { name: name, email: email };

// short syntax ES6 way
let user = { name, email };

Use Constructor

  • Use new keyword
// ex 1
const o = new Object();       // built-in constructor
const a = new Array();        // built-in constructor
const d = new Date();         // "
const r = new RegExp("js");   // "
  • Above instances (o, a, d, r) inherit properties and methods from their corresponding prototype object
    • o inherits properties and methods from Object.prototype
    • a inherits properties and methods from Array.prototype and Object.prototype
    • d inherits properties and methods from Date.prototype and Object.prototype
  • Custom Object
function User(name) {
  this.name = name;
  this.isAdmin = false;
}

let user = new User("Ray");
console.log(user.name); // "Ray"
console.log(user.isAdmin); // false

Use Object.create()

// ex 1
// Create new object obj using Object.create()
//  Object.create() parameter is the created object's prototype
let obj = Object.create( { x : 1, fruit : 'grapes' });
obj.x;       // 1
obj.fruit;   // 'grapes'

// ex 2
const p = { x : 1 };
const o = Object.create(p);
p.isPrototypeOf(o)    // true

Use obj.constructor Property

  • All objects have a constructor property
  • obj.constructor returns the constructor function that created it
// ex 1
function User(name) {
  this.name = name;
}

let user = new User();
user.constructor;  // Function: User

// ex 2
let o = new Object();
o.constructor;    // Function: Object

let o1 = {};
o1.constructor;   // Function: Object

  • You can use object constructor property to instantiate a new object
user1 = new user.constructor();
user1 instanceof User;      // true

Object Copy / Clone

  • Use JSON.stringify() and JSON.parse()
// ex 1
const obj = {
  n : 100,
  s: 'string',
  o : { x : 1, fruit : 'apples', f : function() { console.log('fruit is ' + this.fruit)} }
};

const obj1 = JSON.parse(JSON.stringify(obj));
obj.o.fruit         // 'apples'
obj1.o.fruit        // 'apples'

obj1["o"]["fruit"] = 'oranges';
obj1.o.fruit        // 'oranges'
obj.o.fruit         // 'apples', proves obj1 is a clone of obj

obj.o.f()           // 'fruit is apples'
obj1.o.f()          // throws reference error, doesn't work for functions
  • Use Object.assign()
// ex 1
const obj = {
  n : 100,
  s: 'string',
  o : { x : 1, fruit : 'apples', f : function() { console.log('fruit is ' + this.fruit)} }
};

const obj1 = Object.assign({}, obj);
obj1.o.fruit = 'oranges';
obj1.o.fruit        // 'oranges'

// Object.assign() doesn't work for nested reference variables (object, array) - Use structuredClone()
obj.o.fruit         // 'oranges'
  • Use Spread Syntax
// ex 1
const obj = {
  n : 100,
  s: 'string',
  o : { x : 1, fruit : 'apples', f : function() { console.log('fruit is ' + this.fruit)} }
};

const obj1 = {...obj};
obj1.o.fruit = 'oranges';
obj1.o.fruit        // 'oranges'

// doesn't work for nested reference variables (object, array)
obj.o.fruit         // 'oranges'

  • Use structuredClone()
// ex 1
const obj = {
  n : 100,
  s: 'string',
  o : { x : 1, fruit : 'apples', } // functions can't be cloned
};

const obj1 = structuredClone(obj);
obj1.o.fruit = 'oranges';
obj1.o.fruit        // 'oranges'
obj.o.fruit         // 'apples' - proves obj1 is a clone of obj

Object prototype

  • Use object constructor property , Object.getPrototypeOf() or __proto__ to get an object's prototype
// ex 1
function User(name) {
  this.name = name;
  this.printName = () => console.log(this.name)
}
const u1 = new User('Ray');

typeof u1.constructor;               // 'function'
typeof u1.constructor.prototype;     // 'object'
u1.constructor.prototype             // { constructor: ƒ User(name), __proto__: Object }
Object.getPrototypeOf(u1);           // ""
u1.__proto__;                        // ""
  • Object Copy with Object.create()
// ex 1
let u2 = Object.create(Object.getPrototypeOf(u1), Object.getOwnPropertyDescriptors(u1));

  • Use isPrototypeOf() or instanceof to check object instance
// ex 1
User.prototype.isPrototypeOf(u1);  // true
u1 instanceof User;                // true
  • Use object hasOwnProperty() method to check if a method or property is its own property or is inherited from the prototype
  • Prototype properties are shared by every instance of a class, while and object's own property isn't
  • The prototype object is Live
    • If a new property or method is added to the prototype, any instances of its class will inherit the new properties and methods automatically even if that instance has already been created

Query/Set Object properties

  • Use dot notation or brackets to access object properties
    • obj.property // property used as identifier
    • obj["property"] // property used as string
// ex 1
// query property
let obj = { x : 1000 }
obj.x       // 1000
obj["x"]    // 1000
obj['x']    // 1000

// dynamically set new property
obj.s = 'This is a string';
obj;    // {x: 1, s: "This is a string"}

Computed Properties

  • ES6 introduced the ability to create objects with computed property values
  • JavaScript code can be placed inside square brackets
    • Property will be the return value of that code
  • Symbols are often used as a computed property
// ex 1
let user = { ['user' + 'Name'] : 'Test'};
user;    // {userName: "Test"}

// ex 2
const sym1 = Symbol();
const sym2 = Symbol();

// create object using symbols
let obj = { [sym1] : 'Symbol 1', [sym2] : 'Symbol 2' };
obj;    // {Symbol(): "Symbol 1", Symbol(): "Symbol 2"}

obj[sym1];  // 'Symbol 1'
obj[sym2];  // 'Symbol 2'

Deleting Object Properties

  • Use delete keyword
// ex 1
const obj = { x : 1 };
delete obj.x;           // ok, returns true
delete obj.x;           // ok, does nothing
delete obj.toString();  // does nothing, can't delete inherited method

Testing Object Properties

  • Use in operator or Object.hasOwnProperty()
// ex 1
const obj = { x : 1 };
"x" in obj            // returns true
"y" in obj            // returns false
"toString" in obj     // returns true, toString() is inherited property

obj.hasOwnProperty("x")         // true
obj.hasOwnProperty("toString")  // false
  • Can also test properties by testing against undefined
// ex 1
obj.x !== undefined             // true
obj.y !== undefined             // false
obj.toString() !== undefined    // true

Object Iteration

  • Use for..in loop. Object property must be enumerable for it to show in for..in loop
// ex 1
const obj = { x : 1, y : 2, z : 3 };
for (const p in obj) {
  if (obj.hasOwnProperty(p)) {
      console.log(p, obj[p]);
  }
}
x 1
y 2
z 3

// ex 2  
// merge properties of objects o1 and o2
function merge(o1, o2) {
  for (const p in o2) {
    if (o2.hasOwnProperty(p)) {
      o1[p] = o2[p];
    }
  }
  return o1;
}
  • Use for-of loop with Object.keys(), Object.values() and Object.entries()
  • Object.keys(), Object.values() and Object.entries() return real arrays not Iterables
// ex 1
Array.isArray(Object.keys({})) // true

// ex 2
let registered = true;
const obj = { name: 'Tester', email: '[email protected]', 
  status: registered ? true : false
};

// enumerate keys
for (const p of Object.keys(obj)) {
  console.log(p);   // name, email, status
}

// enumerate values
for (const p of Object.values(obj)) {
  console.log(p);   // 'Tester', '[email protected]', true
}

// enumerate keys and values
for(const [key,val] of Object.entries(obj)) {
    console.log(`${key}: ${val}`);
}
name: Tester
email: [email protected]
status: true

// ex 3
let salaries = {
  "Ray": 100,
  "Anika": 300,
  "Brenda": 250
};
// Sum salaries and return result
function sumSalaries(salaries) {
  // Object.values(salaries) [100, 200. 250]
  return Object.values(salaries).reduce((a, b) => a + b, 0 /* initial value */) // 650
}
sumSalaries(salaries); // 650

Transforming Objects

  • Use Object.entries(obj) to get an array of key/value pairs from object.
  • Use Object.fromEntries(arr) on the resulting array to turn it back into an object
// ex 1
// Double the prices of object items
let fruits = {
  apple: 1,
  orange: 2,
  cherries: 4,
};

// convert prices to array, map each key/value pair into another pair
// and then fromEntries gives back the object
let fruits1 = Object.fromEntries(
  Object.entries(fruits).map(item => [item[0], item[1] * 2])
);
fruits1; // Object { apple: 2, orange: 4, cherries: 8 }

Nested Objects

  • Objects can be nested
const nbaTeams = {
teams: [ { city: 'Golden State', teamName : 'Warriors' },
         { city: 'Chicago', teamName : 'Bulls' }
       ]
};
nbaTeams.teams[0].city;      // 'Golden State'
nbaTeams.teams[0].teamName;  // 'Warriors'

Objects as Parameters

// ex1
let obj = { greeting: "Hello", name: "Ray", age: 54 };

function greet(obj) {
    return `${obj.greeting}! Name is ${obj.name} and I'm ${obj.age}`;
}
let res = greet(obj);
res;   // "Hello! Name is Ray and I'm 54"

// ex 2
function greet({greeting='Hello!', name, age}) {
    return `${greeting}! Name is ${name} and I'm ${age}`;
}

// Note that property order can be changed
res = greet({greeting: "What's up!", age: 54, name: 'Ray'});
res;   // "What's up! Name is Ray and I'm 54"

// ex 3
// Use default greeting
res = greet({name: 'Ray', age: 54});
res;   // "Hello! Name is Ray and I'm 54"

this keyword

  • All JavaScript functions are implicitly passed
    • arguments array
    • this object
  • this contains a reference to the object (normally the calling object) that was in context when a function or method is invoked
  • this is always an object
    • window object
    • global object
    • user defined object

this in Global Function

// ex 1
// global function
function f() {
  console.log(this === window);
}

window.f();   // true
f();          // true

// ex 2
function f1() {
  // strict mode forces this to be 'undefined' if no 'context' object
  'use strict';
  console.log(this === window);
}

window.f();   // true, window object invoked f()
f();          // false

this in Object Method

// ex 1
function print_name() {
  console.log(this.name);
}

const name = "William";
const obj1 = { name : "Julie", print_name : print_name };
const obj2 = { name : "Dante", print_name : print_name };

print_name();         // 'William'
obj1.print_name();    // 'Julie' (this == obj1)
obj2.print_name();    // 'Dante' (this == obj2)

this Binding Rules

  • There are four rules for determining this value (or how this value gets bound)
  • All rules depend on call site - the place in code where function gets executed

Default Binding Rule

  • This rule has lowest precedence
  • In strict mode, this ===undefined
  • In non-strict mode, this == window or global (node.js)
// ex 1
var x = 100;
function f() { console.log(x) };
function f1() { console.log(this.x);   // this == window }
function f2() { console.log(window.x); // this == window }
f();      // 100
f1():     // 100
f2():     // 100
x;        // 100
this.x    // 100
window.x  // 100

// ex 2
// let and const behave differently
let x = 100;
function f() { console.log(x) };
function f1() { console.log(this.x);   // this == window }
function f2() { console.log(window.x); // this == window }
f();      // 100
f1():     // undefined
f2():     // undefined
x;        // 100
this.x    // undefined
window.x  // undefined

Implicit Binding Rule

// ex 1
let obj = { x : 1 , f : function(x) { this.x = x; } };
obj.x         // 1
obj.f(100);   // inside function f, this == obj
obj.x;        // 100

Explicit Binding Rule

  • Use Function.prototype.call() or Function.prototype.apply() methods to force this binding
  • Both call() and apply() take first parameter as this value to use in function
// ex 1
function f(first_name) {
  console.log(`${first_name} ${this.last_name}`);
}

let last_name = 'Jones';
let obj = { last_name : "Smith" };

f('William');         // 'William Jones'
f.call(obj, 'Sam');   // 'Sam Smith'

Hard Binding

  • Force this to be a given object
// ex 1
function f(first_name) {
  console.log(first_name + ' ' + this.last_name);
}

let obj = { last_name : "Jones" };
let obj1 = { last_name : "Smith" };

let f1 = f;
f = function(first_name) {
  // this is present here but not used
  // instead we force this to be obj in f1 call
  f1.call(obj, first_name);   // force last_name to always be 'Jones'
};

f('William');           // 'William Jones'
f.call(obj1, 'Sam');    // 'Sam Jones'

// Better yet, define and use bind() function

// ex 2
function bind(func, o) {
  return function(first_name) {
    func.call(o, first_name); // this is the function "call" site
  }
}

function f(first_name) {
  console.log(first_name + ' ' + this.last_name);
}

let obj  = { last_name : "Jones" };
let f1 = bind(f, obj);
f1('William');    // 'William Jones'
f1('Sam');        // 'Sam Jones'

// ex 3
// Create a bind2() function and add to Function.prototype (all functions will
now inherit bind2() method)

if (!Function.prototype.bind2) {
  Function.prototype.bind2 = function(o) {
    var func = this;  // i.e. the function
    return function() {
      return func.apply(o, arguments);
    }
  }
}

function f(first_name) {
  console.log(first_name + ' ' + this.last_name);
}

let obj  = { last_name : "Sanchez" };
f = f.bind2(f,obj);
f('Julio');   // Julio Sanchez

Hard Binding with bind() method

  • Function.prototype.bind() lets you specify the object that should be used as this for all calls to the given function
  • Reference Function Binding
// ex 1
let obj = { last_name : "Jones" };
function f(first_name) {
  console.log(`${first_name} ${this.last_name}`);
}

// Force last name to always be Jones
f1 = f.bind(obj);
f1('William');   // 'William Jones'

this Binding

  • Be aware of this binding in nested functions and callbacks
  • In the below example inside forEach() callback, this loses its scope and will refer to a name variable in global space - this variable most likely won't exist
// ex 1
let User = {
  name: '',
  preferences: [],
  profile: {},
  showPreferences: function() {
    this.preferences.forEach(function(preference) {
     console.log(`${this.name}: ${preference}`);  // this loses its scope
    });
  }
};

let u1 = Object.create(User);
u1.name = 'Ray';
u1.preferences = ['one', 'two', 'three'];
u1.showPreferences();  
: one
: two
: three

  • Fix #1
  • Use that = this pattern
// ex 2
let User = {
  name: '',
  preferences: [],
  profile: {},
  showPreferences: function() {
    that = this;
    this.preferences.forEach(function(preference) {
     console.log(`${that.name}: ${preference}`); 
    });
  }
};

let u1 = Object.create(User);
u1.name = 'Ray';
u1.preferences = ['one', 'two', 'three'];
u1.showPreferences();  
Ray : one
Ray : two
Ray : three

  • Fix #2
  • Use for-of loop to iterate arrays instead of forEach()
...
  showPreferences: function() {
    for (const preference of this.preferences) {
      console.log(`${this.name}: ${preference}`); 
    }
  }
...
  • Fix #3
  • Use Arrow function
  • Arrow functions don't have their own this context
  • This means this remains bound to the original object making the function call
// ex 3
...
  showPreferences: function() {
    this.preferences.forEach((preference) => {
     console.log(`${this.name}: ${preference}`);
   });
  }
...

How to determine this

  • Steps in determining this value / Precedence rules
    • If function was called with new, this refers to the object returned
    • If function was called with call() or apply() method, this is first parameter (object) passed in (Explicit Binding)
    • If function called via containing/owning object, this is the calling object (Implicit Binding)
    • Otherwise, this == window or global object (except in strict mode)

Object Property Flags and Descriptors

  • Reference Object Property Flags and Descriptors
  • In addition to a value, object properties have three special attributes / flags
    • writable – if true, the value can be changed, otherwise it’s read-only
    • enumerable – if true, then listed in loops, otherwise not listed
    • configurable – if true, the property can be deleted and these attributes can be modified, otherwise not
  • Use Object.getOwnPropertyDescriptor() or Object.getOwnPropertyDescriptors() to get object property flags
  • Use Object.defineProperty() or Object.defineProperties() to create a properties and / or set property flags
  • Clone object using Object.defineProperties()
// ex 1
let obj = { 
  x:1, s:'this is a string', a: [1,2,`three`], f(s) {console.log(s, this.a);}
} 
let obj1 = Object.defineProperties({}, Object.getOwnPropertyDescriptors(obj));

Property Getters and Setters

  • Property Getters and Setters
  • Accessor properties are represented by “getter” and “setter” methods
  • In an object literal they are denoted by the get and set keywords
// ex 1
let user = {
  name: "Porky",
  surname: "Pig",
  get fullName() {
    return `${this.name} ${this.surname}`;
  },
  set fullName(value) {
    [this.name, this.surname] = value.split(" ");
  }
};

user.name;       // 'Porky'
user.surname;    // 'Pig'

// Note no parentheses is needed
user.fullName;   // 'Porky Pig'

// set fullName is executed with the given value
user.fullName = "Bugs Bunny";
user.name);      // 'Bugs'
user.surname;    // 'Bunny'

Global Object

  • At run-time, the JavaScript interpreter creates an object called the global object and assigns properties to it
  • In a browser the global object is window or this
  • In node.js global object is global or this
  • Recent addition - In all environments globalThis can be used for the global object
  • Properties and methods of the global object are globally defined symbols available to a JavaScript program

Global Variables

  • let and const behave differently than var
// ex 1
var x  = 100;
let y = 200;
const z = 400;
x;           // 100
window.x;    // 100
this.x;      // 100
y;           // 200
window.y;    // undefined
this.y;      // undefined
z;           // 400
window.z;    // undefined
this.z;      // undefined

Global Object Properties

  • Several global properties are automatically created at JavaScript run-time
  • Examples Math, JSON, undefined, Infinity, NaN

Math Object

  • Math object has several properties representing mathematical constants
  • Also has methods that carry out a number of common mathematical operations
// ex 1
Math.round(2.75);           // 3
window.Math.round(2.75)     // 3
this.Math.round(2.75)       // 3

JSON Object

  • JSON (JavaScript Object Notation) is a data storage format used for data serialization / configuration and data transfer
  • In JavaScript, JSON is a string representation of an object
  • JSON object has two methods for manipulating JSON data, JSON.stringify() and JSON.parse()

JSON.stringify(value[, replacer, space])

  • JSON.stringify() takes an object and returns a JSON formatted string
// ex1
// Convert object to string using custom toString() function
let user = {
  name: "Ray",
  age: 45,
  toString() {
    return `{name: "${this.name}", age: ${this.age}}`;
  }
};

console.log(user); // {name: "Ray", age: 30}

// easier to use JSON.stringify() especially if embedded arrays or objects
// ex 2
let user = {
  name: "Ray",
  age: 45,
  hobbies: ["exercise", "reading", "web design", "travel"],
};
let json = JSON.stringify(user);
json; // '{"name":"Ray","age":45,"hobbies":["exercise","reading","web design","travel"]}'

  • JSON.stringify() ignores function properties (methods), Symbol keys and values, properties that store undefined
// ex 1
let user = {
  sayHi() { // ignored
    alert("Hello");
  },
  [Symbol("id")]: 123, // ignored
  something: undefined // ignored
};

console.log( JSON.stringify(user) ); // {} (empty object

JSON.parse(str [, reviver])

  • JSON.parse() takes a string of data in JSON format and returns an object
// ex 1
json; // '{"name":"Ray","age":45,"hobbies":["exercise","reading","web design","travel"]}'

let obj = JSON.parse(json);
for (let prop in obj) {
  console.log(prop, obj[prop]); 
}
name Ray, age 45, hobbies Array(4) [ "exercise", "reading", "web design", "travel" ]

// ex 2
// use reviver function to transform string data
let str = '{"title":"Conference","date":"2017-11-30T12:00:00.000Z"}';

let meetup = JSON.parse(str, function(key, value) {
  if (key == 'date') return new Date(value);
  return value;
});

meetup.date.toDateString(); // "Thu Nov 30 2017"

Global Values

  • undefined, Infinity and NaN are global values / properties of the global object
// ex1
typeof undefined;        // 'undefined'
type window.undefined    // 'undefined'
typeof this.undefined    // 'undefined'

let u = undefined;
typeof u;                // 'undefined'

Global Object Methods - isNaN(), parseInt(), eval(), encodeURI()

  • Global object methods are automatically created at JavaScript run-time
// ex 1
parseInt('50 something');          // 50
window.parseInt('50 something');   // 50
this.parseInt('50 something');     // 50

// ex 2
// eval() takes string contents and treats it as JavaScript code
eval("var x = 1000");
console.log(x);     // 1000

Timing Functions

One Shot Timer

  • Use window.setTimeout() and window.clearTimeout(timerID)
// ex 1
// display alert to user after 3 seconds
let time = 3;   // in seconds
const timerID = setTimeout(function() {
   alert(`${time} seconds expired`);
}, time * 1000);

// ex 2 - same as above using arrow function
let time = 3;   // in seconds
const timerID = setTimeout(() => alert(`${time} seconds expired`), time * 1000);

// ex 3 - cancel previous timer
clearTimeout(timerID);

Interval Timer

  • Use window.setInterval() and window.clearInterval(timerID)

Constructors - Date(), RegExp()

  • Several Constructors are automatically created at JavaScript run-time

Date Object / Constructor

  • Date and Time
  • Date objects contain information about dates and times
  • Each object represents a single moment in time
  • Consider using Moment.js for managing dates and times in JavaScript
// ex 1
const today = new Date();
today.toDateString();  // "Mon Apr 06 2020"
today.toString();      // "Mon Apr 06 2020 13:02:26 GMT-0700 (Pacific Daylight Time)"

// ex 2
const christmas = (new Date(2020, 11, 25)).toDateString(); // "Fri Dec 25 2020"

Digital Clock

<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>Digital Clock</title>
    <style>
        #clock {
            font: bold 24pt sans;
            background: #ddf;
            padding: 10px;
            border: solid black 2px;
            border-radius: 10px;
        }
    </style>
    <script>
        function displayTime() {
          const elt = document.getElementById("clock");
          const now = new Date();
          elt.innerHTML = now.toLocaleTimeString();
          setTimeout(displayTime, 1000);   
        }
        window.onload = displayTime;
    </script>
</head>

<body>
    <h3>Digital Clock</h3>
    <span id="clock"></span>
</body>

</html>

RegExp Object / Constructor

  • A Regular Expression (or RegExp) is a sequence of characters used to match text in user input, documents or other data source
  • RegExp consists of simple characters or a combination of simple and special characters

Creating a Regular Expression

// ex 1
// Literal notation
const re = /ab+c/;          // re is an object

// Use RegExp constructor
const re1 = RegExp('ab+c'); // re1 is an object
  • Simple patterns consists of characters for which you want a direct match
// ex 1
var re = /abc/;   // matches 'abc' exactly and in that order in a string
  • When match requires more than just a direct match, special (or meta) characters are used to perform the match
// ex 1
// * is a meta character in the example below and matches 0 or more occurrences
// of the preceding character
// The RegExp below matches the string 'abc' or the string 'ac'
const re = /ab*c/;

RegExp Methods

  • test()

    • Test if string matches the RegExp pattern. Returns true or false
  • split()

    • Split a string into an array of strings. Returns array of sub-strings
  • replace()

    • Replace matched string with replacement string. Returns new string
  • exec()

    • Execute a search for a match in specified string. Returns result array or null
  • match()

    • Retrieves matches when matching a string against a regular expression
    • Returns array of matched results or null
  • Use String.split(RegExp) to split a string into an array of strings

    • Returns array of sub-strings

RegExp Properties

  • global property makes the pattern return all matches

    • By default, the pattern only looks for the first occurrence of a match
    • Use g flag to set global property to true
  • ignoreCase property makes the pattern case-insensitive

    • By default, the pattern is case sensitive
    • Use i flag to set ignoreCase property to true
  • multiline property makes the pattern multiline

    • By default, a pattern will stop at the end of a line
    • Use m flag to set multiline property to true
  • Properties can be checked

// ex 1
let re = /test/ig;
re.ignoreCase;  // true
re.global;      // true
re.multiline;   // false

Regular Expressions Syntax

RegExp Examples

// test
// Check if variable has valid characters
const re = /[A-za-z_-]+/;
re.test('_a_variable')       // true
re.test(100);                // false

// Check for pdf files
const re1 = /.*\.pdf$/;
re1.test('file.pdf');      // true

// split
// Split a string separated by white space
const pattern = /\s+/;
'This is a string'.split(pattern);  // ["This", "is", "a", "string"]

// match
let str = 'string with 1234 and !#@, mixed in';
str.match(/[a-z]+/gi);   // ["string", "with", "and", "mixed", "in" ]

// replace
// simple text replace
const str = 'I like oranges';
str.replace(/oranges/, 'apples')    // 'I like apples'

// Filter non-alphanumeric characters in user name
const user_name = 'j;ohnsmith;@%';
user_name.replace(/\W+/g, '')   // 'johnsmith'

// exec
const re = /quick\s(brown).+?(jumps)/gi       // notice 2 caputures
re.exec("The quick brown fox jumps over")   // ["quick brown fox", "brown", "jumps"]

Functions (Part 2)

  • In JavaScript functions are first class objects
    • This means you can set properties and methods on functions - just like other objects

Function Properties

  • Functions can have properties
  • All functions have a length property that returns the number of parameters
// ex 1
function F(p1, p2) {
  if (isFinite(p1) && isFinite(p2)) {
    return p1 * p2;
  }
  return 'error';
}
F.length;   // 2
F.name;         // 'F'
F.description = 'My F function';
F.description;  // 'My F function'

// ex 2 - add custom property to function
function hello() {
  alert("Hello");
  // let's count how many times we run
  hello.counter++;
}
hello.counter = 0; // initial value
hello(); // Hello
helo(); // Hello
hello.counter; // 2

Function Methods

  • Two useful built-in function methods are call() and apply()
  • These are two powerful methods, as they allow generalized functions to be written that are not tied to specific objects by being methods of that object
  • This gives flexibility over how the functions can be used

call() method

  • The call() method can be used to set the value of this inside a function to an object that is provided as the first argument
// ex 1
let obj1 = { x : 100, f1 : function (x) { console.log(x * this.x); } };
let obj2 = { x : 200 };

obj1.f1(2);              // 200
obj2.f1(2);              // type error, obj2.f1 is not a function
obj1.f1.call(obj2, 2);   // 400, x is taken from obj2 not obj1

// ex 2
function sayHello(greeting='Hello'){
  return `${greeting}, my name is ${this.name}`;
}

const o = { name: 'Ray', x: 100, arr : [1,2,3] }
// object o is "this" inside function sayHello()
// Function parameters, if any, come after "this" argument
sayHello.call(o, 'Howdy');   // "Howdy, my name is Ray"

const o1 = {};
sayHello.call(o1);  // "Hello, my name is undefined"

// ex 3
// arguments variable is not true array but use forEach function
// on it using call method
function f(arr) {
  Array.prototype.forEach.call(arguments,function(elem,idx) {
      console.log(idx, elem);
  });
}

f(1,2,'three');

Output:
0 1
1 2
2 "three"

  • Use null as first parameter in call() if function, does not reference this
// ex1
function square(x) {
  return x*x;
}

square.call(null, 4);  // 16

apply() method

  • The apply() method works in the same as call(), except the arguments of the function are provided as an array or array-like / iterable object, even if there is only one argument
// ex 1
function F(a, b, c) {
  this.arr = [a, b, c ];
}

const o = {};
F.apply(o, [1, 2, 3]);

o.arr;     // [1, 2, 3]
Array.isArray(o.arr);  // true

Memoization

  • Adding a custom property to a function provides a useful feature called Memoization
  • Cache example
// ex 1
// Assume that square takes a long to compute
function square(x){
  square.cache = square.cache || {};
  if (!square.cache[x]) {  // check if result is cached
    square.cache[x] = x*x; // computation for new value
  }
  return square.cache[x];
}
square(3);     // 9
square(-11);   // 121
square.cache;  // {"3": 9, "-11": 121}

Invoked Function Expression (IIFE)

  • An IIFE is an anonymous function that's invoked as soon as it’s defined
  • IIFE with no parameters
// ex 1
(function() {
  const name = 'Raynald';
  console.log(`Name is ${name}`);
})();

// 'Name is Raynald'

// ex 2
// Same as above but using arrow function
(() => {
  const name = 'Raynald';
  console.log(`Name is ${name}`);
})();

// 'Name is Raynald'

  • IIFE with parameters
// ex 1
let param = 100;
let app = (function(p) {
  function init() {
    console.log('init -> ' + p);
  }

  function F() {
    //
  }
  ...

  return {init : init, F : F };
})(window.param);

app.init(); // 'init -> 100'
app.F();

  • Use IIFE to isolate code blocks
// ex 1
(function() {
  // block A
  const name = 'Block A';
  console.log(`Hello from ${name}`);
}());

// 'Hello from Block A'

(function() {
  // block B
 const name = 'Block B';
 console.log(`Hello from ${name}`);
}());

// 'Hello from Block B'

Feature Detection

  • The following can be used to implement Feature Detection
  • After first chkFeatureX() invocation, when chkFeatureX() is called it will directly execute the appropriate code
  • This is an example of the ability of a function to re-write itself !
function chkFeatureX(){
    if (window.featureX) { 
        chkFeatureX = function(){
          console.log('Feature X is supported');
          // Implement code for featureX
          // ...
          return true;
        }
    } else {
        chkFeatureX = function(){
        console.log('Feature X is not supported');
          // Implement alternate code
          // ...
          return false;
        }
    }
    return chkFeatureX();
}

chkFeatureX();  // 'Feature X is not supported'

Recursive Functions

  • TBD

Object-Oriented Programming

  • The two most important principles in OOP are
    • Encapsulation
    • Inheritance

Encapsulation

  • Reference Different ways to achieve encapsulation in JavaScript(ES6)
  • Encapsulation basically involves enclosing all the functionality of an object within the object itself
    • Direct access to properties / data is dis-allowed
  • Encapsulation can be achieved using
    • Factory Functions with Closures
    • ES6 Classes

Inheritance

  • Inheritance promotes code re-use and can be achieved serveral different ways including
    • Prototypal Inheritance (without using ES6 classes)
    • ES6 Classes using extends - See ES6 Classes below

Prototypal Inheritance

Object __proto__ property

  • Every object has a special property called __proto__
  • Basic inheritance can be accomplished using an object's proto
// ex 1
let user = {
  name: "",
  status: false,
  login() {
    console.log(`login user ${this.name}`);
  }
}

// Define admin object that inherits from user
let admin = {
  isAdmin: true,
  __proto__ : user
}

admin.name = "admin"
admin.login();  // 'login user admin'
  • Object method over-ride is also supported
// ex 1
let admin = {
  isAdmin: true,
  login: function() { console.log(`login ADMIN user ${this.name}`); },
  __proto__ : user
}
admin.login();  // 'login ADMIN user admin'

Constructor Prototype

  • Every Constructor has a prototype property
  • Also recall that every object has a constructor property that points to the constructor (function) that created it
  • Basic inheritance can be accomplished using a constructor's protytype property - F.prototype
// ex 1
let user = {
  name: "",
  status: false,
  login() {
    console.log(`login user ${this.name}`);
  }
}
console.log(user.constructor); //  function Object() {[native code] }
console.log(typeof user.constructor); // "function"
console.log(user.constructor === Object); // true

function adminUser(){ this.isAdmin = true; };

console.log(adminUser.prototype); // "[object Object]" - empty {} 
console.log(typeof adminUser.prototype); // "object"

adminUser.prototype = user;
console.log(adminUser.prototype); // "[object Object]" - user object

let admin = new adminUser();
console.log(admin.constructor);     // function Object() {[native code] }
console.log(typeof admin.constructor);  // "function"
console.log(admin.constructor == Object); // true, because of adminUser.prototype = user

console.log(admin.constructor == adminUser); // false, because of adminUser.prototype = user

admin.name = 'admin';
console.log(admin.isAdmin);
admin.login();

Prototype Chaining

// ex 1
function User(name) {
  this.name = name;
  this.status = false;
  this.login = () => console.log(`login user ${this.name}`);
}
let u1 = new User('Ray');
console.log(u1.constructor === User); // true
u1.login(); // 'login user Ray'

function adminUser(){ this.isAdmin = true; };

// Inherit from User by setting adminUser prototype property
// This is called Prototype Chaining
adminUser.prototype = new User('admin');

let u2 = new adminUser('admin');
console.log(u2.constructor === adminUser); // false
console.log(u2.constructor === User); // true
u2.login(); // 'login user admin'
console.log(u2.isAdmin); // true

let u3 = new u1.constructor('Cree');
u3.login(); // 'login user Cree'

// ex 2
function SuperType(str) {
  this.str = str;
  this.colors = ["red", "blue", "green"];
  this.superPrint = () => console.log(this.str);
}

let sup = new SuperType('I am super');
sup.constructor === SuperType; // true
sup.superPrint();              // 'I am super'

// Properties / methods can be added via prototype object
SuperType.prototype.print = function() { console.log(this.str);};
sup.print();  // 'I am super'

// Create SubType object
function SubType(str) {
  this.str = str;
}

// Inherit from SuperType by setting  Subtype prototype property
// This is called Prototype Chaining
SubType.prototype = new SuperType(); // SubType.prototype = SuperType - won't work
let sub = new SubType('I am sub');
sub.constructor === SubType;  // true

sub.print();  // 'I am sub'

// Add methods for all SubType objects
SubType.prototype.subPrint = function() {
  console.log(this.str);
};

sub.subPrint();               // 'I am sub'
SubType.prototype.print = function () {
  console.log('Subtype: ', this.str);
};

sub.print();  // 'Subtype: I am sub'
sub.print.call(sup);  // 'Subtype: I am super'

Constructor Stealing

  • A drawback with Prototype Chaining is that changes in inherited reference values (array and object) propagates to all instances
// ex 1
sub.colors;     // ["red", "blue", "green"]
sub.colors.push("black");
sub.colors;     // ["red", "blue", "green", "black"]

let sub1 = new SubType();
// Array change propogates to sub1
sub1.colors;    // ["red", "blue", "green", "black"]
  • Solution to above is to use the concept of Constructor Stealing
  • Constructor Stealing ensures that class instances have their own copy of reference (arrays, and objects) variables defined in constructor
  • Inheritance using Constructor Stealing
// ex 1
function SuperType(str) {
  this.str = str;
  this.colors = ["red", "blue", "green"];
  this.print = function() {
    console.log(this.str);
  };
}

// Instead of Prototype Chaining, define constructor for SubType objects
using "constructor stealing"
function SubType(str) {
  // inherit from SuperType
  // this will adds colors and print() to instantiated SubType object
  SuperType.call(this, str); // __this__ is instantiated SubType object
}

let sup = new SuperType('I am super');
sup.print();          // 'I am super'

let sub = new SubType('I am sub');
sub.print();         // 'I am sub'
sub.colors;          // ["red", "blue", "green"]

sub.colors.push("black");
sub.colors;         // ["red", "blue", "green", "black"]

let sub1 = new SubType('I am sub1');
sub1.print();       // 'I am sub1'

// Array change does not propagate to sub1
sub1.colors;        // ["red", "blue", "green"]

Combination Inheritance (version 1)

  • If used without Prototype Chaining, Constructor Stealing leads to duplicate function methods in each instance
  • The solution is to use Combination Inheritance
  • Inheritance using Constructor Stealing and Prototype Chaining
// ex 1
function SuperType(str) {
  this.name = str;
  this.colors = ["red", "blue", "green"];
}

// Add method(s) for all SuperType objects using prototype chaining
// Prevents duplication of methods in objects that inherit from SuperType

SuperType.prototype.print = function() {
  console.log(this.str);
};

// Create constructor for SubType objects
// Use constructor stealing to inherit variables
function SubType(str, pattern) {
  SuperType.call(this, str); // <=== Constructor Stealing
  this.pattern = pattern;
}

// Inherit methods from SuperType object using prototype chaining
SubType.prototype = new SuperType();

SubType.prototype.print_pattern = function() {
  console.log(this.pattern);
};

let sup = new SuperType('I am super');
sup.print();          // 'I am super'

let sub = new SubType('I am sub', 'striped');
sub.print();            // 'I am sub'
sub.print_pattern();    // 'striped'
sub.colors;             // ["red", "blue", "green"]

Combination Inheritance (version 2)

  • Best practice pattern for implementing inheritance w/o ES6 Classes
// ex 1
function object(o) {
  function F(){}
  F.prototype = o;
  return new F();
}

function inheritPrototype(subType, superType) {
  var prototype = object(superType.prototype);
  prototype.constructor = subType;
  subType.prototype = prototype;
}


function SuperType(name) {
  this.name = name;
  this.colors = ["red", "blue", "green" ];
}

SuperType.prototype.sayName = function() {
  alert(this.name);
};

function SubType(name, age) {
  SuperType.call(this, name);
  this.age = age;
}

inheritPrototype(SubType, SuperType);

Object-Based Inheritance

  • Use Object.create() to create objects based on another object
  • This provides an alternative way of implementing Inheritance
const SuperType = {
  str:  "",
  colors: [],
  print() {
    console.log(this.str);
  }
}

SuperType.init = function(str, colors) {
  this.str = str;
  this.colors = colors;
}

let sub = Object.create(SuperType);
sub.init('I am sub', ['red', 'blue', 'yellow']);

sub.colors;    // ['red', 'blue', 'yellow']
sub.print();   // 'I am sub'

SuperType.isPrototypeOf(sub);   // true

ES6 Classes

  • Reference ES6 Classes
  • Before ES6, using constructor functions were the only way of achieving class-like behavior in JavaScript
  • ES6 introduced the new class declaration syntax that does exactly the same thing as a constructor function
  • Using ES6 classes makes JavaScript classes look much the same as other class-based languages
  • In reality a class is under the hood basically a function
// ex 1
class C { }
typeof C;   // 'function'
// ex 2
class Dice {
  constructor(sides=6) {
    this.sides = sides;
  }
  roll() {
    return Math.floor(this.sides * Math.random() + 1);
  }
}
let dice = new Dice();
dice instanceof Dice;   // true
dice.sides;             // 6
dice.roll();            // 4

typeof Dice;      // 'function'
typeof dice;      // 'object'

// Construtor version
function Dice(sides=6) {
  this.sides = sides;
  this.roll = () =>  Math.floor(this.sides * Math.random() + 1);
}

let dice = new Dice();
dice instanceof Dice;   // true
dice.sides;             // 6
dice.roll();            // 4

Static Methods

  • Use static keyword in class declarations to create a static method
class C {
  constructor(x=1) {
    this.x = x;
  }
  method1() { }
  method1() { }
  static description = () => { return 'This is a static method'; }
}

let str = C.description();
str;    // 'This is a static method'

Prototypal Inheritance with Classes

  • Every class has a prototype property that is shared by every instance of the class
  • Any properties or methods of a class’s prototype can be accessed by every object instantiated by that class
  • Properties and methods can be added to a class via the class prototype property
// ex 1
class User {
  active = false;
  constructor(name='', email='') {
    this.name = name;
    this.email = email;
  }
  printName = () => { return `User name is ${this.name}`; }
}

const u1 = new User('test', '[email protected]');
u1.name;    // 'test'
u1.email;   // '[email protected]'
let name = u1.printName();
name;       // 'User name is test'
u1.active;  // false
u1.active = true;
u1.active;  // true

// Prototype property is an object
User.prototype;   // Object

// Add new method via prototype property
User.prototype.printEmail = function() { return `User email is ${this.email}` };
u1.printEmail();   // 'User email is [email protected]'

// Add new property
User.prototype.description = 'User info';

// Using an arrow function here doesn't seem to work
User.prototype.printEmail = () => { return `User email is ${this.email}` };

  • Use object constructor property , Object.getPrototypeOf() or __proto__ to get object prototype
  • This works the same as if User was a Constructor instead of a Class
// ex 1
typeof u1.constructor;               // 'function'
typeof u1.constructor.prototype;     // 'object'
u1.constructor.prototype;            // { description, printEmail }
Object.getPrototypeOf(u1);           // { description, printEmail }
u1.__proto__;                        // { description, printEmail }
  • Use isPrototypeOf() to check class instance
// ex 1
User.prototype.isPrototypeOf(u1);  // true

Public and Private Methods

Encapsulation

  • By default, an object’s methods and properties are public
  • Methods and properties are said to be public because they can be queried directly and changed by assignment
  • Use variable scope to keep properties methods private inside of a class declaration
  • Use let to locally define properties that should be kept private
  • Reference Public and Private Properties and Methods
// ex 1
class User {
  constructor(name='', email='') {
    let _name = name;
    let _email = email;
    this.getName = () => _name;
    this.setName = (name) => { _name = name };
    this.getEmail = () => _email;
    this.setEmail = (email) => { _email = email };
  }
  printName = () => { return `User name is ${_name}`; }
}

let u1 = new User('test', '[email protected]');
u1.name;      // undefined
u1.email;     // undefined

// Can't directly change private property
u1.name = 'tester';
u1.getName();      // "test"

u1.getEmail();     // "[email protected]"
u1.setEmail('[email protected]');
u1.getEmail();     // "[email protected]"

Inheritance Using Classes

  • A class can inherit from another class using the extends keyword in a class declaration
  • Reference Class Inheritance
// ex 1
class AdminUser extends User {
   setPriviledge(priv) {
     this.priv = priv;
   }
   getPriviledge() {
     return this.priv;
   }
}
let a1 = new AdminUser('admin', '[email protected]');
a1.getName();          // "admin"
a1.getEmail();         // "[email protected]"
a1.setPriviledge('ADMIN');
a1.getPriviledge();    // "ADMIN"
  • Extend Array "class"
// Use rest parameters to accept any number of variables
class myArray extends Array {
    constructor(...args){
        super(...args);
    }
    delete(i) {
        return this.splice(i,1);
    }
}
const list = new myArray(1, 2, 3);
list.delete(1);  // [2]
list;            // [1,3]

Object Creation (Part II)

  • There are various ways to create objects
    • One by One
    • Factory Pattern
    • Constructor Pattern - using new keyword
    • Constructor - Prototype Pattern
    • ES6 Classes

One by One

  • Use Object() constructor or Object literal {}
// ex 1
// Use Object constructor
let p1 = new Object();
p1.name = "Ray";
p1.age = 53;
p1.job = "Web Developer";
p1.sayName = function() {
  console.log(this.name);
};
p1.sayName();   // 'Ray'

// Use Object Literal 
let p2 = {
  name: "William",
  age: 53,
  job: "Consultant",
  sayName : function() {
    alert(this.name);
  }
};
  • Drawbacks
    • Tedious to write and lots of code duplication
    • Provides no encapsulation - object properties are directly accessible

Factory Pattern

// ex 1
function createPerson(name, age, job) {
  let o = new Object();
  o.name = name;
  o.age = age;
  o.job = job;
  o.sayName = () => console.log (this.name);
  return o;
}

let p1 = createPerson("Ray", 53, "Web Developer");
p1.sayName();   // 'Ray'
typeof p1;      // "object"
  • Drawbacks
    • Provides no encapsulation
    • Doesn't support object identification , all objects are Object type

Constructor Pattern

// ex 1
function Person(name, age, job) {
  // this = {} (implicitly)
  this.name = name;
  this.age = age;
  this.job = job;

  // drawback to this pattern is that each instance has
  // its own separate copy of methods (functions)
  // all instances will have a separate copy of method
  // sayName()
  // this is wasteful code duplication

  this.sayName = () => console.log(this.name);
  // return this; (implicitly)
}

let p1 = new Person("Ray", 53, "Web Developer");
let p2 = new Person("Cassie", 36, "Project Manager");

p1.constructor == Person  // true
p1 instanceof Person      // true

p2.constructor == Person  // true
p2 instanceof Person      // true
  • Supports object identification using instanceof operator but doesn't provide encapsulation

Constructor - Prototype Pattern

  • Use Object prototype to add properties and methods
// ex 1
// create constructor
function Person(name, age, job) {
  this.name = name;
  this.age = age;
  this.job = job;
  this.friends = ["Mike", "Juke"];
}

// add method that all instances of Person() will inherit
Person.prototype.sayName = () => console.log(this.name);

// create instances
let p1 = new Person("Ray", 53, "Web Developer");
let p2 = new Person("Cassie", 36, "Project Manager");

// All instance reference properties (e.g. friends) are separate

p1.friends.push("Smokey");
p1.friends // "Mike, Juke, Smokey"
p2.friends // "Mike, Juke"
  • Similar to Constructor Pattern in that it supports object identification using but still doesn't provide encapsulation

Polymorphism

  • Polymorphism means that different objects can have the same method, but implement it in different ways

Event-driven Asynchronous Programming

  • Video | JavaScript Event Loop
  • Callbacks
  • JavaScript runs in a single threaded environment
  • This means that blocking calls - operations that must wait for data or results - completely stop JavaScript program execution
  • Think of an API call that must wait to get data from a server
  • window.alert() is good example of a blocking call
    • Until a user clicks "OK", JavaScript program execution completely stops
  • To mitigate all this, Event-driven Asynchronous Programming techniques are used
  • This involves using Callback functions
  • Callbacks execute asynchronously to program flow and get called when events occur

Callbacks (Part 2)

  • A callback is a function passed to another function as and argument
  • The callback can then be invoked inside the function it was passed into
  • The use of callbacks facilitate JavaScript Event-driven Asynchronous Programming
  • Async programming example
// ex 1
function wait(message, cb, seconds) {
  setTimeout(cb, seconds * 1000);  // timeout event will fire after x seconds
  console.log(message);
}

function Cb() { console.log('Callback called.'); }

wait('This is a callback example...', Cb, 0);
console.log('1/ program executing...');
console.log('2/ program executing...');
console.log('3/ program executing...');

// Results
// Even with a timeout of 0, timeout event only fires after all
// synchronous calls are completed
This is a callback example...
1/ program executing...
2/ program executing...
3/ program executing...
Callback called.
  • Heavy use of callbacks however can create what's called Callback Hell
  • Note that we use what's referred as _Error-first _callback style
    • First argument is an error object
    • Second argument is any data to be used by the callback
// ex 1
// Callback Hell example
function login(user, callback) {
  // ...
  // When some event fires (i.e. database request done), invoke callback
  // error is returned by login code
  callback(error, user);
}

function getPlayerInfo(id, callback) {
  // ...
  // When some event fires, invoke callback
  callback(error, id);
}

function loadGame(info, callback) {
  // ...
  // When some event fires, invoke callback
  callback(error, info);
}

login(userName, function(error,user) {
    if(error){
      throw error;
    } else {  
        getPlayerInfo(user.id, function(error,info){
        if(error){
          throw error;
        } else {
            loadGame(info, function(error,game) {
              if(error){
                throw error;
              } else {
                // code to run game
              }
            });
        }
        });
    }
});

Promises

// ex 1
// Above callback example using Promises
login(userName)
.then(user => getPlayerInfo(user.id))
.then(info => loadGame(info))
.catch( throw error);

Async Functions

Generators

  • Generators
  • Generators are special functions used to produce iterators that maintain the state of a value

Functional Programming

TBD

DOM Scripting

  • Live DOM Viewer
  • The Document Object Model (DOM) is an API for representing and manipulating HTML document content
    • It allows access to elements (HTML tags, content, attributes) of a web page
    • Enables interaction with the page by
      • Adding and removing elements
      • Changing the order of elements
      • Changing element attributes and styles
  • HTML elements / tags are represented as a tree of Node objects
  • There are in total 12 node types - in practive we only work with four of thme
    • document - the “entry point” into DOM
    • element nodes - HTML-tags, the tree building blocks
    • text nodes - contain text
    • comments - won’t be shown, but JS can read it from the DOM
  • Example web page (HTML document)
// ex 1
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Sample Document</title>
  </head>
  <body>
    <h1>HTML Document</h1>
    <p>This is a <em>simple</em>document</p>
  </body>
</html>
  • The Node tree for above web page is below
             document   // Node object (represents entire document)
                |
               html     // Node object
   _____________|__________________
  |                                |
 head                             body   // Node object
   |                    ___________|____________
 title                 |                        |
   |                   h1                       p
"Sample Document"      |                        |
                  "HTML Document"               em
                                  ______________|_________
                                 |              |         |

                             "This is a"     "simple"  "document"
  • The topmost tree nodes are available directly as document properties
  • <html> = document.documentElement
  • The topmost document node is document.documentElement - DOM node of the <html> tag
  • <body> = document.body - another widely used DOM node is the <body> element – document.body
  • <head> = document.head - the <head> tag is available as document.head

Traversing Elements

  • Node objects (document, element, text) have a number of properties and methods for navigating around the document tree
  • Once you have a reference to an element, you can walk along the document tree to find other nodes

Node Object Properties

  • parentNode
  • childNodes[] // array-like , includes elements, text and comments
  • firstChild, lastChild
  • nextSibling, previousSibling
  • nodeType
  • nodeValue
  • nodeName

DOM Collections

  • childNodes looks like an array, but rather a collection – a special array-like iterable object
  • Use for..of to iterate over it and it has a length property
  • Use Array.from() or spread operator to create a “real” array from the collection
// ex 1
<html>
  <head>
    <meta charset="utf-8">
    <title>Sample Document</title>
  </head>
  <body>
    <h1>HTML Document</h1>
    <p>This is a <em>simple</em>document</p>
  </body>
</html>

document.childNodes[0];                           // <html>
const elem = document.childNodes[0].childNodes[1];  // <body>
elem.nodeType;     // 1
elem.nodeName;     // "BODY"

// ex 2 - Iterate through nodes four different ways
for (let i = 0; i < document.body.childNodes.length; i++) {
   console.log(document.body.childNodes[i].nodeName); 
}

for (let node of document.body.childNodes) {
  console.log(node.nodeName);
}

let nodes = Array.from(document.body.childNodes);
nodes.forEach((el) => console.log(el.nodeName));  

let nodes1 = [...document.body.childNodes]; // Use spread operator
nodes1.forEach((el) => console.log(el.nodeName))

Node Type / Node Name / Tag Name

  • nodeType property gives the “type” of a DOM node
  • nodeName returns the node name
  • tagName returns the tag name
// ex 1
elem.nodeType == 1 for element nodes,
elem.nodeType == 3 for text nodes,
elem.nodeType == 9 for the document object

// ex 2 - can also use instanceof or constructor to get node tye
...
<input type="text" name="test">
...
let input = document.querySelector('input');
input instanceof HTMLInputElement; // true
input.constructor.name; // "HTMLInputElement"
input.nodeType; // 1
input.nodeName; // "INPUT"
input.tagName; // "INPUT"

Node Object Properties (for Elements only)

  • When interested in element nodes only (ignore text and comment nodes), use the following properties
    • parentElement
    • children[] // array-like
    • firstElementChild
    • lastElementChild
    • nextElementSibling
    • previousElementSibling
    • childElementCount
// ex 1
<html>
<body>
  <div>Begin</div>
  <ul>
    <li>Information</li>
  </ul>
  <div>End</div>

  <script>
    for (let elem of document.body.children) {
      console.log(elem); // DIV, UL, DIV, SCRIPT
    }
  </script>
  ...
</body>
</html>

// ex 2
<ul>
  <li>Item 1</li>
  <li>Item 2</li>
  <li>Item 3</li>
</ul>

var ul = document.getElementsByTagName("ul");
ul.children[0]);      // <li>Item 1</li>

Tables

  • The <table> element supports additional (useful) properties
  • See Table Navigation
// ex 1 - Sort table by  "name" column
// HTML
<table>
<thead>
  <tr>
    <th>Name</th><th>Surname</th><th>Age</th>
  </tr>
</thead>
<tbody>
  <tr>
    <td>John</td><td>Smith</td><td>10</td>
  </tr>
  <tr>
    <td>Pete</td><td>Brown</td><td>15</td>
  </tr>
  <tr>
    <td>Ann</td><td>Lee</td><td>5</td>
  </tr>
</tbody>
</table>

// Javascript
let table = document.getElementsByTagName('table')[0];
let sortedRows = Array.from(table.tBodies[0].rows);
  .sort((rowA, rowB) => rowA.cells[0].innerHTML.localeCompare(rowB.cells[0].innerHTML));

table.tBodies[0].append(...sortedRows); 

Selecting Elements

  • Use global document object to refer to Document object
  • Document object element select methods return a Node or NodeList object
  • NodeList objects are "array like"
  • Use Array.from() or the spread syntax ... to convert a NodeList to a real array
  • <head> and <body> elements
// ex 1
const head = document.head;
const body = document.body;
  • Select elements by CSS selector - most widely used to select elements
  • Use querySelector() or querySelectorAll() methods
// Return first element with class "myclass"
const el = document.querySelector(".myclass");

// Return list of divs with class "note" or class "alert"
const div = document.querySelectorAll("div.note, div.alert");
  • Match element by CSS selector
<a href="http://example.com/file.zip">...</a>
<a href="http://test.com">...</a>
  
// can be any collection instead of document.body.children
for (let elem of document.body.children) {
  if (elem.matches('a[href$="zip"]')) {
    console.log(elem.href); //"http://example.com/file.zip"
  }
}
  • Select by id
<div id="sect1">
const sect1 = document.getElementById("sect1"); // sect1 is a Node object
sect1.id; "sect1"

// can also reference element by its id
<div id="sect2">
sect2.id; // "sect2"

  • Select by tag name
// ex 1
const para = document.getElementsByTagName("p");  // para is a NodeList object
const firstPara = para[0];  // NodeList objects are array like

// ex 2
<p>Line 1<span>some text</span></p>
const firstP = document.getElementsByTagName("p")[0];
const firstSpan = firstP.getElementsByTagName('span')[0]

// ex 3
<img src="picture.jpg" width="100" height="100" alt="A Picture">
const images = document.getElementsByTagName("img");
for (let i = 0; i < images.length; i++)
  images[i].style.display = "none"; // hide image
}
  • Select element by name
<input type="text" id="name1" name="name1">
var names = document.getElementsByName("name1");  // names is a NodeList object
  • Select elements by class
  • getElementsByClassName() method was added by HTML5 spec and is available as a method on document or HTML element objects
<div id="log">
  <p class="fatal error">Fatal error</p>
  // more content
</div>

// Find descendants of element with id = "log" that have classes "error" and
"fatal"
const log = document.getElementById("log");
const fatalErr = log.getElementsByClassName("fatal error");
  • Select elements in markup (Example)
<--! markup -->
<form name="search">
  <label>Search the site:
    <input type="text" name="search">
  </label>
  <input type="submit" value="Search!">
</form>
<hr>
<form name="search-person">
  Search the visitors:
  <table id="age-table">
    <tbody>
      <tr>
        <td>Age:</td>
        <td id="age-list">
          <label>
            <input type="radio" name="age" value="young">less than 18</label>
          <label>
            <input type="radio" name="age" value="mature">18-50</label>
          <label>
            <input type="radio" name="age" value="senior">more than 50</label>
        </td>
      </tr>
      <tr>
        <td>Additionally:</td>
        <td>
          <input type="text" name="info[0]">
          <input type="text" name="info[1]">
          <input type="text" name="info[2]">
        </td>
      </tr>
    </tbody>
  </table>
  <input type="submit" value="Search!">
</form>

// JavaScript
// Find the following elements
// 1. The table with id="age-table".
// 2.  All label elements inside that table (there should be 3 of them).
// 3  The first td in that table (with the word “Age”).
// 4. The form with name="search".
// 5. The first input in that form.
// 6 The last input in that form.

"use strict";
// 1
let table = document.querySelector("table#age-table");
console.log(table);

// 2
let labels = table.getElementsByTagName("label");
for (const label of labels) {
  console.log(label.innerHTML.trim());
}

// 3.
let tds = table.getElementsByTagName("td");
for (const td of tds) {
  if (td.innerText.includes("Age")) 
    console.log(td);
}

// 4.
let form = document.querySelector('form[name="search"]');
console.log(form);

// 5. 6.
let inputs = form.querySelectorAll('input');
console.log(inputs[0], inputs[inputs.length-1]);

Element Content

// ex 1
let inHTML = element.innerHTML;   // get content as HTML markup (string)
let outHTML = element.outerHTML;   // get content HTML including element itself (string)
let text = element.innerText;   // get content as text (string)

// ex 2
<body>
  <div>Users:</div>
  <ul>
    <li>Ray</li>
    <li>Darla</li>
  </ul>
  <input type="text" name="name">
  <div id="elem">I love <b>apples</b></div>
</body>
...
let div = document.querySelector('#elem');
div.innerHTML; // "I love <b>apples</b>"
div.outerHTML; // '<div id="elem">I love <b>apples</b></div>'
div.innerText; // "I love apples"
document.body.textContent; //
"   Users:
  
    Ray
    Darla
  
  
  I love oranges

Write Content

// ex 1
<p>This is a <em>paragraph</em></p>

let para = document.getElementsByTagName("p")[0];
para.innerHTML;    // "This is a <em>paragraph</em>"
para.innerText;    // "This is a paragraph"
para.innerHTML = "This is a <strong>paragraph</strong>"

// ex 2
<ul id="list">
  <li>Item 1</li>
  <li>Item 2</li>
</ul>

const ul = document.getElementById("list");
ul.innerHTML    // "<li>Item 1</li><li>Item 2</li>"
ul.innerText    // "Item1 Item 2"

Element hidden property

  • The hidden attribute and the DOM property specifies the visibility of an element
// ex 1 - Both divs below are hidden</div>
<div hidden>With the attribute "hidden"</div>
<div id="elem">JavaScript assigned the property "hidden"</div>
...
elem.hidden = true;  // hide second div

// ex 2 - Blink an element
<div id="elem">A blinking element</div>
...
setInterval(() => elem.hidden = !elem.hidden, 1000);

Create and Insert Element

  • See Modifying the Document for newer methods for inserting elements and text
  • Use Element methods
    • createElement()
    • createTextNode()
    • appendChild()
    • insertBefore()
// ex 1
const elem = document.createElement("tagName");
const text = document.createTextNode("string");

// ex 2
const p = document.createElement("p");
typeof p;       // 'object'
p;              // '<p></p>'

const content = document.createTextNode("Hello there");
typeof content; // 'object'
content;        // 'Hello there'
  
// Add text to paragraph
p.appendChild(content);

// this also works using textContent property
p.textContent = 'Hello there';

// Add to document tree
document.body.appendChild(p);   // <p>Hello there</p>
document.body;                  // <body>​<p>​Hello there​</p>​</body>​

// ex 3
// Asynchronously load and execute a script
function loadAsync(url) {
  // create <script> tag
  let s = document.createElement("script");
  s.src = url;
  // Insert script into <head>
  document.head.appendChild(s);
}

// ex 4
// Function to create elements
function createElement (tag,text="") {
    const el = document.createElement(tag);
    if (typeof text === 'string' && text) {
       el.textContent = text;
    }
    return el;
}

elem.body.insertAdjacentHTML

  • Using elem.body.insertAdjacentHTML makes it easy to insert markup
// ex 1 - add a status message in status area
<style>
.alert {
  padding: 15px;
  border: 1px solid #d6e9c6;
  border-radius: 4px;
  color: #3c763d;
  background-color: #dff0d8;
}
</style>

<div class="status"></div>
....
<script>
  let statusDiv= document.querySelector('.status');
  statusDiv.insertAdjacentHTML("afterbegin", `<div class="alert">
    <strong>Hi there!</strong> You've read an important message.
  </div>`);
</script>

Delete Element

// ex 1
let oldChild = parentNode.removeChild(child);

// ex 2
// create list
const ul = createElement('ul');
let li1 = ul.appendChild(createElement('li', 'One'));
let li2 = ul.appendChild(createElement('li', 'Two'));
ul;    // <ul><li>One</li><li>Two</li></ul>

// delete first list item
let removed = ul.removeChild(li1);
removed;   // <li>One</li>
ul;        // <ul><li>Two</li></ul>

Replace Element

  • Use replaceChild() method
// ex 1
var replaceNode = parentNode.replaceChild(new, old);

Clone Element

  • Use cloneNode method
// ex 1
<div class="alert">This is an alert!</div>

div = document.querySelector('div');
div2 = div.cloneNode(true);  // "deep" clone of div
div.after(div2); // add div2 element after div

Element Attributes

  • HTML elements consists of a tag name and attributes (set of name/value pairs)
  • Some global attributes are : class, id, title, style, data-*
  • Attributes of an HTML elements become properties of the HTML object
// ex 1
<img src="http://xxx.png" id="my-image" class="oval" alt="This is my image">

const img = document.getElementById("my-image");
img.src;              // "http://xxx.png"
img.className;        // "oval"
img.id;               // "my-image"
img.id = "new-id";
img.id;               // "new-id"

  • Can also use element methods
    • getAttribute()
    • setAttribute(),
    • hasAttribute()
    • removeAttribute() to manipulate element attributes
// ex 1
let url = img.getAttribute("src");
img.setAttribute("id", "new-id");

// ex 2
let div1 = document.getElementById("div1");
let align = div1.getAttribute("align");
div1.setAttribute("align", "center");

Custom Attributes

  • Non-standard attributes, dataset
  • All attributes starting with data- are reserved for use in HTML elements
  • They are available in the dataset property
  • For example, if a HTML element has an attribute named data-about, it’s available as elem.dataset.about
// ex 1
<div data-about="contact">
let contactDiv = document.querySelector('[data-about]'); // using attribute selector
contactDiv.dataset.about; // 'contact'
contactDiv.getAttribute('[data-about]'); // 'contact'

Style Attribute / Property

// Basic CSS
// p is selector, color and font-size are properties, red and 1em
// are values.
// Entire statement is called a "rule"
p { color : red; font-size : 1em }
  • Use element style property to manipulate CSS properties and values
  • style attribute/property represents in-line style not style sheet
// ex 1
const el = document.querySelector('.myclass');

// All values must be specified as a string
el.style.color = "blue";
el.style.width = "300px";
el.style.border = "red 1px solid";      

// Use Camel Case property name for property name with dashes
el.style.fontWeight = "bold";    // font-weight

// Can also use bracket notation for property name
el.style['font weight'] = 'normal';
  • Can also use element methods to manipulate styles
    • getAttribute()
    • setAttribute()
// ex 1
// Set element style attribute to string s
el.setAttribute("style", s);

// Above is equivalent to
el.style.cssText = s;

// ex 2
// Query the inline style of an element
let s = el.getAttribute("style");

// Above is equivalent to
let s = el.style.cssText

Check Style Properties

  • Reading the element style property only shows inline styles
  • To see all styles applied to an element use
    • getComputedStyle()
    • getPropertyValue()
const el = document.querySelector('.my-class);
const styles = getComputedStyle(el);
typeof styles;    // 'object'

let color = styles.getPropertyValue('color');

classList property

  • Instead of manipulating individual CSS styles one can use classList property and methods

    • classList.add()
    • classList.remove()
    • classList.toggle()
  • classList was added in HTML5

// ex 1
// file style.css
....
.my-class {
  background-color: yellow;
  border: 1px solid black
}
.....

file index.html:
...
<div id="my-id">
  <p>Line 1</>
  <p>Line 2</>
  <p>Line 2</>
</div>

// file app.js
.....
const div = document.querySelector("my-id");
div.className = "my-class";

el.classList.remove("my-class");
el.classList.add("my-class");
el.classList.toggle("my-class");

Events

  • JavaScript interactivity with HTML is handled mainly through Events
  • Events are subscribed to using listeners (or handlers) that execute when event occurs

Event Handlers

  • Events may be user initiated or may be triggered by the browser itself
  • Event handlers may be configured three different ways
    • Inline Event Handler
      • Via HTML element attribute e.g. onclick
    • DOM Level 0 Event Handler
    • DOM Level 2 Event Handler
  • Note: The click event occurs when a user:
    • Clicks with the mouse
    • Presses the Enter key
    • Taps the screen
  • This makes the click event a very useful all-round event covering many types of interaction

Inline Event Handler

  • The original way of dealing with events in the browser was to use inline attributes that were added directly into HTML
// ex 1
<input type="button" onclick="alert('clicked the button')" value="Click Me">

// ex 2
// When <button> is clicked, this == HTMLButtonElement
<button onclick="console.log('Clicked ' + this)">
  Click Me
</button>

DOM Level 0 Event Handler

This method uses Node object event handler properties to attach the handler

// ex 1
<button id="my-btn">Send</button>

const btn = document.getElementById("my-btn");
btn.onclick = function(evt) {
  // this points to element event occurred on
  console.log(this.id);   // 'my-btn'
}

btn.onclick = null;   // remove handler

DOM Level 2 Event Handler

  • The recommended way of dealing with events is to use element event listener methods
    • addEventListener()
    • removeEventListener()
// ex 1
elem.addEventListener(event, callback, capturePhase);
elem.removeEventListener(event, callback);  // callback must be named function

// ex 2
<button id="my-btn">Send</button>

const btn = document.getElementById("my-btn");
btn.addEventListener("click", function() {
      console.log(this.id);
    }, false  // fire event during bubbling phase
);

// ex 3
// Add listener to entire page
addEventListener('click', () => alert('You clicked on the page'));
  • addEventListener() allows multiple events to be added to element
  • When addEventListener() is used, events can only be removed with removeEventListener()
  • IE8 and below uses attachEvent() and detachEvent()

Object handlers - handleEvent()

  • An object with a handleEvent() method can be assgined as an event handler using addEventListener()
// ex 1
<button id="elem">Click me</button>

<script>
  let obj = {
    handleEvent(event) {
      console.log(event.type + " at " + event.currentTarget);
    }
  };

  elem.addEventListener('click', obj);
</script>

Event Object

  • Whenever an Event Handler is triggered by an event, the event callback function is passed an Event Object
  • This Event Object contains information (via properties) about the event

Event Properties

  • target
    • element event is actually fired on
  • currentTarget
    • element event handler is attached to
  • screenX, screenY
    • show the number of pixels from the left and top of the screen where the event took place
  • clientX, clientY
    • show the number of pixels from the left and top of the browser window where the event took place
  • pageX, pageY
    • show the number of pixels from the left and top of the document where the event took place (takes page scrolling into account)
// ex 1
var btn = document.getElementById("btn");
btn.onclick = function(evt) {
  var type = evt.type;
  var target = evt.target;            // target == this
  var curr_target = evt.currentTarget;
};

Event Types

  • Several types of events exist
  • See full list here

Mouse Events

  • mousedown, mouseup events
  • In the below example mousedown, mouseup and click events should all fire in that order
// ex1
<p id='click'>Click me</p>

const p = document.getElementById('click');
p.addEventListener('click',() => console.log('click') );
p.addEventListener('mousedown',() => console.log('down') );
p.addEventListener('mouseup',() => console.log('up') );

  • dblclick event
  • Occurs when the user double-clicks on the element attach to listener
// ex 1
<p id='dbclick'>Double Click</p>

.highlight {
  background-color: yellow;
}

const p = document.getElementById('dbclick');
p.addEventListener('dblclick', highlight);

function highlight(event){
    event.target.classList.toggle('highlight');
}
  • mousedown, mouseup, mousemove events
    • mouseover event occurs when the mouse pointer is placed over the element
    • mouseout event occurs when the mouse pointer moves away from an element
    • mousemove event whenever the mouse moves It will only occur while the cursor is over the element to which it’s applied

Keyboard Events

  • Three events that occur when a user presses a key are -
  • keydown , keypress and keyup

Touch Events

  • touchstart, touchend, touchmove, touchcenter, touchleave
  • Use HammerJS or zingtouch libraries to handle swiping and gestures

Form Events

  • focus, blur, change, submit

Stopping Event Default Behavior

  • Some elements have default behavior associated with certain events
    • When a user clicks on a link, the browser redirects to the address in the href attribute
    • A form is submitted when the user clicks on the Submit button
  • Use preventDefault() event object method to prevent default behavior
  • Prevent redirection when a link is clicked on
// ex 1 
<p>
  <a id='broken' href='https://example.com'>Broken Link</a>
</p>
...
const link = document.getElementById("link");
link.onclick = (evt) => {
  evt.preventDefault();  <=== Prevent redirection
  console.log('Target:', evt.target)
};
  • Prevent a form from being submitted to server
// ex 2
// Basic search form
<form name='search' action='./search.php'>
  <input type='search' name='search'>
  <button type='submit'>Go</button>
</form>
...

// Form submit event handler
form = document.forms.search;
form.addEventListener('submit', (evt) => { 
  evt.preventDefault();   <=== prevent form from being submitted to server
  console.log('Form event: ', evt);
});

Event Propagation

  • When you click on an element, you are actually clicking on all the elements it’s nested inside of
// ex 1
<html>
  <head>
    <meta charset="utf-8">
    <title>Event Propagation</title>
  </head>
  <body>
    <button>Click Me!</button>
  </body>
</html>
  • In the above markup, when <button> is clicked, Event Propagation occurs in one of two ways or models -
    • Bubbling
    • Capturing
  • Bubbling is the default behavior

Event Bubbling

  • Originally implemented by Microsoft IE

  • In this model the event fires on the element clicked on first, then bubbles up the document tree, firing an event on each parent element until it reaches the root node

  • In the above markup, the click event fires on the <button> element

    • then on the <body> element
    • then on the <html> element
    • then on the document element
  • Below is a diagram of the event fire sequence

1) document 7)
    ^
    |
    |____ 2) html 6)
              ^
              |
              |___ 3) body 5)
                       ^
                       |
                       |___ 4) button

  • The event sequence is 4) -> 5) -> 6) -> 7)

Event Capturing

  • Originally implemented by Netscape
  • In this model the event first fires on the root element, then propagates downwards, firing an event on each child element until it reaches the target element that was clicked on
  • The event sequence is 1) -> 2) -> 3) -> 4)

Default Behavior

  • Default behavior is Bubbling
  • To change to Capturing add set capture flag to true in addEventListener()
// ex1
element.addEventListener('click', (event) =>
console.log('Clicked on element'),true);

Stopping Event Bubbling

  • Event Bubbling can be stopped from occurring by invoking event object stopPropagation() method in the event callback function
// ex 1
element.addEventListener(
  'click', (event) => {
   console.log('clicked on element');
   event.stopPropagation(); 
 }, false
);

Event Delegation

  • Event delegation can be used to attach an event listener to a parent element in order to capture events that are triggered by its child elements
  • Event bubbles up until it reaches parent element with an attached event handler, at which point the handler will fire
// ex 1
// Additional <li> can be added dynamically and <ul> event
// handler can handle it
<ul id="list">  // click here, target == currentTarget == <ul>
  <li><a href="...">Link 1</a></li>
  <li><a href="...">Link 2</a></li>
  <li><a href="...">Link 3</a></li>
  ...
</ul>

// Attach event handler to <ul> element
// When any <li> is clicked event will bubble up until it reaches
// <ul> handler
const ul = document.getElementById("list");
ul.onclick = function(evt) {
  // click on <ul>, target == currentTarget == <ul>
  // click on <li>, target == <li>, currentTarget == <ul>
  console.log(`currentTarget:`, evt.currentTarget);
  console.log(`target:`, evt.target);
};

Custom Events

  • Custom events can be created using Event() constructor
// ex 1
const evnt = new Event("build");

elem.addEventListener('build', function(evt) {
  // handle 'build' event
}, false);

elem.dispatchEvent(evnt); // Dispatch/trigger event

Forms

BOM Scripting

  • The Browser Object Model (BOM) is a collection of properties and methods that contain information about the browser and computer screen
  • The BOM implements the window object and only applies in a browser environment

Browser Information

  • Use window.navigator object to get information about the browser being used

URL Information

  • Use window.location object for information about the URL of the current page
  • Re-direct current page to example.com site
// ex 1
location.href = 'http://example.com';
  • Reload current page
// ex 1
location.reload();

Browser History

  • Use window.history object to access previously visited pages
window.history.go(1); // goes forward one page
window.history.go(0); // reloads the current page
window.history.go(-1); // goes back one page

window.history.forward() // go forward one page
window.history.back()    // go back one page 

Controlling Windows

  • Use window.open(), window.close(), window.moveTo(), window.resizeTo()

Screen Information

  • Use window.screen object for information about the screen the browser is displayed on

    • window.screen.height
    • window.screen.width
    • window.screen.availwidth
    • window.screen.availdHeight
  • Viewport size

    • window.innerHeight
    • window.innerWidth

Window Scrolling

  • Use window.pageXOffset and window.pageYOffset to get scroll bar position

  • Set Scroll Bar Position

    • window.scrollBy(x,y)
    • window.scrollTo(x,y)
  • Scroll an element into view

  • Use elem.scrollIntoView(alignWithTop)

Cookies

  • Use document.cookie or use library like Cookies.js

Error Handling

AJAX

  • AJAX is an acronym for Asynchronous JavaScript and XML
  • Traditionally web page updates require the server to reconstruct the entire page (HTML,CSS and JavaScript) and send to client (browser)
  • With AJAX, data is passed between client/browser and server via the XMLHttpRequest (XHR) object
  • Full page reload is unnecessary to update page contents
  • AJAX uses CORS in order to work across different domains
  • Note that scripts can be loaded from any domain using <script> tag idependent of CORS
// ex 1
...
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
....

GET and POST methods

  • Use GET method for "non-destructive" operations - read data
  • GET requests send all data in URL query string
// ex1
http://example.com/search.php?firstname=LeBron&lastname=James
  • Use POST method to store data on server (in database)
  • Data is sent separately in body of HTTP request as POST data

Fetch API

// ex 1
fetch('https://example.com/data')
.then( // code that handles the response )
.catch( // code that runs if the server returns an error )
  • Skeleton example showing how to process results
// ex 2
const url = 'https:example.com/data';
const headers = new Headers({ 
     'Content-Type': 'text/plain', 'Accept-Charset' : 'utf-8', 
     'Accept-Encoding':'gzip,deflate' 
});
const request = (url,{
    headers: headers
});

// Make request
fetch(request)
.then( function(response) {
    if(response.ok) {
        return response;
    }
    throw Error(response.statusText);
})
.then( response => // do something with response )
.catch( error => console.log('There was an error!') )

  • Full example - process result and insert into page
// ex 3
<div id="result"><!-- Result gets inserted here --></div>
...
<button id='go'>Go</button>
...

const result = document.querySelector("#result");
const go = document.querySelector("#go");
url = 'http://example.com/data'

// text response
go.addEventListener('click', () => {
    fetch(url)
    .then( response => {
        result.innerText = 'Waiting for response...';
    if(response.ok) {
        return response;
    }
        throw Error(response.statusText);
    })
    .then( response => response.text() )
    .then( text => result.innerText = text )
    .catch( error => console.log('There was an error:', error))
},false);

// JSON response
go.addEventListener('click', () => {
    fetch(url)
    .then( response => {
        result.innerText = 'Waiting for response...';
    if(response.ok) {
        return response;
    }
    throw Error(response.statusText);
    })
    .then( response => response.json() )
    .then( data => result.innerText = data.value )
    .catch( error => console.log('There was an error:', error))
},false);

FormData Interface

HTML 5 APIs

data-* Attribute

  • Data attributes provide a convenient way of adding data directly into the HTML markup

Web Storage API

  • References
  • Web Storage API provides a key-value store on the client’s computer
  • Similar to using cookies but
    • has fewer restrictions
    • has more storage capacity,
    • and is generally easier to use
  • Suitable for storing user and / or application-specific information that can then be used during future sessions

Geolocation API

  • References
  • The Geolocation API is used to obtain the geographical position of a device

Web Workers

  • JavaScript is a single-threaded language - this that only one process can run at one time
  • Web Workers allow processes to be run in the background
  • This adds support for concurrency in JavaScript
  • Web Workers are useful when time consuming operations need to be performed - these operations can be done in a separate thread

Web Sockets

  • References
  • WebSockets provide a persistent connection between a client and server
  • Both client and server can start sending data at any time

Notification API

Shims and Polyfills

⚠️ **GitHub.com Fallback** ⚠️