Topics: tools, development
Author: Maitrik
<!-- works -->
alert('Hello')
alert('World')
<!-- works -->
alert("There will be an error")
[1, 2].forEach(alert)
Distinctions between primitives and objects
A primitive - All other types are called “primitive” because their values can contain only a single thing (be it a string or a number or whatever). - Is a value of a primitive type. - There are 6 primitive types: string, number, boolean, symbol, null and undefined.
An object - Is capable of storing multiple values as properties. - Can be created with {}, for instance: {name: "John", age: 30}. There are other kinds of objects in JavaScript; functions, for example, are objects.
var someObject = {someProperty: someValue};
console.log( typeof someObject );
var myObj = {
name : '"lol"'
};
console.log( myObj.hasOwnProperty('name') );
console.log( myObj.hasOwnProperty('nickname') );
ToString, String(value) – Occurs when we output something. Can be performed with String(value). The conversion to string is usually obvious for primitive values.
ToNumber, Number(value)– Occurs in math operations. Can be performed with Number(value).
undefined -> NaN
null -> 0
true / false -> 1 / 0
string -> The string is read “as is”, whitespaces from both sides are ignored ->
An empty string becomes 0. An error gives NaN
0, null, undefined, NaN, "" -> false
any other -> value
Tables | Undefined | Null |
---|---|---|
Definition | variable has been declared but not yet been assigned a value | assignment value that means “no value” |
Type | Undefined | Object |
JSON | Invalid | Valid |
Nature | Variable declared but not yet assigned | Represent intentional absence of object value |
Check | typeof variableName === “undefined” | variableName === null |
Arithmetic | Not-a-number (NaN) error | treated as zero value |
Comparison | Equality operator will return true | Identity operator will return false |
Identifier | Can be an identifier for a property of global object | Not an identifier for a property of the global object |
FOR loops are great for doing the same task over and over when you know ahead of time how many times you'll have to repeat the loop.
WHILE loops are ideal when you have to loop, but you don't know ahead of time how many times you'll need to loop.
Sometimes you want to make sure your loop runs at least one time no matter what. When this is the case, you want a modified while loop called a DO/WHILE loop.
#FOR Loop
for( var i = 0; i < 10 ; i++){
}
#WHILE Loop
var myCond = true;
while(myCond){
console.log("While is here!");
myCond = false;
}
var myCondi = false;
do{
console.log("Do/While is here!");
}while(myCondi)
Functions are values. They can be assigned, copied or declared in any place of the code.
Function Declaration
If the function is declared as a separate statement in the main code flow, that’s called a “Function Declaration”.
Function Declarations are processed before the code block is executed. They are visible everywhere in the block.
a function, declared as a separate statement, in the main code flow.
```js
function sum(a, b) {
return a + b;
}
```
Function Expression
If the function is created as a part of an expression, it’s called a “Function Expression”.
Function Expressions are created when the execution flow reaches them.
A function, created inside an expression or inside another syntax construct. Here, the function is created at the right side of the “assignment expression”
```js
let sum = function(a, b) {
return a + b;
};
```
Callback functions
The arguments of ask are called callback functions or just callbacks.
```js
// Without CallBack
function ask(question, yes, no) {
if (confirm(question)) yes()
else no();
}
function showOk() {
alert( "You agreed." );
}
function showCancel() {
alert( "You canceled the execution." );
}
// usage: functions showOk, showCancel are passed as arguments to ask
ask("Do you agree?", showOk, showCancel);
// With CallBack
ask(
"Do you agree?",
function() { alert("You agreed."); },
function() { alert("You canceled the execution."); }
);
```
Function Expression vs Function Declaration
A Function Expression is created when the execution reaches it and is usable from then on.
Function Declaration is usable in the whole script/code block.
// The Function Declaration sayHi is created when JavaScript is preparing
// to start the script and is visible everywhere in it.
sayHi("John"); // Hello, John
function sayHi(name) {
alert( `Hello, ${name}` );
}
// A Function Expression won't work
sayHi("John"); // error!
let sayHi = function(name) { // (*) no magic any more
alert( `Hello, ${name}` );
};
Let's go back to the analogy of computer languages being like regular spoken languages. In English, you have nouns (which you can think of as "things") and verbs (which you can think of as "actions"). Until now, our nouns (data, such as numbers, strings, or variables) and verbs (functions) have been separate.
A constructor, as its name suggests, is designed to create and set up multiple instances of an object.
An object literal on the other hand is one-off, like string and number literals, and used more often as configuration objects or global singletons (e.g. for namespacing).
Object literal: - Literal notation creates a single object. Literal notation uses curly brackets { } and the object's default properties are defined within the brackets using property:value notation.
```js
var objectName = {};
var james = {
job: "programmer",
married: false,
};
var myObject = {
iAm : 'an object',
whatAmI : function(){
alert('I am ' + this.iAm);
}
}
```
Object constructor:
- When we write **bob = new Object( );** we are using a built-in constructor called Object. This constructor is already defined by the JavaScript language and just makes an object with **no properties or methods.**
- Constructor notation involves defining an object constructor. And like defining a function, we use the function keyword. You can think of this constructor as a "template" from which you can create multiple objects. To create a new object from a constructor, we use the new keyword.
```js
function Object(){
this.iAm = 'an object';
this.whatAmI = function(){
alert('I am ' + this.iAm);
};
};
var objectName = new Object();
function Person(job, married) {
this.job = job;
this.married = married;
}
var gabby = new Person("student",true);
```
Differences between constructor and literal - The constructor object has its properties and methods defined with the keyword 'this' in front of it, whereas the literal version does not. - In the constructor object the properties/methods have their 'values' defined after an equal sign '=' whereas in the literal version, they are defined after a colon ':'. - The constructor function can have (optional) semi-colons ';' at the end of each property/method declaration whereas in the literal version if you have more than one property or method, they MUST be separated with a comma ',', and they CANNOT have semi-colons after them, otherwise JavaScript will return an error.
Bracket Notation : ObjectName["PropertyName"]
- An advantage of bracket notation is that we are not restricted to just using strings that is: no spaces and other limitations.
- Square brackets notation obj["property"]. Square brackets allow to take the key from a variable, like obj[varWithKey].
```js
let user = {};
// set
user["likes birds"] = true;
// get
alert(user["likes birds"]); // true
// delete
delete user["likes birds"];
```
Additional operators
- To delete a property: delete obj.prop.
- To check if a property with the given key exists: "key" in obj.
- To iterate over an object: for (let key in obj) loop.
Cloning and merging, Object.assign
- A variable stores not the object itself, but its “address in memory”, in other words “a reference” to it.
- When an object variable is copied – the reference is copied, the object is not duplicated.
- If we imagine an object as a cabinet, then a variable is a key to it. Copying a variable duplicates the key, but not the cabinet itself.
```js
let user = {
name: "John",
age: 30
};
//-----------Copying object---------
let admin = user; // copy the reference
admin.name = "Pete" // Updated to user reference object
alert( user.name ); // Pete as object reference changed
//-----------Clone using loop---------
// Cloning
let user = {
name: "John",
age: 30
};
let clone = {}; // the new empty object
for (let key in user) {
clone[key] = user[key];
}
clone.name = "Pete"; // changed the data in it
alert( user.name ); // John in original object as clone has its own object reference
//---------Object.Assign------------
let clone = Object.assign({}, user);
```
// id is a symbol with the description "id"
let id1 = Symbol("id");
let id2 = Symbol("id");
alert(id1 == id2); // false
let id = Symbol("id");
let user = {
name: "John",
[id]: 123 // not just "id: 123"
};
Global symbols - Symbols inside the registry are called global symbols. If we want an application-wide symbol, accessible everywhere in the code – that’s what they are for.
```js
// read from the global registry
let id = Symbol.for("id"); // if the symbol did not exist, it is created
```
- The Symbol.keyFor internally uses the global symbol registry to look up the key for the symbol. So it doesn’t work for non-global symbols. If the symbol is not global, it won’t be able to find it and return undefined.
```
alert( Symbol.keyFor(Symbol.for("name")) ); // name, global symbol
alert( Symbol.keyFor(Symbol("name2")) ); // undefined, the argument isn't a global symbol
```
let user = {
name: "John",
age: 30,
sayHi() {
alert(this.name);
}
};
user.sayHi(); // John
function sayHi() {
alert(this);
}
sayHi(); // undefined
In non-strict mode the value of "this" in such case will be the global object (window in a browser, we’ll get to it later in the chapter Global object). This is a historical behavior that "use strict" fixes.
NOTE: Arrow functions are special: they have no this. When this is accessed inside an arrow function, it is taken from outside.
The object-to-primitive conversion is called automatically by many built-in functions and operators that expect a primitive as a value.
There are 3 types (hints) of it: - "string" (for alert and other string conversions) - "number" (for maths) - "default" (few operators)
The specification describes explicitly which operator uses which hint. There are very few operators that “don’t know what to expect” and use the "default" hint. Usually for built-in objects "default" hint is handled the same way as "number", so in practice the last two are often merged together.
The conversion algorithm is:
Call objSymbol.toPrimitive if the method exists,
Otherwise if hint is "string" - try obj.toString() and obj.valueOf(), whatever exists.
Otherwise if hint is "number" or "default" - try obj.valueOf() and obj.toString(), whatever exists.
All numbers in JavaScript are stored in 64-bit format IEEE-754, also known as “double precision floating point numbers”.
Append "e" with the zeroes count to the number. Like: 123e6 is 123 with 6 zeroes.
A negative number after "e" causes the number to be divided by 1 with given zeroes. That’s for one-millionth or such.
For different numeral systems: - Can write numbers directly in hex (0x), octal (0o) and binary (0b) systems - parseInt(str, base) parses an integer from any numeral system with base: 2 ≤ base ≤ 36. - num.toString(base) converts a number to a string in the numeral system with the given base.
For converting values like 12pt and 100px to a number: - Use parseInt/parseFloat for the “soft” conversion, which reads a number from a string and then returns the value they could read before the error.
For fractions: - Round using Math.floor, Math.ceil, Math.trunc, Math.round or num.toFixed(precision). - Make sure to remember there’s a loss of precision when working with fractions.
More mathematical functions: - See the Math object when you need them. The library is very small, but can cover basic needs.
There are 3 types of quotes. Backticks allow a string to span multiple lines and embed expressions.
Strings in JavaScript are encoded using UTF-16.
To get a character, use "[array]"
To get a substring, use: slice or substring.
To lowercase/uppercase a string, use: toLowerCase/toUpperCase.
To look for a substring, use: indexOf, or includes/startsWith/endsWith for simple checks.
To compare strings according to the language, use: localeCompare, otherwise they are compared by character codes.
There are several other helpful methods in strings: - str.trim() – removes (“trims”) spaces from the beginning and end of the string. - str.repeat(n) – repeats the string n times.
// square brackets (usual)
let arr = [item1, item2...];
// new Array (exceptionally rare)
let arr = new Array(item1, item2...);
let arr = new Array(2); // will it create an array of [2] ?
alert( arr[0] ); // undefined! no elements.
alert( arr.length ); // length 2
alert( [] + 1 ); // ( "" + 1 ) -> "1"
alert( [1] + 1 ); // ( "1" + 1 ) -> "11"
alert( [1,2] + 1 ); // ( "1,2" + 1 ) -> "1,21"
for (let i=0; i<arr.length; i++) – works fastest, old-browser-compatible.
for (let item of arr) – the modern syntax for items only,
for (let i in arr) – never use.
We can use an array as a deque with the following operations:
sort(), reverse() and splice() modify the array but slice(), concat() and map() create a new array.
To add/remove elements:
To search among elements: - indexOf/lastIndexOf(item, pos) – look for item starting from position pos, return the index or -1 if not found. - includes(value) – returns true if the array has value, otherwise false. - find/filter(func) – filter elements through the function, return first/all values that make it return true. - findIndex is like find, but returns the index instead of a value. - To iterate over elements: - forEach(func) – calls func for every element, does not return anything.
To transform the array: - map(func) – creates a new array from results of calling func for every element. - sort(func) – sorts the array in-place, then returns it. - reverse() – reverses the array in-place, then returns it. - split/join – convert a string to array and back. - reduce(func, initial) – calculate a single value over the array by calling func for each element and passing an intermediate result between the calls.
```js
let value = arr.reduce(function(previousValue, item, index, array) {
// ...
}, initial);
```
Additionally: - Array.isArray(arr) checks arr for being an array.
MAP - Map is a collection of keyed data items, just like an Object. But the main difference is that Map allows keys of any type. - unlike objects, keys are not converted to strings. Any type of key is possible.
- 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 value by the key.
- map.clear() – clears the map
- map.size – returns the current element count.
let map = new Map();
map.set('1', 'str1'); // a string key
map.set(1, 'num1'); // a numeric key
map.set(true, 'bool1'); // a boolean key
// remember the regular Object? it would convert keys to string
// Map keeps the type, so these two are different:
alert( map.get(1) ); // 'num1'
alert( map.get('1') ); // 'str1'
alert( map.size ); // 3
// array of [key, value] pairs
let map = new Map([
['1', 'str1'],
[1, 'num1'],
[true, 'bool1']
]);
Iteration over Map - For looping over a map, there are 3 methods:
- map.keys() – returns an iterable for keys,
- map.values() – returns an iterable for values,
- map.entries() – returns an iterable for entries [key, value], it’s used by default in for..of.
let recipeMap = new Map([
['cucumber', 500],
['tomatoes', 350],
['onion', 50]
]);
// iterate over keys (vegetables)
for (let vegetable of recipeMap.keys()) {
alert(vegetable); // cucumber, tomatoes, onion
}
// iterate over values (amounts)
for (let amount of recipeMap.values()) {
alert(amount); // 500, 350, 50
}
// iterate over [key, value] entries
for (let entry of recipeMap) { // the same as of recipeMap.entries()
alert(entry); // cucumber,500 (and so on)
}
SET - A Set is a collection of values, where each value may occur only once. - Its main methods are: - new Set(iterable) – creates the set, optionally from an array of values (any iterable will do). - set.add(value) – adds a value, 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.
```js
let set = new Set();
let john = { name: "John" };
let pete = { name: "Pete" };
let mary = { name: "Mary" };
// visits, some users come multiple times
set.add(john);
set.add(pete);
set.add(mary);
set.add(john);
set.add(mary);
// set keeps only unique values
alert( set.size ); // 3
for (let user of set) {
alert(user.name); // John (then Pete and Mary)
}
```
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);
});
set.keys() – returns an iterable object for values,
set.values() – same as set.keys, for compatibility with Map,
set.entries() – returns an iterable object for entries [value, value], exists for compatibility with Map.
WeakMap - WeakMap is a variant of Map that allows only objects as keys and removes them once they become inaccessible by other means. - It does not support operations on the structure as a whole: no size, no clear(), no iterations.
WeakSet - WeakSet is a variant of Set that only stores objects and removes them once they become inaccessible by other means.
WeakSet/WeakMap is a special kind of Set/Map that does not prevent JavaScript from removing its items from memory.
Also does not support size/clear() and iterations.
WeakMap and WeakSet are used as “secondary” data structures in addition to the “main” object storage.
Once the object is removed from the main storage, if it is only found in the WeakMap/WeakSet, it will be cleaned up automatically.
// let [item1 = default, item2, ...rest] = array
// we have an array with the name and surname
let arr = ["Ilya", "Kantor"]
// Before
let firstName = arr[0];
let surname = arr[1];
// destructuring assignment
let [firstName, surname] = arr;
alert(firstName); // Ilya
alert(surname); // Kantor
The destructuring assignment also works with objects.
// let {prop : varName = default, ...} = object
let options = {
title: "Menu",
width: 100,
height: 200
};
let {title, width, height} = options;
alert(title); // Menu
alert(width); // 100
alert(height); // 200
// chnage name
// assign value
// change order
// function({
// incomingProperty: parameterName = defaultValue
//.})
let options = {
title: "Menu"
};
let {
width: w = 100,
height: h = 200,
title
} = options;
alert(title); // Menu
alert(w); // 100
alert(h); // 200
let options = {
title: "My menu",
items: ["Item1", "Item2"]
};
function showMenu({
title = "Untitled",
width: w = 100, // width goes to w
height: h = 200, // height goes to h
items: [item1, item2] // items first element goes to item1, second to item2
}) {
alert( `${title} ${w} ${h}` ); // My Menu 100 200
alert( item1 ); // Item1
alert( item2 ); // Item2
}
showMenu(options);
new Date(year, month, date, hours, minutes, seconds, ms)
Access date components - getFullYear() - getMonth() - getDate() - getHours() - getMinutes() - getSeconds() - getMilliseconds() - getTime() - getTime() - getTimezoneOffset()
Setting date components - setFullYear(year [, month, date]) - setMonth(month [, date]) - setDate(date) - setHours(hour [, min, sec, ms]) - setMinutes(min [, sec, ms]) - setSeconds(sec [, ms]) - setMilliseconds(ms) - setTime(milliseconds)
Date.now() - There’s a special method Date.now() that returns the current timestamp. - It is semantically equivalent to new Date().getTime(), but it doesn’t create an intermediate Date object. So it’s faster and doesn’t put pressure on garbage collection.
The JSON (JavaScript Object Notation) is a general format to represent values and objects.
JavaScript provides methods: - JSON.stringify to convert objects into JSON. - JSON.parse to convert JSON back into an object.
JSON.stringify - JSON is data-only cross-language specification, so some JavaScript-specific object properties are skipped by JSON.stringify. - Namely: - Function properties (methods). - Symbolic properties. - Properties that store undefined.
```js
let user = {
sayHi() { // ignored
alert("Hello");
},
[Symbol("id")]: 123, // ignored
something: undefined // ignored
};
alert( JSON.stringify(user) ); // {} (empty object)
```
- The important limitation: there must be no circular references.
```js
let room = {
number: 23
};
let meetup = {
title: "Conference",
participants: ["john", "ann"]
};
meetup.place = room; // meetup references room
room.occupiedBy = meetup; // room references meetup
JSON.stringify(meetup); // Error: Converting circular structure to JSON
```
- Excluding and transforming: replacer
- `let json = JSON.stringify(value[, replacer[, space]])`
- JSON.stringify is used with the first argument only. But if we need to fine-tune the replacement process, like to filter out circular references, we can use the second argument of JSON.stringify.
```js
let room = {
number: 23
};
let meetup = {
title: "Conference",
participants: [{name: "John"}, {name: "Alice"}],
place: room // meetup references room
};
room.occupiedBy = meetup; // room references meetup
alert( JSON.stringify(meetup, ['title', 'participants', 'place', 'name', 'number']) );
/*
{
"title":"Conference",
"participants":[{"name":"John"},{"name":"Alice"}],
"place":{"number":23}
}
*/
```
JSON.parse
```js
//Syntax : let value = JSON.parse(str[, reviver]);
let schedule = `{
"meetups": [
{"title":"Conference","date":"2017-11-30T12:00:00.000Z"},
{"title":"Birthday","date":"2017-04-18T12:00:00.000Z"}
]
}`;
// JSON.parse(str[, reviver]);
// JSON.parse(schedule, function());
schedule = JSON.parse(schedule, function(key, value) {
if (key == 'date') return new Date(value);
return value;
});
alert( schedule.meetups[1].date.getDate() ); // works!
```
Custom “toJSON” - Like toString for string conversion, an object may provide method toJSON for to-JSON conversion. JSON.stringify automatically calls it if available.
```js
let room = {
number: 23,
toJSON() {
return this.number;
}
};
let meetup = {
title: "Conference",
room
};
alert( JSON.stringify(room) ); // 23
alert( JSON.stringify(meetup) );
/*
{
"title":"Conference",
"room": 23
}
*/
```
Recursion is a programming term that means a “self-calling” function. Such functions can be used to solve certain tasks in elegant ways.
When a function calls itself, that’s called a recursion step. The basis of recursion is function arguments that make the task so simple that the function does not make further calls.
A recursively-defined data structure is a data structure that can be defined using itself.
- For instance, the linked list can be defined as a data structure consisting of an object referencing a list (or null).
- list = { value, next -> list }
- Linked list
```js
let list = { value: 1 };
list.next = { value: 2 };
list.next.next = { value: 3 };
list.next.next.next = { value: 4 };
```
- Trees like HTML elements tree or the department tree from this chapter are also naturally recursive: they branch and every branch can have other branches.
function showName(firstName, lastName, ...titles) {
alert( firstName + ' ' + lastName ); // Julius Caesar
// the rest go into titles array
// i.e. titles = ["Consul", "Imperator"]
alert( titles[0] ); // Consul
alert( titles[1] ); // Imperator
alert( titles.length ); // 2
}
showName("Julius", "Caesar", "Consul", "Imperator");
function showName() {
alert( arguments.length );
alert( arguments[0] );
alert( arguments[1] );
// it's iterable
// for(let arg of arguments) alert(arg);
}
// shows: 2, Julius, Caesar
showName("Julius", "Caesar");
// shows: 1, Ilya, undefined (no second argument)
showName("Ilya");
let arr = [3, 5, 1];
let arr2 = [8, 9, 15];
let str = "Hello";
let merged = [0, ...arr, 2, ...arr2];
alert( [...str] ); // H,e,l,l,o
alert(merged); // 0,3,5,1,2,8,9,15 (0, then arr, then 2, then arr2)
There’s an easy way to distinguish between them: - When ... is at the end of function parameters, it’s “rest parameters” and gathers the rest of the list of arguments into an array. - When ... occurs in a function call or alike, it’s called a “spread operator” and expands an array into a list.
Use patterns: - Rest parameters are used to create functions that accept any number of arguments. - The spread operator is used to pass an array to functions that normally require a list of many arguments.
Together they help to travel between a list and an array of parameters with ease.
Lexical Environment - In JavaScript, every running function, code block {...}, and the script as a whole have an internal (hidden) associated object known as the Lexical Environment. - “Lexical Environment” is a specification object. We can’t get this object in our code and manipulate it directly. - A Lexical Environment is created when a code block runs and contains block-local variables. Example : If, For, While and {...} - The Lexical Environment object consists of two parts: 1. Environment Record – an object that stores all local variables as its properties (and some other information like the value of this). 2. A reference to the outer lexical environment, the one associated with the outer code.
- A “variable” is just a property of the special internal object, associated with the currently executing block/function/script Environment Record.
- Working with variables is actually working with the properties of that object.
- “To get or change a variable” means “to get or change a property of that object.
- When the code wants to access a variable – the inner Lexical Environment is searched first, then the outer one, then the more outer one and so on until the global one.
- A function gets outer variables as they are now; it uses the most recent values.
- One call – one Lexical Environment : a new function Lexical Environment is created each time a function runs.
- Lexical Environment is cleaned up and deleted after the function run
```js
//----No Clousre----
//----Access outer Lexical Envrionment Object variable---
let name = "John";
function sayHi() {
alert("Hi, " + name);
}
name = "Pete"; // (*)
sayHi(); // Pete
```
Closures - A closure is a function that remembers its outer variables and can access them. - All functions in JavaScript are closures - A closure is an inner function that has access to the outer (enclosing) function’s variables—scope chain. - The closure has three scope chains: 1. it has access to its own scope (variables defined between its curly brackets), 2. it has access to the outer function’s variables, and 3. it has access to the global scope variables.
```js
function makeWorker() {
let name = "Pete";
return function() {
alert(name);
};
}
let name = "John";
// create a function
let work = makeWorker();
// call it
work(); // Pete
// ----- Example Two ------
let count = 100;
function makeCounter() {
let count = 0;
return function() {
return count++; // has access to the outer counter
};
}
let counter = makeCounter();
alert( counter() ); // 0
alert( counter() ); // 1
alert( counter() ); // 2
// ----- Example Three ------
let c = 4
const addX = x => n => n + x
const addThree = addX(3)
let d = addThree(c)
console.log('example partial application', d) // example partial application 7
//---Real life example------
function makeSizer(size) {
return function() {
document.body.style.fontSize = size + 'px';
};
}
var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);
document.getElementById('size-12').onclick = size12;
document.getElementById('size-14').onclick = size14;
document.getElementById('size-16').onclick = size16;
//--- html code
<a href="#" id="size-12">12</a>
<a href="#" id="size-14">14</a>
<a href="#" id="size-16">16</a>
```
Immediately invoked function expressions - IIFE - The Function Expression is wrapped with parenthesis (function {...}) - Parentheses around the function is a trick to show JavaScript that the function is created in the context of another expression, and hence it’s a Function Expression: it needs no name and can be called immediately.
```js
// Ways to create IIFE
(function() {
alert("Parentheses around the function");
})();
(function() {
alert("Parentheses around the whole thing");
}());
!function() {
alert("Bitwise NOT operator starts the expression");
}();
+function() {
alert("Unary plus starts the expression");
}();
```
Var : Global and Function scope - var variables are either function-wide or global, they are visible through blocks. - Variables have no block scope, they are visible minimum at the function level. - Hoisting : Variable declarations are processed at function start.
Let : Will provide true block scoping, unlike var - Ideal to use it for programming as it will help to avoid hosting.
Const - will make variable Read only - Never change , will give error if you try to change it. - Will shadow outer declaration - Block Scoping - We generally use upper case for constants that are “hard-coded”. Or, in other words, when the value is known prior to execution and directly written into the code.
The global object provides variables and functions that are available anywhere.
“window” object - "let/const" doesn't create a window property - The value of this in the global scope is window. - All scripts share the same global scope, so variables declared in one script.js file become visible in another ones:
```js
// Window 1
<script>
var a = 1;
let b = 2;
</script>
// Window 2
<script>
alert(a); // 1
alert(b); // 2
</script>
```
- Different scripts (possibly from different sources) see variables of each other.
- The idea to merge multiple aspects into a single window object was to “make things simple", but the multi-purpose window is considered a design mistake in the language.
- Solution:
- Using "script type='module'" fixes the design flaw of the language by separating top-level scope from window.
- Such script is considered a separate “module” with its own top-level scope (lexical environment).
```js
<script type="module">
// ----- var x does not become a property of window -----
var x = 5;
alert(window.x); // undefined
// ----- this in a module is undefined -----
alert(this); // undefined
let x = 5;
</script>
<script type="module">
alert(window.x); // undefined
alert(x); // Error: undeclared variable
</script>
```
- Valid uses of the global object
- create “polyfills”: add functions that are not supported by the environment (say, an old browser), but exist in the modern standard.
```js
if (!window.Promise) {
window.Promise = ... // custom implementation of the modern language feature
}
```
```js
let sayHi = function func(who) {
alert(`Hello, ${who}`);
};
```
- It allows the function to reference itself internally.
- It is not visible outside of the function.
// Sysntax
// let func = new Function ([arg1[, arg2[, ...argN]],] functionBody)
let sum = new Function('a', 'b', 'return a + b');
alert( sum(1, 2) ); // 3
// syntax
// `let timerId = setTimeout(func|code, [delay], [arg1], [arg2], ...)`
function sayHi(phrase, who) {
alert( phrase + ', ' + who );
}
setTimeout(sayHi, 1000, "Hello", "John"); // Hello, John
// Syntax
// let timerId = setInterval(func|code, [delay], [arg1], [arg2], ...)
// repeat with the interval of 2 seconds
let timerId = setInterval(() => alert('tick'), 2000);
// after 5 seconds stop
setTimeout(() => { clearInterval(timerId); alert('stop'); }, 5000);
Modal
Recursive setTimeout - Recursive setTimeout guarantees a delay between the executions, while setInterval – does not. - The real delay between func calls for setInterval is less than in the code because the time taken by func's execution “consumes” a part of the interval.
```js
// he real delay between func calls for setInterval is less than in the code
let i = 1;
setInterval(function() {
func(i);
}, 100);
// The recursive setTimeout guarantees the fixed delay (here 100ms).
let i = 1;
setTimeout(function run() {
func(i);
setTimeout(run, 100);
}, 100);
```
Zero time scheduling - Zero-timeout scheduling setTimeout(...,0) is used to schedule the call “as soon as possible, but after the current code is complete”.
```js
setTimeout(() => alert("World"));
alert("Hello");
```
- The first line “puts the call into calendar after 0ms”. But the scheduler will only “check the calendar” after the current code is complete, so "Hello" is first, and "World" – after it
- Where do we use it ?
- Trick to split CPU-hungry tasks using setTimeout.
- To let the browser do something else while the process is going on (paint the progress bar).
Call - The call() method is used to call a function with a given this and arguments provided to it individually. - myFunc.call(context, arg1, arg2…) – calls func with given context and arguments.
Apply - The apply() method is an important method of the function prototype and is used to call other functions with a provided this keyword value and arguments provided in the form of array or an array like object.
- myFunc.apply(context, [args]) – calls func passing context as this and array-like args into a list of arguments.
- The generic call forwarding is usually done with apply:
```js
let wrapper = function() {
return original.apply(this, arguments);
}
```
Bind - In JavaScript it’s easy to lose this. Once a method is passed somewhere separately from the object – this is lost. - The bind() method creates a new function that, when called, has its this keyword set to the provided value, with a given sequence of arguments preceding any provided when the new function is called.
```js
let user = {
firstName: "John",
sayHi() {
alert(`Hello, ${this.firstName}!`);
}
};
setTimeout(user.sayHi, 1000); // Hello, undefined!
// When setTimeout invokes it later, this will refer to something useless
```
- Functions provide a built-in method bind that allows to fix this.
- `let boundFunc = func.bind(context);`
- The result of func.bind(context) is a special function-like “exotic object”, that is callable as function and transparently passes the call to func setting this=context.
- Method func.bind(context, ...args) returns a “bound variant” of function func that fixes the context this and first arguments if given.
```js
let user = {
firstName: "John",
say(phrase) {
alert(`${phrase}, ${this.firstName}!`);
}
};
let say = user.say.bind(user);
say("Hello"); // Hello, John ("Hello" argument is passed to say)
say("Bye"); // Bye, John ("Bye" is passed to say)
```
apply(), call(), and bind() all take a this argument as a context to execute a function in, but call() and apply() invoke the function immediately where bind() returns a function that we can pass around or store as needed.
function mul(a, b) {
return a * b;
}
let double = mul.bind(null, 2);
alert( double(3) ); // = mul(2, 3) = 6
alert( double(4) ); // = mul(2, 4) = 8
alert( double(5) ); // = mul(2, 5) = 10
function curry(f) { // curry(f) does the currying transform
return function(a) {
return function(b) {
return f(a, b);
};
};
}
// usage
function sum(a, b) {
return a + b;
}
let carriedSum = curry(sum);
alert( carriedSum(1)(2) ); // 3
function curry(func) {
return function curried(...args) {
if (args.length >= func.length) {
return func.apply(this, args);
} else {
return function(...args2) {
return curried.apply(this, args.concat(args2));
}
}
};
}
function sum(a, b, c) {
return a + b + c;
}
let curriedSum = curry(sum);
alert( curriedSum(1, 2, 3) ); // 6, still callable normally
alert( curriedSum(1)(2,3) ); // 6, currying of 1st arg
alert( curriedSum(1)(2)(3) ); // 6, full currying
let group = {
title: "Our Group",
students: ["John", "Pete", "Alice"],
showList() {
// Traditional func will give error
this.students.forEach(function(student) {
// Error: Cannot read property 'title' of undefined
alert(this.title + ': ' + student)
});
// Arrow function will work
this.students.forEach(
student => alert(this.title + ': ' + student)
);
}
};
group.showList();
Objects can store properties and property was a simple “key-value” pair to us.
However, Object properties, besides a value, have three special attributes (so-called “flags”) - writable – if true, 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.
- Object.getOwnPropertyDescriptor allows to query the full information about a property.
```js
// Syntax : Object.getOwnPropertyDescriptor(obj, propertyName);
let user = {
name: "John"
};
let descriptor = Object.getOwnPropertyDescriptor(user, 'name');
alert( JSON.stringify(descriptor, null, 2 ) );
/* property descriptor:
{
"value": "John",
"writable": true,
"enumerable": true,
"configurable": true
}
*/
```
- Object.defineProperty allows to change property.
```js
// Syntax : Object.defineProperty(obj, propertyName, descriptor)
// For Multiple properties : Object.defineProperties(obj, descriptors)
let user = {};
Object.defineProperty(user, "name", {
value: "John",
writable: false,
enumerable: true,
configurable: true
});
let descriptor = Object.getOwnPropertyDescriptor(user, 'name');
alert( JSON.stringify(descriptor, null, 2 ) );
/*
{
"value": "John",
"writable": false,
"enumerable": true,
"configurable": true
}
*/
```
Two type of property in object. - data properties : - accessor properties : essentially functions that work on getting and setting a value - Accessor properties are only accessible with get/set. - get – a function without arguments, that works when a property is read, - set – a function with one argument, that is called when the property is set, - enumerable – same as for data properties, - configurable – same as for data properties.
```js
let user = {
name: "John",
surname: "Smith",
get fullName() {
return `${this.name} ${this.surname}`;
},
set fullName(value) {
[this.name, this.surname] = value.split(" ");
}
};
// set fullName is executed with the given value.
user.fullName = "Alice Cooper";
alert(user.name); // Alice
alert(user.surname); // Cooper
```
- Smarter getters/setters
- Getters/setters can be used as wrappers over “real” property values to gain more control over them.
```js
let user = {
get name() {
return this._name;
},
set name(value) {
if (value.length < 4) {
alert("Name is too short, need at least 4 characters");
return;
}
this._name = value;
}
};
user.name = "Pete";
alert(user.name); // Pete
user.name = ""; // Name is too short...
```
let animal = {
eats: true,
walk() {
alert("Animal walk");
}
};
let rabbit = {
jumps: true,
__proto__: animal
};
let longEar = {
earLength: 10,
__proto__: rabbit
};
// walk is taken from the prototype chain
longEar.walk(); // Animal walk
alert(longEar.jumps); // true (from rabbit)
There are actually only two limitations: - The references can’t go in circles. JavaScript will throw an error if we try to assign proto in a circle. - The value of proto can be either an object or null, other types (like primitives) are ignored.
“this” : - No matter where the method is found: in an object or its prototype. In a method call, "this" is always the object before the dot "object.method()". - If we call obj.method(), and the method is taken from the prototype, this still references obj. So methods always work with the current object even if they are inherited. - In inheritance methods are shared, but the object state is not.
let animal = {
eats: true
};
function Rabbit(name) {
this.name = name;
}
Rabbit.prototype = animal;
let rabbit = new Rabbit("White Rabbit"); // rabbit.__proto__ == animal
alert( rabbit.eats ); // true
The "prototype" property is widely used by the core of JavaScript itself. All built-in constructor functions use it.
Object.prototype - obj = {} is the same as obj = new Object(), where Object is a built-in object constructor function, with its own prototype referencing a huge object with toString and other methods.
```js
let obj = {};
alert(obj.__proto__ === Object.prototype); // true
// obj.toString === obj.__proto__.toString == Object.prototype.toString
// obj -> Object.prototype -> null
```
built-in objects : Array, Date, Function - Array/Date/Function -> Array/Date/Function.prototype (provides Array/Date/Function methods) -> Object.prototype (provides toString, object methods...). -> null - By specification, all of the built-in prototypes have Object.prototype on the top. Sometimes people say that “everything inherits from objects”.
```js
// ----- Array ------
let arr = [1, 2, 3];
// it inherits from Array.prototype?
alert( arr.__proto__ === Array.prototype ); // true
// then from Object.prototype?
alert( arr.__proto__.__proto__ === Object.prototype ); // true
// and null on the top.
alert( arr.__proto__.__proto__.__proto__ ); // null
// the result of Array.prototype.toString
alert(arr); // 1,2,3
// ----- Function -------
function f() {}
// it inherits from Function.prototype?
alert(f.__proto__ == Function.prototype); // true
// then from Object.prototype?
alert(f.__proto__.__proto__ == Object.prototype); // true, inherit from objects
```
Primitives - strings, numbers and booleans are not object. But.... - They are not objects. But if we try to access their properties, then temporary wrapper objects are created using built-in constructors String, Number, Boolean, they provide the methods and disappear. - null and undefined have no corresponding prototypes too
Changing native prototypes - Native prototypes can be modified. For instance, if we add a method to String.prototype, it becomes available to all strings: - During the process of development, we may have ideas for new built-in methods we’d like to have, and we may be tempted to add them to native prototypes. But that is generally a bad idea. - In modern programming, there is only one case where modifying native prototypes is approved. That’s polyfilling.
```js
String.prototype.show = function() {
alert(this);
};
"BOOM!".show(); // BOOM!
```
let clone = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj));
- Object.getPrototypeOf(obj) – returns the [[Prototype]] of obj (same as proto getter).
- Object.setPrototypeOf(obj, proto) – sets the [[Prototype]] of obj to proto (same as proto setter).Object.create(null)
to create a “very plain” object without protolet animal = {
eats: true
};
// create a new object with animal as a prototype
let rabbit = Object.create(animal);
alert(rabbit.eats); // true
alert(Object.getPrototypeOf(rabbit) === animal); // get the prototype of rabbit
Object.setPrototypeOf(rabbit, {}); // change the prototype of rabbit to {}
lass MyClass {
prop = value; // field
constructor(...) { // constructor
// ...
}
method(...) {} // method
get something(...) {} // getter method
set something(...) {} // setter method
[Symbol.iterator]() {} // method with computed name/symbol name
// ...
}
class User {
constructor(name) { this.name = name; }
sayHi() { alert(this.name); }
}
// proof: User is a function
alert(typeof User); // function
// ...or, more precisely, the constructor method
alert(User === User.prototype.constructor); // true
// The methods are in User.prototype, e.g:
alert(User.prototype.sayHi); // alert(this.name);
// there are exactly two methods in the prototype
alert(Object.getOwnPropertyNames(User.prototype)); // constructor, sayHi
Sometimes people say that class is a “syntax sugar” in JavaScript,because we could actually declare class without class keyword.
there are important differences 1. a function created by class is labelled by a special internal property [[FunctionKind]]:"classConstructor". So it’s not entirely the same as creating it manually. 2. Class methods are non-enumerable. A class definition sets enumerable flag to false for all methods in the "prototype". 3. Classes always use strict. All code inside the class construct is automatically in strict mode.
Class Expression
let User = class MyClass {
sayHi() {
alert(MyClass); // MyClass is visible only inside the class
}
};
new User().sayHi(); // works, shows MyClass definition
alert(MyClass); // error, MyClass not visible outside of the class
class Animal {
constructor(name) {
this.speed = 0;
this.name = name;
}
run(speed) {
this.speed += speed;
alert(`${this.name} runs with speed ${this.speed}.`);
}
stop() {
this.speed = 0;
alert(`${this.name} stopped.`);
}
}
// Inherit from Animal by specifying "extends Animal"
class Rabbit extends Animal {
hide() {
alert(`${this.name} hides!`);
}
}
let rabbit = new Rabbit("White Rabbit");
rabbit.run(5); // White Rabbit runs with speed 5.
rabbit.hide(); // White Rabbit hides!
Any expression is allowed after extends
class User extends f("Hello") {}
Overriding a method and constructor - Classes provide "super" keyword for that. - super.method(...) to call a parent method. - super(...) to call a parent constructor (inside our constructor only). - Arrow functions have no super - Constructors in inheriting classes must call super(...), and (!) do it before using this.
class MyClass {
static property = ...;
static method() {
...
}
}
In terms of OOP, delimiting of the internal interface from the external one is called encapsulation.
Coffee machine outside and inside, Programming objects are like coffee machines.
JavaScript, there are three types of properties and members: - Public: accessible from anywhere. They comprise the external interface. Till now we were only using public properties and methods. - Protected: accessible only from inside the class and those extending it. - Protected properties are usually prefixed with an underscore . "_waterAmount" - That’s a well-known convention, not enforced at the language level. Programmers should only access a field starting with from its class and classes inheriting from it. - Use getter/setter syntax to setup.
```js
class CoffeeMachine {
_waterAmount = 0;
set waterAmount(value) {
if (value < 0) throw new Error("Negative water");
this._waterAmount = value;
}
get waterAmount() {
return this._waterAmount;
}
constructor(power) {
this._power = power;
}
}
// create the coffee machine
let coffeeMachine = new CoffeeMachine(100);
// add water
coffeeMachine.waterAmount = -10; // Error: Negative water
```
- Private: accessible only from inside the class. These are for the internal interface.
- Privates should start with #. They are only accessible from inside the class.
- On the language level, # is a special sign that the field is private. We can’t access it from outside or from inheriting classes.
```js
class CoffeeMachine {
#waterLimit = 200;
#checkWater(value) {
if (value < 0) throw new Error("Negative water");
if (value > this.#waterLimit) throw new Error("Too much water");
}
_waterAmount = 0;
set waterAmount(value) {
this.#checkWater(value);
this._waterAmount = value;
}
get waterAmount() {
return this.waterAmount;
}
}
let coffeeMachine = new CoffeeMachine();
coffeeMachine.#checkWater(); // Error
coffeeMachine.#waterLimit = 1000; // Error
coffeeMachine.waterAmount = 100; // Works
```
arr.constructor === PowerArray
// here PowerArray inherits from the native Array:
// add one more method to it (can do more)
class PowerArray extends Array {
isEmpty() {
return this.length === 0;
}
}
let arr = new PowerArray(1, 2, 5, 10, 50);
alert(arr.isEmpty()); // false
let filteredArr = arr.filter(item => item >= 10);
alert(filteredArr); // 10, 50
alert(filteredArr.isEmpty()); // false
class Rabbit {}
let rabbit = new Rabbit();
// is it an object of Rabbit class?
alert( rabbit instanceof Rabbit ); // true
// works with constructor functions instead of class
function Rabbit() {}
alert( new Rabbit() instanceof Rabbit ); // true
// built-in classes like Array:
let arr = [1, 2, 3];
alert( arr instanceof Array ); // true
alert( arr instanceof Object ); // true
let sayMixin = {
say(phrase) {
alert(phrase);
}
};
let sayHiMixin = {
__proto__: sayMixin, // (or we could use Object.create to set the prototype here)
sayHi() {
// call parent method
super.say(`Hello ${this.name}`);
},
sayBye() {
super.say(`Bye ${this.name}`);
}
};
class User {
constructor(name) {
this.name = name;
}
}
// copy the methods
Object.assign(User.prototype, sayHiMixin);
// now User can say hi
new User("Dude").sayHi(); // Hello Dude!
The try..catch construct has two main blocks: try, and then catch:
try..catch to work, the code must be runnable means javascript code required to be syntactically correct.
try {
// code...
} catch (err) {
// error handling
}
// ---- Example -----
try {
alert('Start of try runs'); // (1) <--
lalala; // error, variable is not defined!
alert('End of try (never reached)'); // (2)
} catch(err) {
alert(Error has occurred!
); // (3) <--
alert(err); // (4) <-- ReferenceError: lalala is not defined
// Error object
// message – the human-readable error message.
// name – the string with error name (error constructor name).
// stack (non-standard) – the stack at the moment of error creation.
alert(err.name); // ReferenceError
alert(err.message); // lalala is not defined alert(err.stack); // ReferenceError: lalala is not defined at ... } alert("...Then the execution continues");
- Real life example, If json is malformed, JSON.parse generates an error, so the script “dies”.
```js
let json = "{ bad json }";
try {
let user = JSON.parse(json); // <-- when an error occurs...
alert( user.name ); // doesn't work
} catch (e) {
// ...the execution jumps here
alert( "Our apologies, the data has errors, we'll try to request it one more time." );
alert( e.name );
alert( e.message );
}
throw
operator. Technically, the argument of throw can be anything, but usually it’s an error object inheriting from the built-in Error
class. More on extending errors in the next chapter.
- Rethrowing is a basic pattern of error handling: a catch
block usually expects and knows how to handle the particular error type, so it should rethrow errors it doesn’t know.// Creating your own error using Error constructor
let error = new Error("Things happen o_O");
alert(error.name); // Error
alert(error.message); // Things happen o_O
// Example
let json = '{ "age": 30 }'; // incomplete data
try {
let user = JSON.parse(json);
if (!user.name) {
throw new SyntaxError("Incomplete data: no name"); // throw
}
blabla(); // unexpected error
alert( user.name );
} catch(e) {
if (e.name == "SyntaxError") {
alert( "JSON Error: " + e.message );
} else {
throw e; // rethrow (*)
}
}
try…catch…finally - The finally clause works in case of any exit from try..catch, even via the return statement: right after try..catch is done, but before the calling code gets the control. - The try..catch construct may have one more code clause: finally. - The code has two ways of execution: - If you answer “Yes” to “Make an error?”, then try -> catch -> finally. - If you say “No”, then try -> finally. - The finally clause is often used when code start doing something before try..catch and want to finalize it in any case of outcome.
```js
try {
... try to execute the code ...
} catch(e) {
... handle errors ...
} finally {
... execute always ...
}
```
Even if we don’t have try..catch, most environments allow to setup a “global” error handler to catch errors that “fall out”. In-browser that’s window.onerror.
window.onerror = function(message, url, line, col, error) {
// ...
};
instanceof
version is much better, because in the future we are going to extend ValidationError
, make subtypes of it, like PropertyRequiredError
.// The "pseudocode" for the built-in Error class defined by JavaScript itself
class Error {
constructor(message) {
this.message = message;
this.name = "Error"; // (different names for different built-in error classes)
this.stack = "<nested Calls>"; // non-standard, but most environments support it
}
}
class ValidationError extends Error {
constructor(message) {
super(message);
this.name = "ValidationError";
}
}
// The ValidationError class is very generic. Many things may go wrong.
// The property may be absent or it may be in a wrong format (like a string value for age)
class PropertyRequiredError extends ValidationError {
constructor(property) {
super("No property: " + property);
this.name = "PropertyRequiredError";
this.property = property;
}
}
// Usage of ValidationError
function readUser(json) {
let user = JSON.parse(json);
if (!user.age) {
throw new ValidationError("No field: age");
}
if (!user.name) {
throw new ValidationError("No field: name");
}
return user;
}
// Working example with try..catch
try {
let user = readUser('{ "age": 25 }');
} catch (err) {
if (err instanceof ValidationError) {
alert("Invalid data: " + err.message); // Invalid data: No property: name
alert(err.name); // PropertyRequiredError
alert(err.property); // name
} else if (err instanceof SyntaxError) {
alert("JSON Syntax Error: " + err.message);
} else {
throw err; // unknown error, rethrow it
}
}
loadScript('/my/script.js');
// the code below loadScript doesn't wait for the script loading to finish
// ...
// Naturally, the browser probably didn’t have time to load the script.
// So the immediate call to the new function fails.
loadScript('/my/script.js'); // the script has "function newFunction() {…}"
newFunction(); // no such function!
//CallBack to Rescure
loadScript('/my/script.js', function() {
// the callback runs after the script is loaded
newFunction(); // so now it works
...
});
// Pyramid of Doom , callback hell
loadScript('1.js', function(error, script) {
if (error) {
handleError(error);
} else {
// ...
loadScript('2.js', function(error, script) {
if (error) {
handleError(error);
} else {
// ...
loadScript('3.js', function(error, script) {
if (error) {
handleError(error);
} else {
// ...continue after all scripts are loaded (*)
}
});
}
})
}
});
let promise = new Promise(function(resolve, reject) {
// executor (the producing code, "singer")
});
state
— initially “pending”, then changes to either “fulfilled” or “rejected”,
- result
— an arbitrary value, initially undefined.resolve(value)
— to indicate that the job finished successfully:
- sets state to "fulfilled",
- sets result to value.
- reject(error)
— to indicate that an error occurred:
- sets state to "rejected",
- sets result to error.// reject
let promise = new Promise(function(resolve, reject) {
// after 1 second signal that the job is finished with an error
setTimeout(() => reject(new Error("Whoops!")), 1000);
});
/ resolve
let promise = new Promise(function(resolve, reject) {
// the function is executed automatically when the promise is constructed
// after 1 second signal that the job is done with the result "done"
setTimeout(() => resolve("done"), 1000);
});
The properties state
and result
of the Promise object are internal.
We can’t directly access them from our “consuming code”.
We can use the methods .then/.catch/.finally for that.
Promise object serves as a link between the executor (the “producing code” or “singer”) and the consuming functions (the “fans”), which will receive the result or error.
Consuming functions can be registered (subscribed) using methods .then, .catch and .finally.
then
If promise
is resolved receives result
If promise
is rejected receives error
promise.then(
function(result) { /* handle a successful result */ },
function(error) { /* handle an error */ }
);
catch
Use .catch(errorHandlingFunction)
if you’re interested only in errors.
The call .catch(f) is a complete analog of .then(null, f), it’s just a shorthand.
finally
finally is a good handler for performing cleanup, e.g. stopping our loading indicators, as they are not needed any more, no matter what the outcome is.
new Promise((resolve, reject) => {
/* do something that takes time, and then call resolve/reject */
})
// runs when the promise is settled, doesn't matter successfully or not
.finally(() => stop loading indicator)
.then(result => show result, err => show error)
.finally(f)
is a more convenient syntax than .then(f, f)
: no need to duplicate the function f.// CallBack
function loadScript(src, callback) {
let script = document.createElement('script');
script.src = src;
script.onload = () => callback(null, script);
script.onerror = () => callback(new Error(`Script load error for ${src}`));
document.head.append(script);
}
// Promise object that resolves when the loading is complete.
// The outer code can add handlers using .then
// Declare Promise
function loadScript(src) {
return new Promise(function(resolve, reject) {
let script = document.createElement('script');
script.src = src;
script.onload = () => resolve(script);
script.onerror = () => reject(new Error(`Script load error for ${src}`));
document.head.append(script);
});
}
// Usage
let promise = loadScript("https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js");
promise.then(
script => alert(`${script.src} is loaded!`),
error => alert(`Error: ${error.message}`)
);
promise.then(script => alert('One more handler to do something else!'));
Promises
Promises allow us to do things in the natural order. First, we run loadScript(script), and .then we write what to do with the result.
We can call .then on a Promise as many times as we want. Each time, we’re adding a new “fan”, a new subscribing function, to the “subscription list”.
Callbacks
We must have a callback function at our disposal when calling loadScript(script, callback). In other words, we must know what to do with the result before loadScript is called.
There can be only one callback.
new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 1000); // (*)
}).then(function(result) { // (**)
alert(result); // 1
return result * 2;
}).then(function(result) { // (***)
alert(result); // 2
return result * 2;
}).then(function(result) {
alert(result); // 4
return result * 2;
// Make a request for user.json
fetch('/article/promise-chaining/user.json')
// Load it as json
.then(response => response.json())
// Make a request to GitHub
.then(user => fetch(`https://api.github.com/users/${user.name}`))
// Load the response as json
.then(response => response.json())
// Show the avatar image (githubUser.avatar_url) for 3 seconds (maybe animate it)
.then(githubUser => {
let img = document.createElement('img');
img.src = githubUser.avatar_url;
img.className = "promise-avatar-example";
document.body.append(img);
setTimeout(() => {
img.remove();
resolve(githubUser);
}, 3000);
}))
// triggers after 3 seconds
.then(githubUser => alert(`Finished showing ${githubUser.name}`));
//------------------------
// split the code into reusable functions
function loadJson(url) {
return fetch(url)
.then(response => response.json());
}
function loadGithubUser(name) {
return fetch(`https://api.github.com/users/${name}`)
.then(response => response.json());
}
function showAvatar(githubUser) {
return new Promise(function(resolve, reject) {
let img = document.createElement('img');
img.src = githubUser.avatar_url;
img.className = "promise-avatar-example";
document.body.append(img);
setTimeout(() => {
img.remove();
resolve(githubUser);
}, 3000);
});
}
// Use them:
loadJson('/article/promise-chaining/user.json')
.then(user => loadGithubUser(user.name))
.then(showAvatar)
.then(githubUser => alert(`Finished showing ${githubUser.name}`));
// ...
.catch
not only catches explicit rejections, but also occasional errors in the handlers above.fetch('/') // fetch works fine now, the server responds with the HTML page
.then(response => response.json()) // rejects: the page is HTML, not a valid json
.catch(err => alert(err)) // SyntaxError: Unexpected token < in JSON at position 0
//Example
new Promise((resolve, reject) => {
resolve("ok");
}).then((result) => {
blabla(); // no such function
}).catch(alert); // ReferenceError: blabla is not defined
let promise = Promise.resolve(value);
// Same as:
let promise = new Promise(resolve => resolve(value));
let promise = Promise.reject(error);
// Same as:
let promise = new Promise((resolve, reject) => reject(error));
let promise = Promise.all([...promises...]);
// Example:
Promise.all([
new Promise(resolve => setTimeout(() => resolve(1), 3000)), // 1
new Promise(resolve => setTimeout(() => resolve(2), 2000)), // 2
new Promise(resolve => setTimeout(() => resolve(3), 1000)) // 3
]).then(alert); // 1,2,3 when promises are ready: each promise contributes an array member
let promise = Promise.race(iterable);
// Example:
Promise.race([
new Promise((resolve, reject) => setTimeout(() => resolve(1), 1000)),
new Promise((resolve, reject) => setTimeout(() => reject(new Error("Whoops!")), 2000)),
new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000))
]).then(alert); // 1 as first settled promise “wins the race”, all further results/errors are ignored.
let promise = Promise.resolve();
promise.then(() => alert("promise done"));
alert("code finished"); // this alert shows first
<script src="...">
is loaded, ready to be executed.
- a network operation, e.g. fetch is complete.setTimeout(() => alert("timeout")); // third as MicroTask
Promise.resolve()
.then(() => alert("promise")); // second as MicroTask
alert("code"); // first
let promise = Promise.reject(new Error("Promise Failed!"));
window.addEventListener('unhandledrejection', event => {
alert(event.reason); // Promise Failed!
});
async
ensures that the function returns a promise, and wraps non-promises in it.async function f() {
return 1;
}
f().then(alert); // 1
// Equals to following
async function f() {
return Promise.resolve(1);
}
f().then(alert); // 1
await
makes JavaScript wait until that promise settles and returns its result.await
literally makes JavaScript wait until the promise settles, and then go on with the result.await
only works inside an async
function.
- If it’s an error, the exception is generated, same as if throw error were called at that very place.
- Otherwise, it returns the result, so we can assign it to a value.async function f() {
let promise = new Promise((resolve, reject) => {
setTimeout(() => resolve("done!"), 1000)
});
let result = await promise; // wait till the promise resolves (*)
alert(result); // "done!"
}
f();
// With Promise
function loadJson(url) {
return fetch(url)
.then(response => {
if (response.status == 200) {
return response.json();
} else {
throw new Error(response.status);
}
})
}
loadJson('no-such-user.json') // (3)
.catch(alert); // Error: 404
// With async
async function loadJson(url) { // (1)
let response = await fetch(url); // (2)
if (response.status == 200) {
let json = await response.json(); // (3)
return json;
}
throw new Error(response.status);
}
loadJson('no-such-user.json')
.catch(alert); // Error: 404 (4)
next()
. When called, it resumes execution till the nearest yield <value>
statement// function* f(…) or function *f(…)
// both correct
function* generateSequence() {
yield 1;
yield 2;
return 3;
}
let generator = generateSequence();
let one = generator.next();
alert(JSON.stringify(one)); // {value: 1, done: false}
let two = generator.next();
alert(JSON.stringify(two)); // {value: 2, done: false}
let three = generator.next();
alert(JSON.stringify(three)); // {value: 3, done: true}
// iterable
for(let value of generator) {
alert(value); // 1, then 2, then 3
}
// With spread operator '...'
let sequence = [0, ...generateSequence()];
alert(sequence); // 0, 1, 2, 3
//In Function
function* generateSequence(start, end) {
for (let i = start; i <= end; i++) {
yield i;
}
}
let sequence = [...generateSequence(1,5)];
alert(sequence); // 1, 2, 3, 4, 5
function* gen() {
// Pass a question to the outer code and wait for an answer
let result = yield "2 + 2?"; // (*)
alert(result);
}
let generator = gen();
let question = generator.next().value; // <-- yield returns the value
generator.next(4); // --> pass the result into the generator
// Iterators
Symbol.iterator // Object method to provide iteraterable
any value // next() return value is
// Generators
function* // Declaration
{value:…, done: true/false} // generator.next() returns
//-----
//Async iterators
Symbol.asyncIterator // Object method to provide iteraterable
Promise // next() return value is
// Async generators
async function* // Declaration
Promise that resolves to {value:…, done: true/false} // // generator.next() returns
<script type="module">
a = 5; // error
</script>
Each module has its own top-level scope. In other words, top-level variables and functions from a module are not seen in other scripts.
A module code is evaluated only the first time when imported
// 📁 admin.js
let admin = { };
function sayHi() {
alert(`Ready to serve, ${admin.name}!`);
}
// 📁 init.js
import {admin} from './admin.js';
admin.name = "Pete";
// 📁 other.js
import {admin, sayHi} from './admin.js';
alert(admin.name); // Pete
sayHi(); // Ready to serve, Pete!
<script>
alert(this); // window
</script>
<script type="module">
alert(this); // undefined
</script>
<script type="module" src="...">
don’t block HTML processing, they load in parallel with other resources.
- module scripts wait until the HTML document is fully ready (even if they are tiny and load faster than HTML), and then run.
- relative order of scripts is maintained: scripts that go first in the document, execute first.// 3rd
// object: the script can 'see' the button below
// as modules are deferred, the script runs after the whole page is loaded
<script type="module">
alert(typeof button);
</script>
// 2nd
// Compare to regular script below:
// Error: button is undefined, the script can't see elements below
// regular scripts run immediately, before the rest of the page is processed
<script>
alert(typeof button);
</script>
// 1st
<button id="button">Button</button>
<script async type="module">
is allowed on both inline and external scripts. Async scripts run immediately when imported modules are processed, independently of other scripts or the HTML document.// the script below has async, so it doesn’t wait for anyone.
// all dependencies are fetched (analytics.js), and the script runs
// doesn't wait for the document or other <script> tags
<script async type="module">
import {counter} from './analytics.js';
counter.count();
</script>
// the module must have a path, e.g. './sayHi.js' or wherever the module is
import {sayHi} from 'sayHi'; // Error, "bare" module
// nomodule
<script type="module">
alert("Runs in modern browsers");
</script>
<script nomodule>
alert("Modern browsers know both type=module and nomodule, so skip this")
alert("Old browsers ignore script with unknown type=module, but execute this.");
</script>
Webpack
and deploy to the production server.// 📁 say.js
function sayHi(user) {
alert(`Hello, ${user}!`);
}
function sayBye(user) {
alert(`Bye, ${user}!`);
}
{sayHi, sayBye}; // a list of exported variables
// 📁 main.js
import {sayHi, sayBye} from './say.js';
sayHi('John'); // Hello, John!
sayBye('John'); // Bye, John!
// 📁 main.js with * and as
import * as say from './say.js';
say.sayHi('John');
say.sayBye('John');
Named export
- export class User {...}
- import {User} from ...
Default export
export default class User {...}
import User from ...
named exports must (naturally) have a name, while export default may be anonymous.
Please note that import/export statements don’t work if inside {...}.
Re-export:
- export {x [as y], ...} from "mod"
- export * from "mod"
(doesn’t re-export default).
- export {default [as y]} from "mod"
(re-export default).
import User from './user.js';
{User};
// Re-Export
{default as User} from './user.js';
import ... from getModuleName(); // Error, only from "string" is allowed
if(...) { import ...; // Error, not allowed! }
{import ...; // Error, we can't put import in any block}
import(module)
function can be called from anywhere. It returns a promise that resolves into a module object.let modulePath = prompt("Module path?");
import(modulePath)
.then(obj => <module object>)
.catch(err => <loading error, no such module?>)
JavaScript animations should be implemented via requestAnimationFrame. That built-in method allows to setup a callback function to run when the browser will be preparing a repaint. Usually that’s very soon, but the exact time depends on the browser.
When a page is in the background, there are no repaints at all, so the callback won’t run: the animation will be suspended and won’t consume resources. That’s great.
function animate({timing, draw, duration}) {
let start = performance.now();
requestAnimationFrame(function animate(time) {
// timeFraction goes from 0 to 1
let timeFraction = (time - start) / duration;
if (timeFraction > 1) timeFraction = 1;
// calculate the current animation state
let progress = timing(timeFraction);
draw(progress); // draw it
if (timeFraction < 1) {
requestAnimationFrame(animate);
}
});
}
Options: - duration – the total animation time in ms. - timing – the function to calculate animation progress. Gets a time fraction from 0 to 1, returns the animation progress, usually from 0 to 1. - draw – the function to draw the animation.
JavaScript animations can use any timing function. We covered a lot of examples and transformations to make them even more versatile. Unlike CSS, we are not limited to Bezier curves here.
CSS animations use GPU while JS animations use CPU
var x = 1; // Initialize x
console.log(x + " " + y); // '1 undefined'
var y = 2; // Initialize y
// The following code will behave the same as the previous code:
var x = 1; // Initialize x
var y; // Declare y
console.log(x + " " + y); // '1 undefined'
y = 2; // Initialize y
num = 6;
num + 7;
var num;
/* gives no errors as long as num is declared*/
var x = 1; // Declare and Initialize x
console.log(x); // '1'
console.log(hoist); // Output: ReferenceError: hoist is not defined
let hoist = 'The variable has been hoisted.';
const PI;
console.log(PI); // Ouput: SyntaxError: Missing initializer in const declaration
PI=3.142;
function getCircumference(radius) {
console.log(circumference)
circumference = PI*radius*2;
const PI = 22/7;
}
getCircumference(2) // ReferenceError: circumference is not defined
// PI was used before it was declared, which is illegal for const variables.
var double = 22;
function double(num) {
return (num*2);
}
console.log(typeof double); // Output: number
var double;
function double(num) {
return (num*2);
}
console.log(typeof double); // Output: function
Async/await is a new way to write asynchronous code. Previous options for asynchronous code are callbacks and promises.
Async/await is, like promises, non blocking.
Async/await makes asynchronous code look and behave a little more like synchronous code. This is where all its power lies.
Async - declares an asynchronous function (async function someName(){...}). - Automatically transforms a regular function into a Promise. - When called async functions resolve with whatever is returned in their body. - Async functions enable the use of await.
Await - pauses the execution of async functions. (var result = await someAsyncCall();). - When placed in front of a Promise call, await forces the rest of the code to wait until that Promise finishes and returns a result. - Await works only with Promises, it does not work with callbacks. - Await can only be used inside async functions.
el.addEventListener('click', listener, false) // listener doesn't capture
el.addEventListener('click', listener) // listener doesn't capture
var names = animals.map(function(animal){
return animal.name;
})
Functional: You pass an object to the function and do stuff
_.map([1, 2, 3], function(n){ return n * 2; });
OOP: You call function on the object and do stuff
_([1, 2, 3]).map(function(n){ return n * 2; });
In both examples [1,2,3]
(array) is an object.
let arr = [1];
let new_arr = arr.push(2);
// ------
let a = {
foo: 'bar'
};
let b = a;
b.foo = 'test2'
console.log(b.foo); // test2
console.log(a === b) // true
let name = ‘Bill’;
let full_name = name.concat(‘ Gates’);
// ------
let a = {foo: "bar"};
let b = Object.assign({},a);
b.foo = "bar2"
console.log(a); // {foo: "bar"}
console.log(b);// {foo: "bar2"}
console.log(b === a) // false
str = num.toString();
str = String(num);
num = Number(str);
num = +str;
return expression; or return;
expression
(function () {
return expression;
}())