HOWTO.javascript - raynaldmo/HOWTO GitHub Wiki
- JavaScript Overview
- References
- Variables
- Data Types (Part 1)
- Falsy Values
- Objects (Part 1)
- Symbols
-
Operators
- Comparison Operators
==, === - Soft Equality
== - Hard / Strict Equality
=== - Inequality
!=, !== - Relational
<, >, <=, >=Operators - Increment / Decrement Operators
++, -- - Unary Operator
+, - - Logical Operators
!, &&, || - Logical NOT (!)
- Logical AND (&&)
- Logical OR (||)
- Null Coelescing Operator
?? - Bitwise Operators
~, &, | ^ - Type Conversion / Coercion
-
Comparison Operator
==
- Comparison Operators
- Functions (Part 1)
- Rest Parameters and Spread Syntax
- Strict Mode
- Variable Scope (Part 2)
- Data Types (Part 2)
- Destructuring Assignment
- Objects (Part 2)
- Global Object
- Functions (Part 2)
- Object-Oriented Programming
- Event-driven Asynchronous Programming
- Functional Programming
- DOM Scripting
- Events
- Forms
- BOM Scripting
- Cookies
- Error Handling
- AJAX
- Fetch API
- FormData Interface
- HTML 5 APIs
- Web Storage API
- Geolocation API
- Web Workers
- Web Sockets
- Notification API
- Shims and Polyfills
Table of contents generated with markdown-toc
A complete JavaScript implementation is made up of
- The core (ECMAScript)
- Document Object Model (DOM)
- Browser Object Model (BOM)
- ECMAScript/ECM-262 describes the JavaScript language
- syntax
- data types
- statements
- keywords
- reserved words
- operators
- objects
- 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
- BOM
- BOM is an interface for manipulating browser components (windows, frames etc.)
- Pop-up new browser windows
- Move/resize/close windows
- Implements:
-
navigatorobject (provides detailed info about browser) -
locationobject (provides detailed info about page loaded in browser) -
screenobject (provides info about screen resolution) - custom objects such as XMLHttpRequest object (used for AJAX)
-
- Tools for Web Developers
- The Modern JavaScript Tutorial
- JavaScript Refresher for React Beginners – Key JS Concepts to Know
- Can I use
- You might not need jQuery
- Variables are used to store values and must be declared before they can be used
- Variables are declared with
let(or oldervar) orconstkeyword - Variables may contain two different types of data
- Primitive value
(number, string, boolean, symbol, undefined, null) - Reference value
(object)
- Primitive value
- Primitive values are simple atomic pieces of data, while reference values are objects that may be made up of multiple values
- 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
- Use
constkeyword to declare a variable that won't be reassigned to another value
const name = 'Ray';
name = 'Roy; // Uncaught TypeError: Assignment to constant variable
- Use
letkeyword 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'
- Scope refers to where a variable is accessible by a program
- There are basically two variable scopes
- Global scope
- Local 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
- A new feature added by ES6 is that blocks can be used to create local scope for a variable
- Variable must be declared using
letorconst
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
letorconstisn'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
- Previous to JavaScript ES6, variables were declared using the keyword
var - Once a variable is declared using
letit can't be re-declared usingletorvar - Once a variable is declared using
varit can be re-declared usingvarbut not usinglet - With respect to the above
constbehaves that same aslet
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(andconst) 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
varbecomes 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
- 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() {}
- Array
- Because of loose typing, there needs to be a way to determine data type
- Use
typeofoperator 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'
- 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
\\
- Single quote marks
// 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()ortoString()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'
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"
- 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()andstr.endsWith()
"Widget".startsWith("Wid"); // true, "Widget" starts with "Wid"
"Widget".endsWith("get"); // true, "Widget" ends with "get"
- Use
str.slice(start [, end]) - (Note:
str.substring()andstr.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"
- String characters are compared by their numeric code
-
str.codePointAt(pos)andstr.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 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 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
-
Infinityis 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
-
NaNis 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 beingNaN/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 beingNaN/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
- 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.
iwill 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
- Use
parseInt(),parseFloat()orNumber()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 has two values,
trueorfalse
let on_off = true;
- To Convert to Boolean type use
Boolean()constructor
let message = 'I am a message';
let bool = Boolean(message); // bool == true;
if (message) { // message is automatically converted to Boolean 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
undefinedbut normally a variable is set tonullto 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 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
undefinedby JavaScript whereas values are usually set tonullmanually (explicitly) by the programmer
let obj = null;
typeof obj; // 'object';
typeof null; // 'object';
- In JavaScript the following values are considered to be false
false0 or -0-
""or''// empty string - `` // empty template literal
NaNnullundefined
- An object is an un-ordered collection of property / key values
- An object is not an ordered list like an
Array,SetorMap- 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
- 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
-
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
- 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
- Work in a similar way to the soft and hard equality operators
- These operate the same as Soft Equality / Inequality operators so type conversion is performed
- 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
- Performs numeric (string to number) conversion
// ex 1
let s = '01';
+s; // 1, same as parseInt(s) or Number(s);
-s; // -1
- 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
- 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"
- 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 }
-
The result of
a ?? bis:- 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 ?? busing the operators that we already know, like this
result = (a !== null && a !== undefined) ? a : b;
- 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
- When an operator or a statement expects a value of a certain type but a different type is given, JavaScript does automatic type conversion
- 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
- Non-numeric operands are converted to numbers or NaN
- 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
- 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
- No type conversion is performed
let a = 0 || "4"; // a == "4"
let b = 0 && "4"; // b == 0
- 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
newkeyword) that creates objects - All functions return a value
- If this is not explicitly stated, the function will return
undefined
- If this is not explicitly stated, the function will return
- Four ways to define a function
- Function Declaration
- Function Expression
- Function Constructor
- Arrow Function
// 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 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
- 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');
- 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
-
returnkeyword 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
thisis referenced, it's taken from outside context / function
- If
- Do not have
arguments - Can’t be called with
new - Don’t have
supermethod - Cannot be used as a constructor
- Do not have their "own"
- 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;
}
- Functions can be invoked in four ways
- As a function
- As a a method
- As a constructor
- Indirectly through
call()orapply()method
let square = function(x) {
return x * x;
};
let r = square(10); // r == 100
// 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
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
- 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 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 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'
- Every function has a special variable called
arguments -
argumentsis an array like object that contains every argument passed to the function when it is invoked -
argumentshas length property but because it's not a true array doesn't have normal array methods likeforEach(),join()andslice()
// 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
- 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
- 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
- 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'
- 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
- 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 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
- 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
- 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();
- Use IIFE
- Inside a function declaration,
thisisundefined - In browser,
thisis equal towindowobject - In node,
thisis equal toglobalobject
(function() {
'use strict';
return this === undefined;
}()); // true
(function() {
return this === undefined;
}()); // false
- 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,constorvarkeyword 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
}());
-
thiskeyword 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
//
}
- JavaScript supports global and starting with ES6, block level scope
- Curly braces with
letorconstkeyword
// 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
letorconstin 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
- 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
- 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;
};
- Variables declared with
varare hoisted to top during compile stage - Best practice is to use
constandletto 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
- 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,
undefinedis returned
// 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
// 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
- 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]
-
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"}
- 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'
}
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 ]
- Use standard
forloop
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-ofloop
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-inloop (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
Array.isArray([]) // true
Array.isArray({}) // false
- 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
-
Iterableobjects are a generalization of arrays - Iterables allow us to make any object useable in a
for..ofloop - 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.iteratormethod - 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"
- Introduced in ES6
- Set Object
- A
Setis 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
-
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']
- Introduced in ES6
- Map Object
- A
Mapis used to keep a list of key and value pairs - Similar to
hashesordictionariesin 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
-
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
- 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 infor..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
- 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
- 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
- Pass parameters as an object, and the function immediately destructurizes them into variables
// 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);
- Fundamental operations that can be performed on objects
- Create
- Query and Set
- Delete
- Test
- Iteration
- 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
newkeyword
// 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.prototypeandObject.prototype - d inherits properties and methods from
Date.prototypeandObject.prototype
- o inherits properties and methods from
- 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
// 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
- All objects have a
constructorproperty - 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
constructorproperty to instantiate a new object
user1 = new user.constructor();
user1 instanceof User; // true
- Use
JSON.stringify()andJSON.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
- Use object
constructorproperty ,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()orinstanceofto 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
prototypeobject 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
- 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"}
- 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'
- Use
deletekeyword
// 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
- Use
inoperator orObject.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
- Use
for..inloop. Object property must be enumerable for it to show infor..inloop
// 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-ofloop withObject.keys(),Object.values()andObject.entries() -
Object.keys(),Object.values()andObject.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
- 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 }
- 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'
// 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"
- All JavaScript functions are implicitly passed
-
argumentsarray -
thisobject
-
-
thiscontains a reference to the object (normally the calling object) that was in context when a function or method is invoked -
thisis always an object-
windowobject - global object
- user defined object
-
// 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
// 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)
- There are four rules for determining
thisvalue (or howthisvalue gets bound) - All rules depend on call site - the place in code where function gets executed
- This rule has lowest precedence
- In strict mode,
this===undefined - In non-strict mode,
this==windoworglobal(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
// 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
- Use
Function.prototype.call()orFunction.prototype.apply()methods to forcethisbinding - Both
call()andapply()take first parameter asthisvalue 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'
- Force
thisto 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
-
Function.prototype.bind()lets you specify the object that should be used asthisfor 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'
- Be aware of this binding in nested functions and callbacks
- In the below example inside
forEach()callback,thisloses its scope and will refer to anamevariable 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 = thispattern
// 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-ofloop to iterate arrays instead offorEach()
...
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
thiscontext - This means
thisremains bound to the original object making the function call
// ex 3
...
showPreferences: function() {
this.preferences.forEach((preference) => {
console.log(`${this.name}: ${preference}`);
});
}
...
- Steps in determining
thisvalue / Precedence rules- If function was called with
new,thisrefers to the object returned - If function was called with
call()orapply()method,thisis first parameter (object) passed in (Explicit Binding) - If function called via containing/owning object,
thisis the calling object (Implicit Binding) - Otherwise,
this==windoworglobalobject (except in strict mode)
- If function was called with
- 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()orObject.getOwnPropertyDescriptors()to get object property flags - Use
Object.defineProperty()orObject.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
- Accessor properties are represented by “getter” and “setter” methods
- In an object literal they are denoted by the
getandsetkeywords
// 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'
- 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
windoworthis - In
node.jsglobal object isglobalorthis -
Recent addition - In all environments
globalThiscan be used for the global object - Properties and methods of the global object are globally defined symbols available to a JavaScript program
-
letandconstbehave differently thanvar
// 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
- Several global properties are automatically created at JavaScript run-time
- Examples
Math,JSON,undefined,Infinity,NaN
-
Mathobject 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 (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
-
JSONobject has two methods for manipulating JSON data,JSON.stringify()andJSON.parse()
-
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 storeundefined
// ex 1
let user = {
sayHi() { // ignored
alert("Hello");
},
[Symbol("id")]: 123, // ignored
something: undefined // ignored
};
console.log( JSON.stringify(user) ); // {} (empty object
-
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"
-
undefined,InfinityandNaNare 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 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
- Use
window.setTimeout()andwindow.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);
- Use
window.setInterval()andwindow.clearInterval(timerID)
- Several Constructors are automatically created at JavaScript run-time
- 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"
<!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>
- 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
// 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/;
-
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
-
global property makes the pattern return all matches
- By default, the pattern only looks for the first occurrence of a match
- Use
gflag to set global property to true
-
ignoreCase property makes the pattern case-insensitive
- By default, the pattern is case sensitive
- Use
iflag 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
mflag 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
// 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"]
- In JavaScript functions are first class objects
- This means you can set properties and methods on functions - just like other objects
- 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
- Two useful built-in function methods are
call()andapply() - 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
- The
call()method can be used to set the value ofthisinside 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
nullas first parameter incall()if function, does not referencethis
// ex1
function square(x) {
return x*x;
}
square.call(null, 4); // 16
- The
apply()method works in the same ascall(), 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
- 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}
- 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'
- 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'
- TBD
- The two most important principles in OOP are
- Encapsulation
- Inheritance
- 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 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
- Reference Prototypes and Inheritance
- JavaScript uses a Prototypal Inheritance model
- 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'
- Every Constructor has a
prototypeproperty - Also recall that every object has a
constructorproperty that points to the constructor (function) that created it - Basic inheritance can be accomplished using a constructor's
protytypeproperty -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();
// 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'
- 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"]
- 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"]
- 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);
- 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
- Reference ES6 Classes
- Before ES6, using constructor functions were the only way of achieving class-like behavior in JavaScript
- ES6 introduced the new
classdeclaration 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
- Use
statickeyword 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'
- Every class has a
prototypeproperty 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
prototypeproperty
// 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
constructorproperty ,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
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]"
- A class can inherit from another class using the
extendskeyword 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]
- There are various ways to create objects
- One by One
- Factory Pattern
- Constructor Pattern - using
newkeyword - Constructor - Prototype Pattern
- ES6 Classes
- 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
// 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
Objecttype
// 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
instanceofoperator but doesn't provide encapsulation
- 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 means that different objects can have the same method, but implement it in different ways
- 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
- 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, async/await
- Using Promises is a way to mitigate Callback Hell
// ex 1
// Above callback example using Promises
login(userName)
.then(user => getPlayerInfo(user.id))
.then(info => loadGame(info))
.catch( throw error);
- Generators
- Generators are special functions used to produce iterators that maintain the state of a value
TBD
- 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
documentproperties <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 asdocument.head
- 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
parentNodechildNodes[] // array-like , includes elements, text and commentsfirstChild, lastChildnextSibling, previousSiblingnodeTypenodeValuenodeName
-
childNodeslooks like an array, but rather a collection – a special array-like iterable object - Use
for..ofto 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))
-
nodeTypeproperty gives the “type” of a DOM node -
nodeNamereturns the node name -
tagNamereturns 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"
- When interested in element nodes only (ignore text and comment nodes),
use the following properties
parentElement-
children[]// array-like firstElementChildlastElementChildnextElementSiblingpreviousElementSiblingchildElementCount
// 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>
- 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);
- Use global
documentobject to refer to Document object - Document object element select methods return a
NodeorNodeListobject - 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()orquerySelectorAll()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]);
- innerHTML: the contents
- outerHTML: full HTML of the element
- To access an element's content use
innerHTMLouterHTMLinnerText-
textContentproperties
// 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
// 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
hiddenattribute 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);
- 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;
}
- Using
elem.body.insertAdjacentHTMLmakes 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>
- See Modifying the Document for newer methods for removing elements
- Use
removeChild()method
// 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>
- Use
replaceChild()method
// ex 1
var replaceNode = parentNode.replaceChild(new, old);
- Use
cloneNodemethod
// 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
- 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");
- Non-standard attributes, dataset
- All attributes starting with
data-are reserved for use in HTML elements - They are available in the
datasetproperty - For example, if a HTML element has an attribute named
data-about, it’s available aselem.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'
- Styles and Classes
- CSS is used to style elements and can be scripted by JavaScript
// 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
styleproperty to manipulate CSS properties and values -
styleattribute/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
- Reading the element
styleproperty 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');
-
Instead of manipulating individual CSS styles one can use
classListproperty and methodsclassList.add()classList.remove()classList.toggle()
-
classListwas 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");
- JavaScript interactivity with HTML is handled mainly through Events
- Events are subscribed to using listeners (or handlers) that execute when event occurs
- 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
- Via HTML element attribute e.g.
- DOM Level 0 Event Handler
- DOM Level 2 Event Handler
- Inline 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
- 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>
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
- 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 withremoveEventListener() - IE8 and below uses
attachEvent()anddetachEvent()
- An object with a
handleEvent()method can be assgined as an event handler usingaddEventListener()
// 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>
- 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
-
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;
};
- Several types of events exist
- See full list here
-
mousedown,mouseupevents - In the below example
mousedown,mouseupandclickevents 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') );
-
dblclickevent - 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,mousemoveevents-
mouseoverevent occurs when the mouse pointer is placed over the element -
mouseoutevent occurs when the mouse pointer moves away from an element -
mousemoveevent whenever the mouse moves It will only occur while the cursor is over the element to which it’s applied
-
- Three events that occur when a user presses a key are -
-
keydown,keypressandkeyup
-
touchstart,touchend,touchmove,touchcenter,touchleave - Use HammerJS or zingtouch libraries to handle swiping and gestures
-
focus,blur,change,submit
- Some elements have default behavior associated with certain events
- When a user clicks on a link, the browser redirects to the
address in the
hrefattribute - A form is submitted when the user clicks on the Submit button
- When a user clicks on a link, the browser redirects to the
address in the
- 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);
});
- 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
-
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
- then on the
-
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)
- 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 is Bubbling
- To change to Capturing add set capture flag to
trueinaddEventListener()
// ex1
element.addEventListener('click', (event) =>
console.log('Clicked on element'),true);
- 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 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 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
- References
- 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
windowobject and only applies in a browser environment
- Use
window.navigatorobject to get information about the browser being used
- Use
window.locationobject 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();
- Use
window.historyobject 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
- Use
window.open(),window.close(),window.moveTo(),window.resizeTo()
-
Use window.screen object for information about the screen the browser is displayed on
window.screen.heightwindow.screen.widthwindow.screen.availwidthwindow.screen.availdHeight
-
Viewport size
window.innerHeightwindow.innerWidth
-
Use
window.pageXOffsetandwindow.pageYOffsetto 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)
- Use
document.cookieor use library like Cookies.js
- See Error Handling
- 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>
....
- 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
- References
- Use Fetch API for asynchronous data transmission - AJAX
- Replaces the need for direct manipulation of
XMLHttpRequestobject - Fetch API Uses Promises - avoids callback hell
- Global
fetch()method is provided to send requests - Skeleton example
// 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);
- References
- The Fetch API can be used with the
FormDatainterface - Use
FormDatato simplify the process of submitting form data using Ajax
- Data attributes provide a convenient way of adding data directly into the HTML markup
- 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
- References
- The Geolocation API is used to obtain the geographical position of a device
- 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
- References
- WebSockets provide a persistent connection between a client and server
- Both client and server can start sending data at any time
- The Notification API allows you to show messages using the system's notifications
- References
- Use Modernizr