- basic building blocks, basic types of information that we can store.
REPL : read → evaluate → print → loop
- only 1 type of number exists in JS, this can be:
- positive and negative numbers
- whole number (intigers)
- decimal numbers
- math operations :
arithmetic operators are the basic operators that we use to do sums in JavaScript:- + Addition
- - Subtraction
- * Multiplication
- / Division
- % Modulo or Remainder (8 % 3 → returns 2)
- ** Exponent (5 ** 2 → 25)
PEMDAS (order of operations):
Parentheses first
Exponents (ie Powers and Square Roots, etc.)
Multiplication and
Division
Addition and
Subtraction
-
NaN (Not A Number):
it is considered a numeric value (number), but it represents something that is not a number.- 0/0 → NaN (because it's impossible to assign a value to it, 0 / 0 is meaningless)
- 1 + NaN → NaN
typeof :
the typeof operator returns a string indicating the type of the unevaluated operand.
- eg.:
typeof 4 //"number" typeof NaN //"number"
-
-
x += y // x = x + y
++ → increments by
1
eg.:let x = 1 x++ // x = x + 1
-
-=
(subtraction assignment)
eg.:x -= y // x = x - y
-- → decremnts by
1
eg.:let x = 1 x-- // x = x - 1
-
Boolean :
There are two options for a boolean value, true
or false
.
-
booleans are to store yes or no values (true or false value)
-
eg.:
let isLoggedIn = true; let gameOver = false; const skyIsBlue = true;
String :
A string is textural information, a string of characters.
-
represent text
-
"must be wrapped in quotes"
.
"double"
or'single'
quotes, both work, but be consistent with them. Except a quote in a string, like:
'The dog said "Hello master!"'
- eg.:
let name = "Jane Doe"; // it's a string let numberString = "42"; // it's a string let emptyString = "" // it's a string (an empty string)
- eg.:
-
index :
-
strings are indexed. So each character has a corresponding index (a positional number).
In other words:
every character in a string has a corresponding number associated with it. It is a positional number, starting from0
. -
[]
square brackets with number[0]
, this is the 1st index of the character.eg.: the second character of the word 'dog'
let animal = "dog" animal[1] // "o" animal[42] // undifenied
-
-
length property :
thelength
property of a string object contains the length of the string.
It gives the number of characters.- chain on
.length
eg.:let animal = "dog"; animal.length; // 3 characters
- chain on
-
concatenation :
concatenating together strings to get a new string. Smushes two strings together (without space).
eg.:"hello" + "hello"; //"hellohello"
eg.:
let greeting = "Hello"; let user = "Gamer"; welcome + user; //HelloGamer //or add a space between let helloUser = greeting + " " + user; //Hello Gamer
-
you can't change a string❗️ eg.: the
let name = 'John';
with capital J. If I make the J lowercase j, it will be a new string. They take a different place in the memory.let name = 'John'; // place in the memory A name = 'john'; // place in the memory B
-
type coercion:
you can addnumber
to a string: because JavaScript tries and coerce different types to a common type. It makes the number string, because the other way around would not work (makes the string into number?). a common type. eg.:let result = 1 + "hello"; //"1hello"
Null :
- Intentional absence, lack of any value.
- Must be assigned
- eg.:
For a logged in user, we initially set it to 0
let loggedInUser = null; // explicitly indicitas there's nothing there
- Variables that do not have an assigned value.
- eg.:
I don't give a value to a variablelet variable; // undifenied = it is not defined
The difference between null and undifined:
- null is what you set explicitly.
- undefined is what you run into (accidentally) because that something is not defined
- varables are like labels for values:
- we can store a value and give it a name so that we can:
- refer back to it later
- use that value to do things
- change it laters
- we can store a value and give it a name so that we can:
To define a variable, we should use let, const, or var (though var isn't recommended these days anymore).
We only use those keywords when first declaring the variable (the first time we tell javaScript it exists).
For example:
let numberOfEggs = 12;
Then if we want to change the value of a variable, we reference the variable name WITHOUT let/const/var like:
numberOfEggs = 10;
It is possible defining a variable without let/const/var in JavaScript, but you should never do it. JS will treat your variable as a global variable.
-
basic syntax:
-
let :
let statement declares a block-scoped local variable, optionally initializing it to a value.
- let year = 2021;
eg.:let numChickens = 4; let numRoosters = 1; let totalChickens = numChickens + numRoosters //5;
-
const :
const
works like let except you cannot change the value! They cannot be reassigned. We useconst
to store things we know that will not change.
- const num = 7;
eg.:let num = 7; num = 20; // ERROR! const days= 30; days = days + 1 //TypeError: Assignment to constant variable.
-
var :
it's the old variable keyword. We don't use it anymore (although it still works).
-
-
Variables can CHANGE TYPES
- in the "regular" JS variables can chage types, but in TypeScript (a fancier JS) it cannot change types (it has to remain).
- eg.:
we can change a numeric variable to a boolean.
let numPeople = 42; // Number numPeople = false; //Changed to boolean numPeople = 99; //Back to number
-
-
rules:
- can't start with numbers
- can't contain space
- camelCase :
let daysOfTheYear = 365;
s
-
best practice:
- start with a lowercase letter (although uppercase works, but don't do that)
- it can start with an
_
(underscore) :let _age_ = 10;
, but no usually used. - the name should represent what it contians (meaningful names).
- generally avoid 1 letter variables.
- for booleans is a good practice to start the nameing with
is
.
Likelet isGameOver = false;
-
JS: Methods (string methods):
-
methods are built-in ACTIONS that we can perform with individual strings.
-
every string has a set of methods
-
every string can do the same thing as another string:
eg.:
the "hello" string can do the same as the "goodbye" string -
methods help to: search within a string
-
methods help to: replace part of a string
-
methods help to: chane the casing of a string
-
etc.
-
the syntax:
thing.method()
;
eg.:const message = "Hello, it is a nice day!"; const bigMessage = message.toUpperCase(); // "HELLO, IT IS A NICE DAY!"
- another, more general definition of methods:
Data is represented as properties of the object, and behaviors are represented as methods. For example, a Window object could have methods such as open and close, while its state (whether it is open or closed at any given point in time) would be a property.
-
- thing.toUpperCase()
- thing.toLowerCase()
- thing.trim():
eg.:
trim a space beginning of a stringconst input = " Hi, my name is..."; input.trim() // "Hi, my name is ..."
- thing.indexOf :
first occurrence of the specified value - thing.slice() :
it extracts a portion of a string and returns it as a new string, without modifying the original string
eg.:
pass in a begin index and an optional end index
const joke = "I love this joke!"; joke.slice(2, 11); // "love this" joke; // "I love this joke!" → So the original variable is unchaged
-
thing.replace :
pass in two arguments: the first is what want to replace, the second is what we want to replace it with. eg.:const joke = "I love this joke!"; joke.replace("love", "hate"); // "I hate this joke!" joke; // "I love this joke!" → So the original variable is unchaged
-
thing.replaceAll() :
returns a new string with all matches of a pattern replaced by a replacement.
eg.:let pangram = "The quick brown fox jumps over the lazy dog. If the dog reacted, was it really lazy?"; pangram.replaceAll("dog", "monkey"); // "The quick brown fox jumps over the lazy monkey. If the monkey reacted, was it really lazy?"
-
thing.repeat() :
it constructs and returns a new string which contains the specified number of copies (repeats) of the string on which it was called.
- chain methods:
thing.toUpperCase().trim();
→ makes the string uppercase and trim the space from the beginning or the end.
arguments are inputs that we can pass into the method to modify how they behave.
-
thing.method(arg)
-
we pass arguments inside of the parentheses
(value)
eg.:
pass the argument into theindexOf()
methodconst animal = "tigerwolf"; animal.indexOf("tiger"); // 0 → tiger start at index 0 animal.indexOf("wolf"); // 5 animal.indexOf("x"); // -1 → because it is not found
- 💡 the difference between method and property:
-
property has no
()
after. Eg.:"hello".length";
-
method has
()
after. Eg.:thing.toUppercase();
-
properties are like nouns where methods are like verbs
methods perform a function -
properties can't perform an action
-
the only value that a method has is the one that is returned after it finishes performing the action
-
method is just an action we can call upon later
-
Template literals are strings that allow embedded expressions, which will be evaluated and then turned into a resulting string.
In other words: template literals allow us to create strings where we can embed an expression inside the string and that expression will be evaluated and turned into a string.
-
kindof replaces concatenation.
-
using back-ticks ` ` and $ and curlybraces {} 👉 `String ${expression to evaluate}`
-
eg.:
Instead of this long concatenation:let blackSheep = 3; let whiteSheep = 4; "I counted " + blackSheep + whiteSheep // "I counted 7 sheep."
I can write this. Where:
${3 + 4}
will be evaluated and replace with a string.`I counted ${3 + 4} sheep.`; // "I counted 7 sheep."
-
another example:
concatenationlet product = "pizza"; let amount = 3; let price = 10; "I ordered " + amount + " " + product + "s" + for + price*amount + "€"." // "I ordered 3 pizzas for 30€."
temaplate literals
let product = "pizza"; let amount = 3; let price = 10; `I ordered ${amount} ${pizza}s for ${price*amount}€.` // "I ordered 3 pizzas for 30€."
-
contains properties and methods for mathematical constants and functions. (It’s not a function object.)
-
(an object is a collection of properties and methods.)
-
Math's properties and methods :
(here below these are mainly methods):
-
Math.PI
→ 3.141592653589793 -
Math.round(3.7)
→ 4 -
Math.abs(-434)
→ 434 -
Math.pow(2,4)
→ 16 -
Random Numbers 👉
Math.random()
- built in method
- gives a random decimal between 0 and 1 (non-inclusive)
- eg.:
Math.random() // 0.02169928868605231 Math.random() // 0.17593062488380706 Math.random() // 0.07464813343445598
- how to get a random number to get integers?
- a multy step process
- eg.:
a random number between 1 - 10
const step1 = Math.random(); // 0.252811726254621 const step2 = step1 * 10; // 2.52811726254621 const step3 = Math.floor(step2); // 2 // because it starts from 0, I add 1 const step4 = step3 + 1; // 3 // in 1 line: Math.floor(Math.random() * 10) + 1;
-
comparing two values: the left and the right.
-
booleans: it can be true or false
-
we can compare letters (their value based on their Unicode)
-
>
greater than
<
less than
>=
greater than or equal to
<=
less than or equal to
==
equality
!=
non equality
===
strict equality
!==
strict non-equality -
>
- eg.:
comparing the left to the right value → boolean1 > 3; // false 1 < 3; // true -1 < -1; // false 'a' > 'A'; // true '@' > 'a'; // false because unicode U+0040 > U+0061
- eg.:
-
>=
greater than or equal to- eg.:
comparing the left to the right value → boolean1 >= 3; // false 1 <= 10; // true -1 =< -1; // true 'a' >= 'b'; // false
- eg.:
-
==
double equals-
checks for equality of value, but not equality of type.
-
it coerces both values to the same type and then compares them, it can lead to unexpected results!
-
so it does not care about type❗️
-
eg.:
1 == 1; // true
But
// It doesn't care about the type: 1 == '1'; // true 'c' == 'b'; // false 0 == ''; // true true == false; // false 0 == false; // true null == undefined; // true
So, it converts them to the same type and then compares!
-
-
===
strict equality or tripple equals-
checks for equality of value and type, so it cares about type!
-
always use tripple equals when you're comparing things to see if they're equal.
- eg.:
1 === '1'; // false 0 == false; // false
- eg.:
-
-
all the
console
methods -
eg.:
console.log("Hello!");`
prints out "Hello!" to the console.
- print out, but in a pop-up window (not in the console)
- shows a message
- eg.:
alert("Hey!");
-
shows a message asking the user to input text. It returns the text or, if Cancel button or Esc is clicked, null.
-
it's useful for
input
s -
it gives us a string
-
eg.:
prompt("please enter a number");
-
the code is either true or false
-
the condition between the
{ }
runs, executes only of the condition (if (something === something)
) is true
-
eg.:
let rating = 3; if (rating === 3) { console.log("You are my hero!")' }
-
if the code is false, it does not run, but what is before and after will still run.
- eg.:
the app.js file
console.log("Before the conditional") // runs if (1 + 1 === 3) { console.log("In the condition"); // doesn't run } console.log("After the conditional") // runs
- eg.:
the app.js file
basically it is: if the if
part is false, than otherwise.
-
you can chain them together, so you can have as many
else if
as you need. -
eg.:
let rating = 2; if (rating === 3) { console.log("Hi!"); } else if (rating === 2) { console.log("Bye!"); }
-
eg.:
if one of the condition is true, it runs and the rest will be skipped.const age = 8; if (age > 5) { console.log("Free"); // this line runs because it's true , it doesn't go to the else if line } else if (age > 13 ){ console.log("5$"); } else if (age < 65 ){ console.log("5$"); }
basically it is the everything else
.
It's the last piece of the entire conditional, we don't specify any condition (we don't have parentheses).
It's going to run as the last thing if nothing else matched first (always the last statement).
eg.:
const days = prompt("enter a day!");
if (days === 'Monday') {
console.log("1st day of the week");
} else if (days === 'Tuesday') {
console.log("2md day of the week");
} else if (days === 'Wednesday') {
console.log("3rd day of the week");
} else if (days === 'Thursday') {
console.log("4th day of the week");
} else if (days === 'Friday') {
console.log("5th day of the week");
} else {
console.log("who cares, it's weekend");
}
- eg.:
const password = prompt("please enter a new password"); //password must be 5 characters if (password.length >= 5) { // password not inlcude space // this is the nested part if (password.indexOf(" ") === -1) { console.log("super"); } else { console.log("please don't use spaces") } } else { console.log("password too short"); }
it is another control flow statement, it can replace several statements.
-
not used often, but still.
-
keyword:
swittch
andcase
andbreak
anddefault
(it works like theelse
) -
switch
works like whenever there is a match, theswitch
starts executing the code until it hits abreak
.
default
: if nothing else matched. -
eg.:
const day = prompt("Enter a number"); switch(day) { case 1: console.log('Monday'); break; case 2: console.log("Tuesday"); break; case 3: console.log("Wednesday"); break; case 4: console.log("Thursday"); break; case 5: console.log("Friday"); break; case 6: case 7: console.log("Weekend"); break; default: console.log("Something like weekend!!!!"); }
A truthy value is a value that is considered true when encountered in a Boolean context.
All values are truthy unless they are defined as falsy (i.e., except for false, 0, -0, 0n, "", null, undefined, and NaN).
-
Falsy values:
- false
- 0
- "" (empty string)
- null
- undefined
- NaN
-
Everything else is truthy!
-
eg.:
Truthy
since everything is truthy except when falsy, when you write something to the input, is going to be true.const userInput = prompt("Enter smthing"); if (userInput) { console.log("Truthy!"); } else { console.log("Falsy"); }
-
eg.:
Falsyif (0) { // null, NaN, undefined also Falsy console.log("Truthy!"); } else { console.log("Falsy"); }
The conditional (ternary) operator is the only JS operator that takes three operands: a condition followed by a question mark (?), then an expression to execute if the condition is truthy followed by a colon (:), and finally the expression to execute if the condition is falsy. This operator is frequently used as an alternative to an if...else statement.
So the way it works: we write x ? y : z
, where x, y, and z are all JS expressions.
When our code is executed, x is evaluated as either “truthy” or “falsy.” If x is truthy, then the entire ternary operator returns y. If x is falsy, then the entire ternary operator returns z.
-
eg.:
Ifage
is >= todrinkingAge
, then we get theBuy Drink
message, otherwise we getDo Teen Stuff
message.let drinkingAge = 18; function canYouDrink(age) { return age >= drinkingAge ? 'Buy Drink' : 'Do Teen Stuff' } console.log(canYouDrink(21)); // age >= drinkingAg expression is true // Buy Drink console.log(canYouDrink(16)); // age >= drinkingAg expression is false // Do Teen Stuff
-
join an expression on the left and an expression on the right
-
&&
-
true is when the left && right is true (so the whole thing is true).
- true && true = true
- true && false = false
- false && true = false
- false && false = false
-
eg.:
&& true1 < 4 && 1 === 1; // true
-
eg.: A password checker. The password has to be greater than 10 characters, long or equal to and does not contain any space.
const password = prompt("Enter pw"); if (password.length >= 10 && password.indexOf(' ') === -1) { console.log("Valid password"); } else { console.log("Wrong password"); }
- true if either the left or the right is true or both are true. So when 1 side is true, its all true.
||
(pipe character)- true && true = true
- true && false = true
- false && true = true
- false && false = false
- eg.:
|| true1 === 1 || 2 > 7 // true
The order:
&&
runs first always, so&&
is before than the||
❗️
&&
and ||
together
- eg.:
const age = prompt("Enter your age"); if (age >= 0 && age < 5 || age >= 65) { console.log("free"); } else if (age >= 5 && age < 10) { console.log("$10"); } else if (age >= 10 && age < 65) { console.log("$20"); } else { console.log("there's no age like that"); }
-
!
expression returns true if expression is false -
!
(bang) -
it negates the value, so if something is false, than it turns to true.
-
eg.: False
!('a' === 'a'); // false !null // true, because null is inherently false
-
eg.: When you don't give a name it's false
const name = prompt("Enter your name"); if (!name) { console.log('ooops'); name = prompt("Write it again"); } else { console.log('Hi'); }
Arrays :
it's a data structure. (A data structure is a collection of data.)
-
Arrays allow us to group data together
-
A collection of values, and an ordered collection of values
-
let variable = [first, second, third];
(ordered values from left to right) -
arrays are indexed (starts from 0), so every element has an associated number.
- eg.:
let days = ["Monday", "Tuesday", "Wednesday"]; days[1]; // Tuesday
- eg.:
-
we can chain the indexes:
- eg.:
To get the lettery
of Mondaylet days = ["Monday", "Tuesday", "Wednesday"]; days[0][5] // y
- eg.:
-
update an array
- eg.:
let days = ["Monday", "Tuesday", "Wednesday"]; days.length; // 3 days[3] = "Thursday"; // The outcome: // let days = ["Monday", "Tuesday", "Wednesday", "Thursday"];
- eg.:
push()
add an element to the end of the array-
something.push("item")
in the parenthesis we add what we push -
eg.:
let letters = ["a", "b", "c"]; letters.push("d"); // the result: //letters = ["a", "b", "c", "d"];
-
the new length of the array (so the array has changed)
-
we can push multiple things on an array
let letters = ["a", "b", "c", "d"]; letters.push("e", "f"); // the result: //letters = ["a", "b", "c", "d", "e", "f"];
-
pop()
remove an element from the end of the array-
something.pop()
it does not require any argument, the parenthesis stays empty -
it gives element to us and it removes it from the array
- eg.:
let letters = ["a", "b", "c"]; letters.pop(); "c" // it gives the "c" and removes it from the array // the result: //letters = ["a", "b"];
- eg.:
-
I can capture the removed element into a variable
- eg.:
remove from theletters = ["a", "b", "c"];
array the last element and give it to anewVariable
.let newVariable = letters.pop(); // the result is that the new variable has the "c" // newVariable = "c"
- eg.:
-
stack (add in the end and remove from the end)
-
shift()
-
removes from the beginning (yes, not the unshift removes, but the shift!)
- eg.:
let letters = ["a", "b", "c"]; letters.shift(); // returns: // "a"
- eg.:
-
we can save it to a variable
- eg.:
let letters = ["a", "b", "c"]; let newVariable = letters.shift(); // returns: // newVariable = "a"
- eg.:
-
-
unshift()
with this method we can add a new element to the beginning of an array.- eg.:
in the()
we write the element that we want to add to the beginning of the array.let letters = ["a", "b", "c"]; letters.unshift("Z"); // element what we add! // returns: // ["Z", "a", "b", "c"]
- eg.:
-
concat()
it is used to merge arrays. It concatenates arrays and gives a new array.- We call
concat()
on one of the arrays and then we pass in a second array to concatenate with that initial array. That creates a new third array.- eg.:
let arrayA = ['a', 'b', 'c']; let arrayB = ['d', 'e', 'f']; let arrayAB = arrayA.concat(arrayB); // returns: // arrayAB = ["a", "b", "c", "d", "e", "f"]
- eg.:
- We call
-
includes()
returns true or false. It tels us if an array includes a certain value.- eg.:
doesarrayA
includes'b'
?let arrayA = ['a', 'b', 'c']; arrayA.includes('b'); // returns: // true
- eg.:
-
indexOf()
returns the first index at which a given element can be found in the array.-
it shows us the first time that element occurs
-
when element is not found, it gives
-1
-
eg.:
what is the index of'b'
in thearrayA
?
let arrayA = ['a', 'b', 'c', 'b']; arrayA.indexOf('b'); // it only gives the first match❗️ // returns: // 1
- eg.:
what is the index of
'Y'
in thearrayA
?
When element is not found, it gives-1
let arrayA = ['a', 'b', 'c']; arrayA.indexOf('Y'); // returns: // -1
-
-
reverse()
it reverses an array, it does it in place.
It means it is a destructive method: it changes the original array❗️-
use it with empty parentheses
-
eg.:
let arrayA = ['a', 'b', 'c']; arrayA.reverse(); // it overwrites the original array: // console.log(`arrayA`); // arrayA = ['c', 'b', 'a'];
-
-
slice()
it gives a copy of a portion of an array, a slice of an array.-
the original array will not be modified.
-
the array object is selected from start to end, where start and end represent the index of items in that array.
-
start and end are optional. If I don't specify them (leaving the parenthesis empty) it gives a copy of the whole array.
- eg.:
let letters = ['a','b','c','d','e','f']; letters.slice() // returns the copy of the whole array // ['a','b','c','d','e','f']
- eg.:
-
start is inlcuded, the end is not included, it just goes upto the index.
- eg.:
let letters = ['a','b','c','d','e','f']; letters.slice(2, 4) // goes from the index of 2 upto 4 (not inlcuding the end indeox of 4❗️) // returns: // ['c', 'd']
- eg.:
-
eg.:
only specified the start index
Array with 6 elements, and I want the first 3.let letters = ['a','b','c','d','e','f']; letters.slice(3) // goes from the index of 3 // returns: // ['d', 'e', 'f']
-
giving a negative index, it starts from the end of the array
- eg.:
let letters = ['a','b','c','d','e','f']; letters.slice(-3) // start from the end of the array // result: it gives the last 3 elements // ['d','e','f']
- eg.:
-
-
splice()
it changes the contents of an array by removing or replacing existing elements and/or adding new elements in place.
(Destructive method, doesn't make a copy.)-
splice is sort of joining something to something else.
-
we have to specify:
- where to start
- how many things to delete
- optionally, what to insert
-
eg.:
delete the 'blue'let colors = ['red', 'green', 'blue', 'yellow', 'purple', 'pink']; colors.splice(2, 1); // result: // output: ['red', 'green', 'yellow', 'purple', 'pink']
-
eg.:
insert 'magenta' between 'purple' and 'pink'let colors = ['red', 'green', 'yellow', 'purple', 'pink']; colors.splice(4,0,'magenta') //0 means delete nothing. I want to insert 'magenta' after the 4rd index //result: // ['red', 'green', 'yellow', 'purple', 'magenta', 'pink']
-
💡 Usually it is not efficient to update the middle of an array, rather try to update the end of an array if it is possible.
-
sort()
it sorts the elements of an array in place and returns the sorted array.-
destructive method
-
the default sort order is ascending❗️
But, it is built upon converting the elements into strings, then comparing their sequences of UTF-16 code units values. -
eg.:
- we expect that it will sort ascending, but becuase it uses the 1st digit, the result is different*
let numbers = [1, 42, 7000, -5, 6, 0]; numbers.sort(); // result: // [-5, 0, 1, 42, 6, 7000]
-
-
reference in the memory is like an address
-
in arrays, when we use
==
or===
it only compares the references in memory and not the content. That's why:- eg.:
['a', 'b'] === ['a', 'b']; // FALSE❗️
-
['a', 'b']
has a different reference number compared to['a', 'b']
-
if we link the arrays, they share the same reference
- eg.:
otherNumbers
array point to thenumbers
array's, so they are linkedlet numbers = [1, 2, 3]; let otherNumbers = numbers; // now compare them numbers === otherNumbers // true // if I make a change on one the other will change too❗️ otherNumbers.pop(); // [1, 2] numbers // [1, 2]
- eg.:
-
the values can change as long as the reference remains the same. So we can use
const variableName
.- eg.:
if I change the content of
const number
, it doesn't affect the reference, it remains the same.const number = [1, 2]; number.push(3) // [1, 2, 3] → same reference as before, [1, 2]. const number reference remains.
- eg.:
if I change the content of
-
reassignemnt causes error
- eg.:
const number = 1; number = 2; // error: Uncaught TypeError: Assignment to constant variable.
- eg.:
Multidimensional Arrays (nested arrays)
- arrays can store other arrays, that's the nested array
- eg.:
How to acces the number
const nestedArray = [ ['a', 'b'], [10, 30, 50], ['green', 'yellow'] ]
30
?
We have to go through 2 levels:nestedArray[1] // gives the second line [10, 30, 50] nestedArray[1][1] // gives the second element of the second arrays → number 30
- eg.:
Object's are collections of properties.
Objects are data stractures, where the data is stored in key value pairs, in other words properties.
-
property : 2 peices of information: a
key
and avalue
.
Value is the right side of the:
(colon).- eg.:
const objectLiteral = { key : 'value', key2 : 'value2' }
- eg.:
-
it's about labeling the information
-
we can use custome
keys
to access data (not indexes) -
using
{}
when declaring -
the order doesn't matter
-
we can have different types of objectd
-
eg.:
a key value pairs.const days = { sunnyDays : 120, coldDays : [180, 30, 'Z'], rainyDays : false, niceDays : 'weekends' }
-
using
[]
square brackets and quotation marks["value"]
after the varibale which store the property.- eg.:
const name = {firstName : "John", lastName : "Doe"} name["lastName"] // "Doe"
- eg.:
-
other way is using dot.notation syntax, using a
.
- eg.:
const name = {firstName : "John", lastName : "Doe"} name.lastName // "Doe"
- eg.:
-
💡 all the keys are converted into strings (except symbols)
-
eg.:
so number as a key, boolean as a key, etc. are all strings, not numbers, booleans, etc.const days = { 1 : "Monday", null : "Friday", true : "Sunday", favourite : "Saturday" } days[null] // "Friday" days["null"] // "Friday" days[1] // "Monday" days["1"] // "Monday" days[favourite] // ReferenceError, it expects a variable days["favourite"] // "Saturday"
-
if we are using something dynamic like a variable as a key in an object, we use the square brackets.
Variable as a key using dot notiation doesn't work.
-
adding or updating new information.
- eg.:
to change a key's valueto add an extra element:const dogs = { smalldog : "small", bigdog: "big" } dogs.smalldog = "verysmall"; dogs['bigdog'] = "quitebig"; // result: // { smalldog : "verysmall", bigdog: "quitebig" }
const dogs = { smalldog : "verysmall", bigdog: "quitebig" } dogs['tinydog'] = "tiny"; dogs.megadog = "huge"; // result: // { smalldog : "verysmall", bigdog: "quitebig", tinydog : "tiny", megadog : "huge"}
-
have an array with objects
-
have objects with objects
-
combination of arrays and objects
-
eg.:
An array (or list) of objectsconst blog = [ {user: "John", text: "Helooo", likes: 3}, {user: "Doe", text: "Yasss", likes: 5} ]
-
eg.:
An object which stores an array and an objectconst museums = { name : 'National Gallery', type : 'fine art' attractions : ['images', 'sculptures', 'ceramics'], // ← array ticketPrice: { children : 2, adult : 6 } // ← object }
-
accessing the elements:
- array containing objects:
const blog = [ {user: "John", text: "Helooo", likes: 3}, {user: "Doe", text: "Yasss", likes: 5} ] // accessing: blog[0].text // gives → 'Hellooo!' blog[0]['text'] // gives → 'Hellooo!'
- array containing objects:
remember:
- object are key value pairs { key : 'value'}
- arrays are list of things ['a', 'b', 'c']
the aim of looping is to repeat some functionality.
- loops allow us to repeat code
- there are several types of loops:
for loop expression is this:
for (
[intiailExpression];
[condition];
[incrementExpression];
)
-
for loops are preferred for a set number of iteration
-
so eg.:
Print all the numbers from1
to10
for (let i = 1; i <= 10; i++) { console.log(i); }
the code syntax:
start at 1 →
let i = 1;
stop at 10 →i <= 10;
add 1 each time →i++
-
initial expression :
making a new variablelet i
. It only exists for the purpose of the loop (counter variable). -
condition or conditional expression :
middle part is a boolean expressioni < 10
. As long as it istrue
, (while it is true) the loop is running. So how long the loop should run. -
increment expression :
the value ofi
is updatedi++
(you can also do like divide, multiply or subtract)
-
eg.:
when we want even numbers printed out (i += 2)for(let i = 0; i <= 20; i += 2) { console.log(i) }
to skip the 0 at the starting point: (let i = 2)
for(let i = 2; i <= 20; i += 2) { console.log(i) }
to have odd number, give 1 at the starting point: (let i = 1)
for(let i = 1; i <= 20; i += 2) { console.log(i) }
count down to 0
for(let i = 100; i >= 0; i -= 10) { console.log(i) }
multiply
for(let i = 10; i <= 1000; i *= 10) { console.log(i) } // prints out: 10, 100, 1000
infinite loop (take care it nover stops, your mrowser run out of memory)
for(let i = 10; i >= 0; i++) { console.log(i) }
- when writing a loop always make sure that how your loop will end, to avoid infinite loops.
It is basically iterating over an array. We use a for loop to create a number, which can be used to refer an element's index in the array.
-
eg.:
looping over an array, using 'i' as an index to acces the element from the arrayHere we are iterating from the beginning to the end of the array.
const foods = ['hamburger', 'hotdog', 'soup']; for (let i = 0; i < foods.length; i++) { console.log(i, foods[i]); } // result: // number(i) , (foods[i]) // 0 'hamburger' // 1 'hotdog' // 2 'soup'
Here we are iterating from the end to the beginning of the array.
Se we turn it around, and start with the highest index number and - 1, because we start from 0 index
const foods = ['hamburger', 'hotdog', 'soup']; for (let i = foods.length - 1; i >= 0; i--) { console.log(i, foods[i]); } // result: // number(i) , (foods[i]) // 2 'soup' // 1 'hotdog' // 0 'hamburger'
we are talking about nested loop, when a loop is inside of another loop
-
every iteration of the outer loop, the inner loop is going to have its own full cycle, as many times as the outer loop runs.
If outer loop has 3, the inner loop runs 3 times its own full cycle. -
eg.:
let greeting = 'Hi'; for (let i = 0; i <= 2; i++) { console.log("Outer loop:", i); for (let j = 0; j < greeting.length; j++) { console.log(' Inner loop:' , greeting[j] ) } } // Outer loop: 0 // Inner loop: H // Inner loop: i // Outer loop: 1 // Inner loop: H // Inner loop: i // Outer loop: 2 // Inner loop: H // Inner loop: i
- eg.:
with string template literal : `${i}`for(let i = 1; i <= 2; i++) { console.log(`i is: ${i}`); for (let j = 1; j < 4; j++) { console.log(` j is: ${j}`) } } // i is: 1 // j is: 1 // j is: 2 // j is: 3 // i is: 2 // j is: 1 // j is: 2 // j is: 3
- eg.:
-
Nested loops are very useful when we are dealing with nested arrays.
-
eg.:
I want to print out each name.
The first loop just prints out the 3 arraysconst names = [ ['John', 'Alexandra', 'Erika'], ['Joe', 'Eve', 'Kevin', 'Wilma'], ] for (let i = 0; i < names.length; i++) { console.log(names[i]); } // [ "John", "Alexandra", "Erika" ] // [ "Joe", "Eve", "Kevin", "Wilma" ]
The second, nested loop can access the nested arrays
I have to save the first loop's results into a variable, likeconst row
. It contains the arrays.
We use theconst row
variable for the nested loop, so we can iterate over therow
, and get the names from each array.for (let i = 0; i < names.length; i++) { const row = names[i]; // variable created console.log(`ARRAY #${i + 1}`) // it shows the number of the ARRAY. We add +1, so it doesn't start from ARRAY #0. for (let j =0; j < row.length; j++) { console.log(row[j]); // prints out the names } } // ARRAY #1 - [i] // John - [j] // Alexandra - [j] // Erika - [j] // ARRAY #2 - [i] // Joe - [j] // Eve - [j] // Kevin - [j] // Wilma - [j]
-
one syntax: while
and then inside of the parentheses, a (single condition).
-
while loops contue running as long as the test condition is true.
-
the addition, subtract, multiply, etc. operation are in the loop's body!
-
while loops are preferred when we don't know how many times we are going to iterate
(eg.: in games. The game contiues until somebody wins, because we don't know how many time we have to iterate.) -
eg.:
let num = 0; while (num <= 5) { console.log(num); num++; } // 0 1 2 3 4 5
-
eg.:
a more useful example for while loops. A password: as long as the user doesn't find the password, the program keeps asking you.const pw = "PassworD"; let guess = prompt("Hello user! Guess your password"); while(guess !== pw) { guess = print("The password is incorrect, try again") } console.log("Your password is correct") // it only runs, when the user entered "PassworD" correctly.
- what is the
break
statement?-
the
break
terminates the current loop, stops the execution of the current loop immediately. Then code just resumes running after the loop. -
eg.:
The program would go and print out numbers until reaches 100, but the break stop at 50.if (let i = 0; i < 100; i++) { console.log(i); if (i === 50) break; }
-
eg.:
The program is always true, until there is a break (so infinite loop until breaks).
So, here, it repeats the user's input, until the user writes quit, which makes running thebreak
and the program stops.let input = prompt("Write here:" ); while (true) { input = prompt(input); if(input === "quit") { break; } } console.log("You quit the program.")
-
eg.:
with for loopfor ( let i = 0; i < 1000; i++ ) { console.log(i); if (i === 100) break; }
-
iterates over arrays and other iterable objects.
-
it is a shorter, newer and easier way to iterate than with for loop.
-
eg.:
for... ofconst array1 = ['a', 'b', 'c']; for (const element of array1) { console.log(element); } // "a" // "b" // "c"
with for loop takes more typing
for (let i = 0; i < array1.length; i++) { console.log(array1[i]); } // "a" // "b" // "c"
nested arrays
const nestedArray = [ ['a', 'b', 'c'], ['A', 'B', 'C'] ]; for (let row of nestedArray) { for (let letter of row) { console.log(letter); } } // "a" // "b" // "c" // "A" // "B" // "C"
-
when you do need an index it is better to use a for loop❗️ O
-
with
for...of
__you can't iterate over a { key-value } pair, it gives an error.
iterates over an object, so all enumerable properties of an object that are keyed by strings.
-
it only gives us the key
-
eg.:
const object = { a: 1, b: 2, c: 3 }; for (const property in object) { console.log(property); } // it only gives us the key // "a" // "b" // "c"
to get the value
const object = { a: 1, b: 2, c: 3 }; for (const property in object) { console.log(`${property} : ${object[property]}`); } // key : value // "a : 1" // "b : 2" // "c : 3"
method returns an array of a given object's own enumerable property names, iterated in the same order that a normal loop would.
So it give the key of key-value pairs.
- eg.:
const object = { a: 'somestring', b: 42, c: false }; console.log(Object.keys(object)); // ["a", "b", "c"]
method returns an array of a given object's own enumerable property values, in the same order as that provided by a for...in loop.
So this method gives the values of a key-value pairs.
-
eg.:
const object = { a: 'somestring', b: 42, c: false }; console.log(Object.values(object)); // ["somestring", "42", "false"]
method returns an array of a given object's own enumerable string-keyed property [key, value] pairs.
So it gives back the nested array's key-value pairs. -
eg.:
const object = { a: 'somestring', b: 42 }; console.log(Object.entries(object)); // Array [ "a", "somestring" ] // Array [ "b", 42 ]
- eg.:
to get the average of the valuesconst dollars = { a : 20, b : 42, c : 50, d : 100 } let total = 0; for (average of Object.values(dollars)){ total += average; } console.log(total / Object.values(dollars).length) // result 53 // note, that {value-pairs} objects don't have length, not like [arrays].
Functions are reusable pieces of code, chunks of code, they have a name to them and we can use them execute them at a later point.
(Not all functions have names)
-
reusable chunks of code, so we can use a function any time once we defined a function.
-
modular: we can pass in some sort of input that will impact the output.
-
2 steps: (1.) 🛠 define and then (2.) ⏯ call
-
💡 It's a good habit, that when we have some functionality that could stand alone, so a distinct thing (something that does something) to move it into a separate function.
-
defining a function
arguments
return
scope
function expressions
higher order functions
returning functions
defining methods
thethis
keyword
Define:
function functionName() {
// the code does something
}
Execute:
functionName();
-
eg.:
defining a function and then executing itfunction greetingsLoudly() { console.log("Hello!"); console.log("Bonjour!"); console.log("Bye!"); } greetingsLoudly(); // "Hello!" // "Bonjour!" // "Bye!"
-
hoisting allows functions to be safely used in code before they are declared.
(Execute the function before you define it.) -
every method is a function
basically they are INPUT to a function.
function functionName("PARAMETER") {
do something
}
functionName("ARGUMENT");
Terminology:
-
Argument is the value of the function. So, when you call the function, the value is called argument.
-
Parameter is when we call the function and we pass a value in.
So the parameter is just a placeholder (a variable) that we define to use inside of the function definition.
function example(parameter) {
console.log(parameter);
}
example("argument");
-
eg.:
function greet(name) { console.log(`Hello ${name}`); } greet("John Doe"); // "Hello John Doe"
-
if an expected argument is not provided, it has a value "undefined"
- eg.:
no argument passed in but expected
function greet(name) { console.log(`Hello ${name}`); } greet(); // "Hello undefined"
- eg.:
no argument passed in but expected
-
Multiple arguments :
we can define functions that expect more than one argument.-
in this case we have to tell the function, how many arguments we'll use.
-
separate the parameters & arguements by a comma.
-
order counts:
(from left to right) the first parameter gets the first argument, the second parameter gets the second argument, etc. -
they can be different types: one parameter is a string, the other is a boolean, etc.
-
eg.:
multiple argumentsfunction greetings(firstName, lastName) { console.log(`Hello ${firstName} ${lastName}!`); } greetings("John", "Doe"); // "Hello John Doe!"
-
eg.:
multiple arguments with different types: string and numberfunction repeat(string, number) { let empty = ""; for(let i = 0; i < number; i++) { empty += string //string + "" + string + "" + string ...etc. }; console.log(empty); } repeat("Hello!", 3); // Hello!Hello!Hello!
-
We can ignore arguments, buy not passing them in, because JavaScript doesn't care. Until that argument is used in the code. In this case the code that could cause an error.
- eg.:
ignored an argument that is in the parameter, but that argument is used → errorfunction repeat(string, number) { console.log(string, number); // 2 arguments will needed } repeat("Hello!"); // we give 1 argument, instead of two! // Uncaught ReferenceError: number is not defined!
- eg.:
ignored an argument that is in the parameter, no errorfunction repeat(string, number) { console.log(string); // 1 argument will do it } repeat("Hello!"); // "Hello!" // 1 paramater // undefined // the 2nd is undefined, but it's not an error message
- eg.:
-
-
We can store the output (values) of a function.
-
eg.:
with RETURN we can store variables!function add(num1, num2) { return num1 + num2; } const total = add(2, 4); // call total // result: total gives 6!
-
eg.:
But we can not store in a variable num1 and num2 without the reutrnfunction add(num1, num2) { console.log(num1 + num2); } add(2, 4); // 6 let total = add(2, 4); // 6 total; // but only calling "total" we get undefined!!!
-
To write functions that have an output (that's not just console.log()), but something that we can save, we need to use the return key word❗️❗️❗️
- eg.:
If we save the Math.random() function into a varibale (rand), it captures the output of that function.
If we write a function and we want to capture its output, that's when we have to use return
let rand = Math.random(); rand = 0.9936277568240952;
function something(x, y) { return x + y; } let variable = something(2,4); let; // 6 - because variable now holds that value, thanks to the return key word in the function.
- eg.:
If we save the Math.random() function into a varibale (rand), it captures the output of that function.
-
The return statement stops the execution of a function ❗️❗️❗️
So the line which comes after the return will be never executed.- eg.:
return stops the execution of a functionfunction add(num1, num2) { return num1 + num2; console.log("Hello numbers!"); // this line will be never executed (unreachable code) } add(10, 20); // only result is: 30
- eg.:
-
We can only return a single value (only one thing)!
It can be an array[1, 2, 3, 4]
, because it's a single value. -
Built-in-functions return values, eg.
Math.random()
returns a vlue.
So you can store it in a variable. (let x = Math.random()
)
👈 go back or 👆go up to Functions
Scope :
A scope is a location where a variable is defined, and it specifies where we have access to that variable.
So basically it is the visibility of the variable.
-
where we define a variable in JavaScript impacts where we have access to it.
-
eg.: to see where the the variable
totalMails
deifned
defined with in the functionfunction receivedMails() { let totalMails = 10; console.log(totalMails); } receivedMails(); // result: 10
defined outside of the function - ReferenceError - because the variable exists in the function, it is scoped to that function. So we don't have acces to it. It is like a little bubble.
function receivedMails() { let totalMails = 10; } console.log(totalMails); receivedMails(); // result: ReferenceError: totalMails is not defined!
/using a global variable: outside of the function, and we update inside of the function, that makes a new variable. First we get zero and then six.
let totalMails = 0; function receivedMails() { totalMails = 10; } console.log(totalMails); receivedMails(); console.log(totalMails); // result: 0 // result: 10
-
not very common practice to update a global variable within a function.
-
usually in our functions we have our own internal variables.
When a variable is declared inside a function, it is only accessible within that function and cannot be used outside that function.
-
eg.:
function scope - the variable (greeting) is scoped to the (sayHello) functionfunction sayHello(){ let greeting = "Hello!"; greeting // "Hello!" } greeting; // NOT DEFINED! - outside of the function
-
if there is a variable defined with the same name in the function, then we will reference that variable first.
- eg.:
let food = "pizza"; function dinner() { let food = "quiche"; console.log(food) } console.log(food) // "pizza" - outside of the function dinner(); // "quiche" - inside of the function, redefines the "let food" variable.
- eg.:
A variable when declared inside of a conditional or inside a loop, they are accessible within that condition or loop.
-
The variables declared inside the curly braces are called as within block scope.
-
block refers to almost any time we see curly braces except for a function.
-
block includes conditionals and loops.
-
eg.:
variable declared outside and another inside of a if conditionallet diameter = 10; if (diameter > 2) { const square = 5; let str = "Hello!"; } console.log(diameter); // 10 console.log(str); // Reference Error: str is NOT DEFINED!
-
the before, when
var
was used. Thevar
variables are scoped to functions, but they are not scoped too blocks. -
using
let
orconst
the variables are block scoped (one of the main reasons thatlet
andconst
were introduced).
An inner function nested inside of a parent function, and the inner function has access to the variables defined in the scope of that outer function.
- eg.:
lexical scoping: nested functionfunction outer() { let hero = "Spiderman"; function inner() { let callHero = `Hello ${hero}!` console.log(callHero); } inner(); // executing the innerfunction inside of the outer function } outer(); // executing the outer function // result: "Hello Spiderman!"
👈 go back or 👆go up to Functions
An other way of defining a function is that storing a function in a variable.
-
a function expression is this:
const variable = function (x, y) { return x + y; } variable(1, 2); // 3
-
eg.:
storing a function in a variable, it has no name. The variable has a nameconst square
❗️const square = function(num) { return num + num; } square(10); // 20
-
a function statement eg.:
a function statement, which has a nameadd
❗️function add(x, y) { return x + y; } add(2+4); // 6
-
using a function expression is the same as using any other function (executing or naming is the same).
-
functions are values (in JavaSCript), meaning you can store them, you can pass them, etc.
👈 go back or 👆go up to Functions
are functions that operate on, work with other functions.
- they can accept other functions as arguments, and do something with them.
- they can return a function.
- 💡 remember: a function is just a value.
-
eg.:
functions as arguments. So, a function is passed in (here I namedfunc
).
(you can callfunc
anything else).function waving(func) { func(); func(); func(); } function greeting() { console.log("Hello!"); } waving(greeting); // pass a function as an argument! // "Hello!" // "Hello!" // "Hello!"
If I try to pass a number or a string, it gives an error, because it tries to execute it.
function waving(func) { func(); func(); func(); } waving(10); // TypeError: func is not a function!
👈 go back or 👆go up to Functions
they are returning a function as a value from within a function.
-
eg.:
this function creates random messages.function sayHi(){ const rand = Math.random(); if (rand > 0.5) { return function () { console.log("Hello!"); } } else { return function () { console.log("Good day!"); } } } sayHi(); // just calling the function, I don't call the inner function // result is a function, just the return value: // f () { console.log("Good day!"); } or { console.log("Hello!"); }
If I want to use the return value, I have to capture the function which contains the return value into a variable.
const greetings = sayHi(); // now the greeting variable holds a function... greetings(); //... and I can execute that function. greetings(); greetings(); // "Good day!" // "Hello!" // "Hello!"
Let see what happened in the code above:
- The outer function returns one of the 2 inner functions, but the inner function is never called to be executed.
- So to make the return function executed, we have to reach them: by calling the inner function in the way that applying the parenthesis
()
after the variablegreetings
(so it becomesgreetings()
) - There are 2 ways:
I.
save the result of the higher-order function to a variable, and then call that variable:
sayHi()()
II.
greetings = sayHi()
greetings()
-
So, we can say: a higher-order function returns another function, but if that returned function isn't called yet, we still have to do it manually, by adding the parentheses
()
(it's how we immediately call a function)❗️
-
A factory function: is a function that returns an object and can be reused to make multiple object instances Factory functions can also have parameters allowing us to customize the object that gets returned.
-
eg.:
An other example, where our function generates a function (based on an input)
This function can show if the number I give is betweenmin
andmax
(eg.5
is between1
and10
).Short explanation:
- we're using the
between()
function, that accepts 2 parameters(min, max)
, but it returns another function, that accepts1
single parameter(num)
. - When we call
between()
, it is returned the following:
return function (num) {
return num > min && num < max;
}
This function accepts a single parameter(num)
function between(min, max) { return function(num) { return num > min && num < max; } } between(1, 10); // returns a function: // f () (num) { return num >= min && num <= max; } // but if I save between(min, max) into a variable: const test = between(1, 10); test(5); // true test(30); // false
Detailed, step-by-step explanation:
• function between(min, max) { • return function(num) { • return num > min && num < max; • } • } // we save the between(min, max) into a variable: • const test = between(1, 10); // it translates to this: • test = return function(num) { • return num > 1 && num < 10; • } // to call this inner function (now saved to the 'const test' variable, // we need to pass 1 single argument, in the paranthesis right after the variable's name: • test(5) // Another way to have the same calculation is: // to call both functions at the same time, using multiple parentheses pairs, each pair to one function: • test = between(1, 10)(5);
- we're using the
👈 go back or 👆go up to Functions
We can add functions as properties on objects. We call them methods!
A method is a function that has been placed as a property on an object ❗️: we put a dot .
infront of its name (like we did with built in methods).
-
object’s method (a function that belongs to an object)
-
built in methods, like string methods, array methods, etc.
- eg.:
"ahoj!".toUpperCase(); // AHOJ!
- eg.:
-
What is the difference between a method and a function?
-
A method is simply a function that has been placed as a property on an object.
-
Every method is a function, but not every function is a method.
-
-
We can add a function as a property value (key : value pairs).
- eg.:
We made an object: math
We access 3 functions, each of them is a method.
(also they are objects, math object literals)
So method: is a function that's been added as a property on some object.
We need to call themconst math = { multiply : function(x, y) { return x * y; }, divide : function(x, y) { return x / y; }, square : function(x) { return x * x; } } // calling the method on the 'math' object: math.square(2); // gives 4
- eg.:
-
We can use a function as the value in a property, in an object.
- eg.:
Create acounting
objectconst counting = { square : function(num) { return num * num; }, cube : function(num) { return num ** 3; } } counting.cube(2); // 8
- eg.:
-
Shorthand way of adding methods: leaving out the
function
keyword and the colon:
.- eg.:
const counting = { square(num) { // → square : function(num) return num * num; }, cube(num) { // → cube : function(num) return num ** 3; } } counting.cube(2); // 8
- eg.:
💡 Reminder: an object is a standalone entity, with properties and type.
Like a cup: a cup is an object, with properties:
a cup has a color,
a cup has design, weight, a material it is made of, etc.
👈 go back or 👆go up to Functions
this :
this
keyword is used when we want to access other properties on the same object.
-
most common situation to use
this
: we typically use inside of an object in a method. -
the value of
this
differs depending on how a function is invoked (how it is called), so we can’t know the value ofthis
just by looking at the function itself, but we need to know the context in which the function is invoked. -
So, the
this
references the object that is executing the current function. -
If a function is part of an object, it is called a method → If the function is a method in an object,
this
references that object itself.
Otherwise, if that function is a regular function, so it's not part of an object,this
references the global object, thewindow
. -
eg.:
creating an objectperson
, it has a methodfullname()
this
refers to theperson
object.const person = { first : "John", // property last : "Doe", // property fullname() { // method return `${this.first} ${this.last}` } } person.fullname(); // "John Doe" person.last; // "Doe" person.last = "Smith" person.fullname(); // "John Smith"
if I try to acces last name without the
this
, it gives an errorconst person = { first : "John", last : "Doe", fullname() { return last; } } person.fullname; // ReferenceError: last is not defined
-
when we are in a method and we write
this
dot.
property, it refers to the object that the method is defined on.
Thethis
refers to what is on its left side.
-
So the value of
this
can change ❗️ -
the value of
this
depends on the invocation context of the function it is used in, so it depends on how we call the function.-
eg.:
make adog2
variable which refers to thedog.woof()
method
thethis
will change, and will refer to the top level element in JS, thewindow
1. const dog = { 2. name : 'Bulldog', 3. color : 'grey', 4. woof() { 5. console.log(`${this.name} says woof woof!`); 6. console.log("this is:", this); 7. } 8. } 9. 10. dog.woof(); 11. //(at line 5. result ↓:) 12. // Bulldog says woof woof! 13. //(at line 6. result: ↓) 14. // this is: {name: 'Bulldog', color: 'grey', woof: ƒ} 15. 16. const dog2 = dog.woof; // I capture dog.woof method in another variable. 17. // Note: we just give the function definition when used without parenthesis❗️ 18. 19. dog2(); // it is a function. 20. // [blank] says woof woof! 21. // this is: Window {window: Window, self: Window, document: document, name: '', location: Location, …}
-
when we call the
dog2()
thethis
is not pointing to thedog
object, therefore it doesn't know whatthis
means. That's why it is[[blank] says woof woof!
.
If we assigndog.woof()
with parenthesis to theconst dog2
global variable, the function is immediately executed, but we don't want that, since we want to control when to call it. Therefore we reassign thedog.woof
without paranthesise toconst dog2
, because we want to control when to call it, by usingdog2()
later. -
when I call (invoke)
dog.woof()
it is a method.woof()
refers to the object to the left (dog
). When I rundog2()
, which is the same function asdog.woof
, only it is invoked differently therefore thethis
is not referring to thedog
, but instead to thewindow
top level object.
So, if we call the thewoof
method on thedog
object then thethis
keyword will point to thatdog
object. However, by reassigning the function reference to a global variable likedog2
, when we call it with parentheses we are actually calling the function from the global (window) object, so thethis
keyword will indeed point to that global window object (even though we assigned the function from the object originally - still, the value of thethis
keyword will be determined based on which object we call the function from, and that changed when we assigned it to a global variable).
-
-
this
theWindow
is a special object, it is the top level object, the main object in JS. All the functions lives inside of it. -
just type
window;
into the browser's console, and you'll what the the window object contains. -
when I create a method, it is added to the window object.
- eg.:
by creatinghello
method, it is added to thewindow
object automatically. I can call itwindow.hello
.function hello() { console.log("Hello hello!"); } window.hello; // ƒ hello() { console.log("Hello hello!"); }
💡 We use the function without using the parenthesis when we ant to make a copy of it.
dog2 = dog.woof;
💡 What does invoke mean?
JavaScript Function Invocation is used to executes the function code and it is common to use the term “call a function” instead of “invoke a function”. The code inside a function is executed when the function is invoked.
(calling vs invoking: it is more a semantic, subtle difference).
When you call a function, you are directly telling it to run. When you invoke a function, you are letting something to run it. - eg.:
-
to sum up: the value of this differs depending on how a function is invoked (the call site), so we can’t know the value of this just by looking at the function itself, but we need to know the context in which the function is invoked.
-
MDN's explanation, very helpful.
Array methods accept a function as its arguments.
So a callback is a function passed as an argument to another function, this technique allows a function to call another function.
- forEach
Map
Arrow Functions
setTimeout
setInterval & clearInterval
Filter
Every & Some
Reduce
this
in arrow functions
accepts a callback function.
It runs a function, (so run some code) once per item in some array.
So we pass in a function, and that function will be called once per item, where each item is going to be passed into the function automatically.
-
eg.:
We want to print out all the numbers.
A common way to do
forEach
is to define the function as an anonymus function expression, in the forEach
We also need a parameter, herenum
The way it works: I call for each on every element, so
forEach
executes the print once per element: it passes1
to print, then2
to print then3
, etc.const numbers = [1, 2, 3, 4 ]; numbers.forEach(function(num) { console.log(num); }); // 1 // 2 // 3 // 4
This is the uncommon way, where we define a function above the forEach.
const numbers = [1, 2, 3, 4]; function print(element) { console.log(element) } numbers.forEach(print); // 1 // 2 // 3 // 4
-
but
for...of
❗️❗️❗️ is as efficient (and easier, shorter way) for doing something once per element.- eg.:
using thefor...of
const numbers = [1, 2, 3, 4]; for(let num of numbers) { console.log(num); } // 1 // 2 // 3 // 4
- eg.:
-
eg.:
printing out even numbersconst numbers = [1, 2, 3, 4, 5, 6]; numbers.forEach(function(num) { if (num % 2 === 0) { console.log(num); }); // 2 // 4 // 6
-
eg.:
pizzeria ratings, 1 to 10 starsconst pizzeria = [ { name: "Joe's Pizza", stars: 1 }, { name: "Marios's Pizza", stars: 5 }, { name: "Cecilia's Pizza", stars: 9 } ]; pizzeria.forEach(function(pizza) { console.log(`${pizza.name} ${pizza.stars} / 10`); }); // Joe's Pizza 1 / 10 // Marios's Pizza 5 / 10 // Cecilia's Pizza 9 / 10
💡 nowadays we use
for...of
rather thanforEach
.
👈 go back or 👆go up to Callback functions & Array Methods
creates a new array with the results of calling a callback on every element in the array.
-
it is also a callback function and it also runs that function once per element in the array, like the
forEach
. ButMap
is then generates a new array using the result, using the return value of that callback. -
❗️ it doesn't change the array; it creates a new one❗️
-
something.map(callback)
(to map an array from one state to another)
-
eg.:
The return value, which is coming back from callback functionMap
, is taking and adding into a new array. The new array returns and then we can save it under a new variable (const doubles). Here, we double every intigerconst numbers = [1, 2, 3, 4,]; const doubles = numbers.map(function(num) { // it calls the function on every number (goes to parameter every "num") return num * 2; // each number is * 2, then return to a new array which is stored in the new variable "doubles" }); doubles; // [2, 4, 6, 8]
a not common way to do it
const numbers = [1, 2, 3, 4,]; function doubles(num) { return num * 2; }; numbers.map(doubles); // [2, 4, 6, 8]
-
eg.:
with an object of key-value pairs create an array just for the pizzeria namesconst pizzeria = [ { name: "Joe's Pizza", stars: 1 }, { name: "Marios's Pizza", stars: 5 }, { name: "Cecilia's Pizza", stars: 9 } ]; const restos = pizzeria.map(function(pizza){ return pizza.name; }); restos; // ["Joe's Pizza", "Marios's Pizza", "Cecilia's Pizza"]
-
map
is usually used when we need a portion of a data, or we want to transform every element into a new array.
👈 go back or 👆go up to Callback functions & Array Methods
It allows us to write functions without the keyword function.
-
it is called in short the fat arrow (and in Ruby may known as the hash rocket )
-
syntactically compact alternative to a regular function expression, so the point is to make things shorter.
-
a traditional function vs. an arrow function:
traditional function
(normally this function requires a name)function (a){ return a + 100; };
arrow function
function word removed, arrow=>
placed between(a)
the argument and{
the opening body bracket(a) => { return a + 100; }
-
arrow functions are all anonymys. You can not name them, but we can store them in a variable.
- eg.:
we store the arrow function which has 1 parameter in the "const square" variablewe store the arrow function, wich has 2 parameters in the "const sum" variableconst square = ( x ) => { return x * x };
const sum = ( x, y ) => return x + y }
- eg.:
-
arrow function has parenthesis with the parameters
-
we can't declare a function on its own like:
function(x,y){ return x + y }
we have to give a function statement (a name) like:
function name (x,y) { return x + y };
-
for a single argument we don't need the parenthesis (for multiple arguments, we need!).
- eg.:
const variable = x => { return x + x }
- eg.:
-
we can't declare an arrow function on its own , like
(x,y) => { }
. So we have to save it in a variable. (💡 If we don't save it to a variable, once we create an array (or any other content), it's discarded.- eg.:
const variable = (x,y) => { return x + y }
- eg.:
-
arrow function without arguments, we have to use the empty parenthesis:
- eg.:
dice rollingconst dice = () => { return Math.floor(Math.random() * 6) + 1; }
- eg.:
-
we can make arrow fucntion even more compact by using the implicit return ❗️So we leave out the
return
keyword.
It only works with arrow function (doesn't work with a typical function expression).
To return without return replace the curlybraces{ }
with parenthesis( )
!- eg.:
const variable = (x) => ( x * x ) variable(5); // 25
- eg.:
-
it can be even shorter, a one liner, by leaving out completely the parenthesis. Good for short codes. If the return is long, maybe don't use it on one line, to make it readable.
- eg.:
const variable = (x, y) => x + y variable(2, 4); // 6
- eg.:
-
❗️ implicit returns only work, when there is only 1 statement in the budy of the function!!!
- eg.:
arrow function exercise, using mapconst pizzeria = [ { name: "Joe's Pizza", stars: 1 }, { name: "Marios's Pizza", stars: 5 }, { name: "Cecilia's Pizza", stars: 9 } ]; const restos = pizzeria.map((pizza) => { return `${pizza.name} - ${pizza.stars}` }); // we can leave out the parameters parenthsis, and the return (see the ()) const restos = pizzeria.map(pizza => ( `${pizza.name} - ${pizza.stars}` )); // make it to a 1 line const restos = pizzeria.map(pizza => `${pizza.name} - ${pizza.stars}`); restos; // ["Joe's Pizza - 1", "Marios's Pizza - 5", "Cecilia's Pizza - 9"]
👈 go back or 👆go up to Callback functions & Array Methods
it sets a timer which executes a function or specified piece of code once the timer expires.
it expects to pass 2 arguments:
- a callback
- a number of milliseconds to delay the execution of the function
eg.:
setTimeout: after 3 seconds, it executes the function. We have to pass in a callback:
Otherwise, if we just passed in console.log
, that is going to execute it immediately.
So the goal is to make the browser wait 3 seconds.
setTimeout(() => {
console.log("Hello!") // 1st argument
}, 3000); // 2nd argument
// wait 3 seconds, then:
// "Hello!"
Another example: first it runs "Hello there!", than immediately the "Bye!", and then the setTimeout
"are you still there?". Instead of "Hello!", "Are you still there?", "Bye!"
console.log("Hello!");
setTimeout(() => {
console.log("Are you still there?")
}, 3000);
console.log("Bye!");
👈 go back or 👆go up to Callback functions & Array Methods
it calls a callback function every X time. So, we can repeat something with the setInterval
.
-
eg.:
setInterval
aMath.random
. Every 2 seconds prints out a random number. So it continues to call that function ever 2 seconds.setInterval(() => { console.log(Math.random()) }, 2000); // 2000 milliseconds
How to stop it? Use the
clearInterval()
👈 go back or 👆go up to Callback functions & Array Methods
it cancels a timed, repeating action which was previously established by a call to setInterval()
.
-
We have to save the value of the
setInterval
into a variable. Doing this, it returns the ID of corresponding interval we set up.
So, if we have several differentsetInterval
s we can specify which one we want to stop. -
eg.:
Save the s'setInterval' into a variable (id)
const variable = setInterval(() => {
console.log(Math.random())
}, 2000);
variable; // calling the variable, which has the setInterval, which starts to print every 2 seconds
// 0.8287249162601189
//0.13595591599851664
// 0.5061340888408574
clearInterval(variable);
// variable stops
👈 go back or 👆go up to Callback functions & Array Methods
creates a new array with all the elements that pass the test implemented by the provided function.
So when we want to filter out or make a subset in a new array.
- eg.:
We want to pull out just the the odd numbers into their own array.
So, we pass in a callback and this callback needs to return true or false, (boolean function). If that callback returns true for any element, that element will be added to the filtered array Otherwise, it's ignored
const nums = [6, 5, 4, 3, 2, 1];
const odds = nums.filter(n => {
return n % 2 === 1; // the callback return tru or false
// if true, 'n' is added to the filtered array
});
odds;
// [5, 3, 1]
Another example: filter out to a new array the pizzerias which score is equal or bigger than 5
const pizzeria = [
{
name: "Joe's Pizza",
stars: 1
},
{
name: "Marios's Pizza",
stars: 5
},
{
name: "Cecilia's Pizza",
stars: 9
}
];
const goodRestos = pizzeria.filter( p => {
return p.stars >= 5
});
goodRestos;
// [{name: "Marios's Pizza", stars: 5}, {name: "Cecilia's Pizza", stars: 9}] // array of objects
the same code with a 1 liner
const goodRestos = pizzeria.filter( p => p.stars >= 5);
if we want to have the titles of the pizzerias, not the whole array of objects, we can combine methods, filer
and map
.
const pizzeria = [
{
name: "Joe's Pizza",
stars: 1
},
{
name: "Marios's Pizza",
stars: 5
},
{
name: "Cecilia's Pizza",
stars: 9
}
];
const goodRestos = pizzeria.filter( p => p.stars >= 5);
// 0: [{name: "Marios's Pizza", stars: 5},
// 1: {name: "Cecilia's Pizza", stars: 9}]
const restoNames = goodRestos.map( n => n.name);
// ["Marios's Pizza", "Cecilia's Pizza"]
we can do the codes above in one singe lines: chain them together
const goodRestos = pizzeria.filter( p => p.stars >= 5).map(n => n.name);
goodResots;
//["Marios's Pizza", "Cecilia's Pizza"]
👈 go back or 👆go up to Callback functions & Array Methods
The every()
method tests whether ALL elements in the array pass the the provided function (the test). It returns a Boolean value.
So, if every element passed into that function returns true, then the entire every function call returns true.
- eg.:
check if EVERY goals reach 50. If all the goals are equal or greater than 50, returns TRUE
const goals = [80, 100, 50, 40, 77]; goals.every(points => points >= 50) // each element from points array is passed into the callback ***every(callback)***, callback should return true/false. // result is FALSE, because one of the goals is less than 50.
- eg.:
a function (evens) which accepts a single array of numbers, returns true if the numbers are even and false of odd.function evens(num){ (num.every( (x) => (x % 2 === 0) ) ) { return true; } } evens([2, 4, 6, 8]); // true evens([3, 6, 8]); // false //******* shorter version! ******// function allEvens(num){ return (num.every( x => x % 2 === 0)) } evens([2, 4, 6, 8]); // true evens([3, 6, 8]); // false
👈 go back or 👆go up to Callback functions & Array Methods
Similiar to every, but returns TRUE if SOME of the array elements pass the test function.
-
eg.:
check if SOME of the goals reach 50. If some of the goals are equal or greater than 50, returns TRUEconst goals = [80, 100, 50, 40, 77]; goals.some(points => points >= 50) // at least 1 element has to be >= 50. // result is TRUE, because 4 out of 5 of the goals is greater than 50.
-
eg.:
let see if any of the houses is built after 1920const buildings = [ { name : "House1", year: 1990 }, { name : "House2", year: 1909 }, { name : "House3", year: 1870 } ]; buildings.some(house => house.year > 1920) // true - because 1 of the elements passes the test
👈 go back or 👆go up to Callback functions & Array Methods
executes a reducer function on each element of the array, resulting in a single value.
So its main goal is to take some array and reduce it down to a single value.
-
eg.:
The accumulator it holds the sum.
The currentValue represents each element from the array[1, 3, 5, 7].reduce((accumulator, currentValue) => { return accumulator + currentValue; }); // first callback: 1 + 3 = 4 // second callack: 4 + 5 = 9 // third callback: 9 + 7 = 16
-
eg.:
figure out the total (not using for.. of, but would be easier)
here accumulator is named as totalconst prices = [1.99, 4.50, 19.99]; const totalPrice = prices.reduce((total, num) => { return total + num }) totalPrice; // 26.479999999999997
-
reduce()
can be use to find maximum or minimum values. -
eg.:
find the min. price of the array
what we are accumulating is the minimum value through the arrayconst prices = [1.99, 4.50, 19.99]; const minPrice = prices.reduce((min, currentNum) => { if (currentNum < min) { return currentNum; } return min; }); minPrice; // 1.99
-
eg.:
find the newest houseconst houses = [ { name : "House1", year: 1990 }, { name : "House2", year: 2013 }, { name : "House3", year: 1870 } ]; const newestHouse = houses.reduce((newHouse, currentHouse) => { if (currentHouse.year > newHouse.year) { return currentHouse; } return newHouse; }); newestHouse; //{name: 'House2', year: 2013}
-
we can specify a starting point for the
accumulator
parameter, by adding a 2nd argument to thecurrent value
.- eg.:
adding a 2nd argument
100
tocurrent
as a starting pointconst evens = [2, 4, 6, 8]; evens.reduce((accumulater, current) => accumulater + current, 100); // 120 - because [2, 4, 6, 8] = 20 + 100
- eg.:
adding a 2nd argument
💡 further reading about reduce
💡 another reading about reduce
👈 go back or 👆go up to Callback functions & Array Methods
this
of an arrow function vs this
of a traditional function.
-
this
in a normal method refers to the object in which it is contained. -
this
in an arrow function refers (be scoped to) only the function in which it is created. -
eg.:
regular function:
person
is the value of thethis.name
when we call theperson.hello()
.const person = { name: "Joe", hello : function() { console.log("Hey", this.name, "hello!") } } person.hello(); // Hey Joe hello!
arrow function:
the value ofthis
has not changed (not set) when we call theperson.hello()
.
this
is set to thewindow
object, so it seems thathello
method is defined on its own, not inside of theperson
object.
That is why, it doesn't matter how we call the function.
So, we are not getting a new value forthis
(because its value is that global value, thewindow
object).const person = { name: "Joe", hello : () => { console.log("Hey", this.name, "hello!") } } person.hello(); //Hey hello!
-
So, if we use
this
inside the arrow function, it will refer to the global environment which is thewindow
. If we put the arrow function inside of a normal function, thethis
will refer to the function's scope. -
eg.:
a traditional function withthis
- returning the first and last nameconst person = { first : "John", last : "Doe", fullName : function () { return `${this.first} ${this.last}` } } person.fullName(); // 'John Doe'
this
with arrow functionconst name = { first : "John", last : "Doe", fullName : () => { return `${this.first} ${this.last}` } } name.fullName(); // 'undefined undefined'
-
In arrow functions, JavaScript sets the
this
lexically.
It means that the arrow function does not create its own execution context but inherits thethis
from the outer function where the arrow function is defined ❗️.
Since an arrow function does not create its own execution context, defining a method using an arrow function will cause an error.- So
this
does not bind to the object’s method or the object itself. - in this example, it means
this
refers to the window object.
- So
-
to define a method we shouldn't use an arrow function (give undefined)!
-
💡 when
this
make sense in an arrow function- eg.:
we add asetTimeout
method in to our person object, under the fullName method
const person = { first : "John", last : "Doe", fullName : () => { return `${this.first} ${this.last}` }, timeName : function() { setTimeout( () => { console.log(this); console.log(this.fullName()) }, 3000) // we waint 3 sec, before condsle.log(this.fullName()) } } person.timeName(); // wait 3 seconds // {first: 'John', last: 'Doe', fullName: ƒ, timeName: ƒ} // undefined undefined
- eg.:
-
the
setTimeout()
is a window object, and that's why it's important to use arrow functions in that case. -
Because if it were a normal function, than
this
inside that normal function would point to the window object, since normal functions use the execution context (in this case, the context is the window), sothis.fullName
returns undefined. -
With an arrow function, the
this
would correctly point to the scope of the outer function (timeName) instead of using the window context of thesetTimeout()
function, andthis.fullName
wouldn't be undefined.
See explanation here at 17:25 (this example is based on that). -
in other words: the outer function (
timeName
) is the block/scope where we're defining thesetTimeout
method, and an arrow function nested in thattimeName
function will inherit its context.
When we use thethis
insidetimeName
, we're referring to theperson
object (the object to the left when the method is called, as inperson.timeName
), so we would apply the same thing to thethis
inside thesetTimeout
as well.
When JavaScript code is interpreted, each function creates an execution context, to scope any variables and arguments inside the block created by that function. So with that, the arrow function is part of the execution context created fortimeName
. -
when we use
this
inside of an arrow function, we're going one level back in terms of scope.
the same example: but we use a regular function, and we have an error
const person = {
first : "John",
last : "Doe",
fullName : () => {
return `${this.first} ${this.last}`
},
timeName : function() {
setTimeout(function() {
console.log(this);
console.log(this.fullName())
}, 3000) // we waint 3 sec, before condsle.log(this.fullName())
}
}
person.timeName(); // wait 3 seconds
// Window {window: Window, self: Window, document: document, name: '', …}
// TypeError: this.fullName is not a function
-
gives an error, because of the execution context: here
setTimeout()
is a method on thewindow
object (window.setTimeout
), andthis
refers to thatwindow
object. -
to conclude:
A method:
is a function directly inside of an object.A function:
is either standalone or inside of a method.Non-Arrow Functions:
Whenthis
is used inside of an object’s method thethis
keyword is bound to the object. Yet when it is inside of a function, either stand alone or within another method, it will always refer to thewindow
/ global object.Arrow Functions
Thethis
keyword inherits the execution context from the outer function where the arrow function is defined, i.e. it gets the same scope as it's parent's scope.
💡 recap for the scoping:
- eg.:
to understand the scoping withthis
const thing1 = { thing2 : { thing3 : () => { console.log(this) } } } thing1.thing2.thing3(); // Window ...
thing2
is a property of thething1
object, and the scope of that property is still thething1
object. When we use the arrow function, it will refer to the parent scope of thething1
object, which is indeed theWindow
object.
- As soon as we're starting to write our code, we create an execution context: it is the process of going through our code line by line (known as the thread of execution), and the memory which we store any variables, functions, the stuff, to get declared. This two (going through the code and the memory) together is an execution context, a space where to execute the code.
- The execution context is the wrapper around your existing code; which contains code that you have not written, but is generated by the JS Engine.
- An Execution Context is created each time you run your .js file/app. The first step in this creation phase is Hoisting. The JS Engine reserves space or set's up memory for all the variables and functions defined in your code. These are then accessed when your code is executed line-by-line.
👈 go back or 👆go up to Callback functions & Array Methods
Default function
parameters allow named parameters to be initialized with default values if no value or undefined is passed.
Basically when we don't use a parameter, we can give it a default value.
-
eg.:
a roll die method withMath.random
times, a number of sidesfunction rollDie(sides) { return Math.floor(Math.random() * sides) + 1 } rollDie(6); // expecting a number from 1 to 6 // 2 // 5 rollDie(); // NaN // we didn`t pass any value, so it's NaN, nothung multiply number is NaN
-
The way to avoid
NaN
when we don't pass value to the function, is we give adefault param
-
we add an
=
sign to the parameters in the function. -eg.:
We setb = 1
, so the default value ofb
is1
function multiply(a, b = 1) { return a * b; } multiply(7); // 7 → 7 * 1 multiply(7, 3); // 21 → 7 * 3
If we leav out the
b
, theb
is set to1
, so its defaul value is1
. -
eg.:
so the first example withdefault param
function rollDie(sides = 6) { return Math.floor(Math.random() * sides) + 1 } rollDie(6); // expecting a number from 1 to 6 // 2 // 5 rollDie(); // sides = 6 // 2 // 4
-
the order is very important ❗️
- eg.:
we pass two parametersThe default parameters should come second or third or, etc. They need to come after any parameters that are not default.function greeting( greet, name ) { console.log(`${greet} ${name}`); } greeting("Hi there!", "John"); // Hi there! John greeting("John"); // I pass only 1 argument // John undefined // it doesn't know the order, John is the 2nd normally
we can use multiple default parametersfunction greeting( name, greet = "Hello!" ) { console.log(`${greet} ${name}`); } greeting("John"); // Hello! John
function greeting(greet = "Hello!", name, question = "How are you?" ) { console.log(`${greet} ${name} ${question}`); } greeting("John"); // Hello! John How are you? greeting("Bonjour", "John", "comment tu vas?"); // Bonjour John comment tu vas?
- eg.:
👈 go back or 👆go up to JS Other Features
Spread syntax looks liek this: ...
.
It allows an iterable such as an array expression or string to be expanded in places where zero or more arguments (for function calls) or elements (for array literals) are expected, or an object expression to be expanded in places where zero or more key-value pairs (for object literals) are expected.
-
The keyword is expanding, so spread (taking something and spreading it out).
-
its 3 dots:
...
-
spread in:
function call
array literlas
object literals
Spreading an iterable (array, string, etc.) into a Function Call (a list of arguments)
-
eg.:
Math.max
andMath.min
static functions expect multiple arguments. They returnNaN
if any parameter isn't a number and can't be converted into a number.Math.min(3, 5, 9, 1); // these numbers are separate arguments // 1 Math.max(3, 5, 9, 1); // these numbers are separate arguments // 9 const nums = [3, 5, 9, 1]; Math.min(nums) // NaN // because we passed a single array full of numbers, not multiple sperate arguments
so we are going to use the spread syntax, and by that, we're separating the
nums
into separate arguments, so the functions can use it.const nums = [3, 5, 9, 1]; Math.max(...nums); // num array spreaded out into separate arguments: 3, 5, 9, 1 // 9
the
...
syntax doesn't changenums
-
eg.:
usingconsole.log
, and printing outnums
. Then using the separate syntax, see the difference: each element fromnums
array passed in as a separate argument to theconsole.log
.const nums = [3, 5, 9, 1]; console.log(nums); // [3, 5, 9, 1] // the array itself is printed console.log(...nums); // 3 5 9 1 // numbers are separated by spaces
-
we can use the spread for any other iterable (which we can use the
for..of
)- eg.:
spread on a string: it spreads each character into individual argumentsconsole.log("HELLO"); // HELLO console.log(..."HELLO"); // it equals to this: console.log("H", "E", "L", "L", "O") ← separate arguments // H E L L O
- eg.:
👈 go back or 👆go up to Spread Syntax
How can we spread an iterable into arrays. Basically spreading an array into an other array.
- eg.:
We have 2 arrays, and we want to contain all the arrays into a variable. Using spread, we create a copy of the arrays.
const cities = ["London", "Paris", "Budapest"];
const countries = ["India", "China"];
const places = [...cities]
// ['London', 'Paris', 'Budapest'] // → a copy of const cities
// to prove places a copy of cities
places.push("Amsterdam");
places;
// ['London', 'Paris', 'Budapest', 'Amsterdam']
cities;
// ["London", "Paris", "Budapest"] // → cities unchanged
combine arrays
const cities = ["London", "Paris", "Budapest"];
const countries = ["India", "China"];
places = [...cities, ...countries];
// ['London', 'Paris', 'Budapest', 'India', 'China']
👈 go back or 👆go up to Spread Syntax
Copies properties from one object into another object literal.
-
like in arrays, we can spread properties from an object into a new object.
-
A lot of the time it is useful to spread into objects, so creating copies of an object. For instance when we are working with with libraries and tools (like React).
-
eg.:
We have 2 objects: cat and dog. We can make a copy of the cat for exampleconst cat = { color : 'black', name : 'Milo'}; const dog = { isBig : true, name: 'Charlie'}; {...cat}; // {color: 'black', name: 'Milo'}
we can add something to that copy
{...cat, type : 'Biman'}; // {color: 'black', name: 'Milo', type: 'Biman'}
we can combine cat and dog into a new variable (pets).
It copies over properties from both, but there is a conflict: both have aname
property.
The order matters:dog
is the last one, so it overrites thecat
property (see thename
, it's from thedog
property).const pets = {...cat, ...dog}; pets; // {color: 'black', name: 'Charlie', isBig: true}
-
eg.:
Try to spread an array into an object. The indices are used as the key.{...[2, 6, 7, 4]}; // {0: 2, 1: 6, 2: 7, 3: 4} // 0 index : 2, 1 index : 6, etc... {...'Bonjour'}; // {0: 'B', 1: 'o', 2: 'n', 3: 'j', 4: 'o', 5: 'u', 6: 'r'}
-
also useful to copy objects, when we have a form (a data), and we want to add something extra data to it.
- eg.:
We have a form, calleduser
, and we cope and add our extra data to it, callednewUser
const user = { email : "somethong@email.com", password : "123456789" }; const newUser = {...user, id : "94576", isAdmin: false}; // the copy of the user + our extra data newUser; // {email: 'somethong@email.com', password: '123456789', id: '94576', isAdmin: false}
- eg.:
👈 go back or 👆go up to JS Other Features
The rest parameter syntax allows a function to accept an indefinite number of arguments as an array...
So it collects all remaining arguments into an acutal array.
-
...
-
collects the rest of the parameters, arguments
-
eg.:
Usingrest
+ only one parameter (num
).
Rest
will collect the rest of the parametersfunction sum(...num){ console.log(num); } sum(10,42,77); // give an REAL ARRAY // [10, 42, 77]
without
rest
, only takes the first numberfunction sum(num){ console.log(num); } sum(10,42,77); // 10
With rest, the
reduce
function will work, becauserest
creates an arrayfunction sum(...num) { return num.reduce((total, num) => total + num) } sum(1, 2, 3); // 6
bonus: about the arguments
object
-
it automatically holds all of the arguments passed into a function.
-
it's an array like object:
- it has a length property
- doesn't have array methods (like push or pop, reduce, etc.)
-
it's available inside every function
-
contains all the arguments passed to the function
-
not available inside of arrow functions!
- eg.:
We have a function, with no arguments. Inside the function we print out thearguments
object. When we give arguments to the function, thearguemtns
object will contain those values, in an order, like an array. So it looks like an array but it's not an array → we can't use array methods.We want to have the sum of the numbers we give tofunction sum() { console.log(arguments); } sum(); // 'arguments' is empty // Arguments [callee: ƒ, Symbol(Symbol.iterator): ƒ] // length: 0 sum(10,42,77); // arguments contains all the values we gave // Arguments(3) [10, 42, 77, callee: ƒ, Symbol(Symbol.iterator): ƒ] // 0: 10 // 1: 42 // 2: 77
arguments
. But we can't use thereduce
method, becausearguments
is not an array.function sum() { return arguments.reduce((total, num) => total + num) } sum(10,42,77); // Uncaught TypeError: arguments.reduce is not a function
- when using
arguments
, we can't chose that name. Using rest, we can use any name for a parameter.
- eg.:
👈 go back or 👆go up to JS Other Features
from Arrays
from Objects
Function Parameter
The destructuring assignment syntax is a JavaScript expression that makes it possible to unpack values from arrays, or properties from objects, into distinct variables.
So, it's a way of unpacking, singling out values from arrays.
Unpacking mean put values into ditinct variables.
A short syntax to unpack:
values from arrays and properties from objects into distinct variables.
-
[]
using brackets -
it's basically copying a value into a variable (not extracting from the array or object)
-
based upon position
-
eg.: arrays of number. We can single out into 2 varbales (firstnum and secondnum) for example the first and the second number.
const numbers = [10,20,30,40,50,60]; const [firstnum, secondnum] = numbers; firstnum; // 10 secondnum; // 20 numbers; // [10, 20, 30, 40, 50, 60]
to single out the rest of the numbers into a variable
const numbers = [10,20,30,40,50,60]; const [firstnum, secondnum, ...restOfTheNumbers] = numbers; firstnum; // 10 secondnum; // 20 restOfTheNumbers; // [30, 40, 50, 60]
👈 go back or 👆go up to JS Other Features
extract values from objects.
-
it is more commonly used (than extracting from arrays).
-
{}
using curlybraces -
the name matters (how we access the value inside of the object)
-
it doesn't change the object
-
eg.: We want to accass data more frequently, so here is a shorter syntax using a destrucring assignment
const user = { firstName : 'John', lastName : 'Doe', email : 'johndoe@email.com', address : '1600 Amphitheatre Parkway' state : 'California' }; const { email, lastName, address} = user; email; // created an email varibale // 'johndoe@email.com' lastName; // created a lastName varibale // 'Doe' address; // '1600 Amphitheatre Parkway'
-
if we want to change the name of the variable where we singled out the object's value
-
eg.:
rename the address to sameAddress
const user = { firstName : 'John', lastName : 'Doe', email : 'johndoe@email.com', address : '1600 Amphitheatre Parkway', state : 'California' }; const {address : sameAddress } = user; newAddress; '1600 Amphitheatre Parkway'
- adding default values
- eg.:
we have a user2, but user2 does not have an email and addressconst user2 = { firstName : 'Jane', lastName : 'Doe', state : 'California' }; const { firstName : janey, email, address } = user2; janey; // 'Jane' email; // undefined adress; // undefined const { email = 'jane@email.com', address = '10 Pl. de la Joliette, 13002 Marseille'} = user2; email; // 'jane@email.com' address; // '10 Pl. de la Joliette, 13002 Marseille'
-
👈 go back or 👆go up to JS Other Features
we can destructure on the way into the function!
- eg.:
We want to destructure the first and last name. We can do it on the fly.
Because we are expecting an object and from that object we want to destructure firstName and lastName, then return the fullName
const user = {
firstName : 'John',
lastName : 'Doe',
state : 'California'
};
function fullName ({firstName, lastName}) {
return `${firstName} ${lastName}`
}
fullName(user);
// 'John Doe'
// it's the same BUT WAY LONGER
function fullName(user) {
const {firstName, lastName} = user;
return `${firstName} ${lastName}`
}
fullName(user);
// 'John Doe'
- eg.:
filter the movies based upon their scoreconst movies = [ { title: 'Amadeus', score: 99, year: 1984 }, { title: 'Sharknado', score: 35, year: 2013 }, { title: '13 Going On 30', score: 70, year: 2004 } ]; // destructuring on the way in movies.filter(({score}) => score > 90); // {title: 'Amadeus', score: 99, year: 1984} // without destructuring movies.filter((film) => film.score > 90); // {title: 'Amadeus', score: 99, year: 1984}
👈 go back or 👆go up to JS Other Features
Document Object Model
A bunch of JavaScript objects that represents the webpage. So the DOM represents all page content as objects that can be modified. The document object is the main “entry point” to the page. We can change or create anything on the page using it.
It's our window, our access portal into the contents of a Web page from JavaScript.
So the DOM is a JS representation of a webpage, an interface to access the actual (i.e. already rendered) elements of an HTML document.
It is the 'window' to the contents of a webpage.
And many objects that we can interact with via JS.
- How does it work?
What is thedocument
getElementById()
getElementsByTagName()
getElementsByClassName()
querySelector()
andquerySelectorAll()
When a browser loads the Web page, the HTML and CSS loads, and then creating a bunch of JS objects based on the style elements (which were loaded with the HTML + CSS).
-
every single element (also the
body
) gets its own JS object! -
The DOM tree:
It is a tree structure:- the HTML elements are connected, there is a relationship, parent-children.
- The backbone of an HTML document are tags.
- According to DOM, every HTML tag is an object! Nested tags are children of the enclosing one. The text inside a tag is an object as well. All these objects are accessible using JavaScript, and we can use them to modify the page.
For instance: a
document.body
is the object representing the<body>
tag.
-
eg.:
the DOM tree
<html>
<head>
<title>About elk</title>
</head>
<body>
The truth about elk.
</body>
</html>
HTML
▾
HEAD
▾
TITLE:
#text About elk
▾
BODY
▾
#text ↵␣␣The truth about elk.↵
// ↵ - sign of a newline
// ␣ - sign of a space
Tags are element nodes and form the tree structure: <html>
is at the root, then <head>
and <body>
are its children, etc.
💡 Node (in the DOM):
all parts of the DOM tree is a "node", but also the text content from those elements and attributes can be "nodes" as well. These individual parts of the document are known as "nodes". So a "node" can be simply an HTML element. The DOM is a tree structure that represents the HTML of the website, and every HTML element is a "node".
💡 The topmost node is the root node (Document Node) of the DOM tree, which has one child, the <html>
.
💡 Elements are one type of node, so to be accurate, we call the Element Nodes. Only element nodes can be parents.
Most common node types:
- Element Node
- Text Node
- Comment Node(s)
- Document Node : the content of the entire HTML, in short it's the page.
- Document Type Node it is the
<!DOCTYPE html>
The document object is the entry point into the DOM.
It contains representations of all the content on a page, and lots of methods and properties.
If we want to reach an element, we can do it through the document
(eg.: document.getElementById()
, etc.).
It is an element on the top of the element tree structure:
it is the top of a folder for everything
it's the root of the tree
-
write
document
on the console and it gives us:- eg.:
the representation of the HTML#document <!DOCTYPE html> <html dir="ltr" ... lang="en" class="md"> <head>...</head> </body>...</body> </html>
- eg.:
-
typing
console.dir(document)
can show the JavaScript object with a bunch of properties.- eg.:
#document location: URL... activeElement: body adoptedStyleSheets: [] alinkColor: "" all: [html, head, meta, meta, title, body, h1, img#banner, p, b, b, a, ... // it contains every element of the page - tags. Each of them is a JS object❗️ . . .
- eg.:
-
so the document object is where everything is contained and starts. Inside we have methods and properties, we can change them to manipulate the document.
(It contains objects that represent the content of a Web page.)
The Document method getElementById
returns an Element object representing the element whose id property matches the specified string.
Simply: when we call this method, we pass in a string and this string has to correspond to an ID
on an element.
-
a method to select one single element (because every ID is one and unique)
-
document.getElementById('idname')
-
it is a method that exists on the document
-
it only occurs once per page!
-
if there is an element found with that ID, it will be returned
-
if the ID can't be found it returns
null
(it only gives us 1 thing, there's no collection of elements) -
when we select something JS fetchs us the object that represents some elements on the page based on the matching ID.
-
it gives us the object representation of that element, which we can then manipulate
-
we are not asking the HTML, we are asking the DOM object (the element object that JavaScript created).
-
eg.:
here's the html<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Dogs</title> </head> <body> <h1 id="mainheading">Woof woof woof</h1> <img src="https://images.unsplash.com/dogs" id="dogg" alt="cute dog"> </body> </html>
to select an image
document.getElementById('dogg') //returns: // <img id="banner" src="https://images.unsplash.com/dogs
we can save it to a variable
const image = document.getElementById('dogg'); console.dir('image'); // call the variable // returns the object which contains a lots of properties // img#banner // alt: "" // currentSrc: "https://images.unsplash.com/dogs" // height: 879 // tagName: "IMG" // ...
The getElementsByTagName
method of Document interface returns an HTMLCollection of elements with the given tag name.
Simply: call getElementsByTagName
, pass in a tag name, and it returns to an HTML collection.
-
a method to select elements
-
document.getElementsByTagName('tagname')
-
when tag name is not found, we have an empty collection (and not
null
) -
an HTML collection:
it represents a generic collection (array-like object similar to arguments) of elements (in document order) and offers methods and properties for selecting from the list.- it is not an array (but array like)
- has length
- iterable
(for..of)
-
an Element:
in JavaScript is that object that we're getting back that has all the properties that represent a single HTML element.
Element is the most general base class from which all element objects (i.e. objects that represent elements) in a Document inherit. It only has methods and properties common to all kinds of elements. More specific classes inherit from Element.
- eg.:
to get the image, we pass theimg
tag
The HTMLJS<html> <head> <title>getElementBy example</title> </head> <body> <img class="myimage" src="https://images.com/photo01.jpg" alt=""> <img class="myimage" src="https://images.com/photo02.jpg" alt=""> <img class="myimage" src="https://images.com/photo03.jpg" alt=""> <p>Some outer text</p> <p>Some outer text</p> </body> </html>
select the 2nd imageconst images = document.getElementsByTagName('img'); images; // returns: // HTMLCollection(3) [img.myimage, img.myimage, img.myimage] // 0: img.myimage // 1: img.myimage // 2: img.myimage
we can iterate itconsole.dir(images[1]); // returns an object: // img.myimage // accessKey: "" // align: "" // ariaAtomic: null // ariaAutoComplete: null // src: "https://images.com/photo02.jpg" // ...
we can change all the images' sourcefor (let img of images) { console.log(img.src) } // returns: // https://images.com/photo01.jpg // https://images.com/photo02.jpg // https://images.com/photo03.jpg
for (let img of images) { img.src = "https://upload.photos.org/images_example.jpg" console.log(img.src) } // all the 3 image became the same: https://upload.photos.org/images_example.jpg image
we are selecting elements by a class.
-
a method to select elements
-
document.getElementsByClassName('classname')
-
when class name is not found, we have an empty collection (and not
null
) -
eg.:
select an image by its class name
The HTML<html> <head> <title>getElementBy example</title> </head> <body> <img class="myimage" src="https://images.com/photo01.jpg" alt=""> <img class="myimage" src="https://images.com/photo02.jpg" alt=""> <img class="myimage" src="https://images.com/photo03.jpg" alt=""> <p>Some outer text</p> <p>Some outer text</p> </body> </html>
JS
const images = document.getElementsByClassName('myimage'); images; // returns: // HTMLCollection(3) [img.myimage, img.myimage, img.myimage] 👈 this is not an array! // 0: img.myimage // 1: img.myimage // 2: img.myimage
we can change all the images' source
for (let img of images) { img.src = "https://upload.photos.org/images_example.jpg" console.log(img.src) } // all the 3 image became the same: https://upload.photos.org/images_example.jpg image
an all-in-one method to select a single element.
-
document.querySelector('h1');
← tagname -
document.querySelector('#id');
← id name -
document.querySelector('.classname');
← class name -
it works with any css style
-
we have to use the
.
or#
before the name('#name')
-
querySelector
just gives us the first match❗️ -
we can chaine on other CSS style selectors
document.querySelector('.image:nth-of-type(2)');
-
eg.:
select an image with the querySelector - using the class The HTML<html> <head> <title>getElementBy example</title> </head> <body> <img class="myimage" src="https://images.com/photo01.jpg" alt=""> <img class="myimage" src="https://images.com/photo02.jpg" alt=""> <img class="myimage" src="https://images.com/photo03.jpg" alt=""> <p>Some outer text</p> <p>Some outer text</p> <a href="#" title="hello">Hello, click me!</a> <a href="#" title="bye">Good bye!</a> </body> </html>
JS
const images = document.querySelector('.myimage'); images; // returns: // <img class="myimage" src="https://images.com/photo01.jpg" alt=""> 👈 shows me the first one
get the second image by chaining a CSS style
const images = document.querySelector(.myimage:nth-of-type(2)); images; // returns: // <img class="myimage" src="https://images.com/photo02.jpg" alt=""> 👈 shows me the second
select an
a
tag with the CSS (title="hello") attribute (in CSS we use[]
to select an attribute)document.querySelector('a[title="hello"]'); // returns: // <a href="#" title="hello">Hello, click me!</a>
same idea as querySelector
but returns a collection of mathcing elements, instead of just the first match.
-
document.querySelectorAll('h1');
← tagname -
document.querySelectorAll('.classname');
← class name -
we can use all th CSS selectors and the way we select is the same as in CSS
-
eg.:
we sleect ALL the<p>
The HTML<html> <head> <title>getElementBy example</title> </head> <body> <img class="myimage" src="https://images.com/photo01.jpg" alt=""> <img class="myimage" src="https://images.com/photo02.jpg" alt=""> <img class="myimage" src="https://images.com/photo03.jpg" alt=""> <p>Some outer text</p> <p>Some outer text</p> <h1><a href="wikipedia.com" title="text01">This is an h1</a></h1> <h2><a href="wikipedia.com" title="text02">This is an h2</a></h2> <p><a href="wikipedia.com" title="hello">Hello, click me!</a></p> <p><a href="wikipedia.com" title="bye">Good bye!</a></p> </body> </html>
JS
document.querySelectorAll('p'); // returns: // NodeList(2) [p, p] // 0: p // 1: p // length: 2
with just simple querySelector
document.querySelector('p'); // returns: // <p>Some outer text</p> 👈 shows me the first one
select all the
<a>
nested inside of a<p>
. The way we select is the same as we do it in CSSdocument.querySelectorAll('p a'); // returns: // NodeList(2) [a, a] // 0: a // 1: a
iterate over and print out every
href
sourceconst links = document.querySelectorAll('p a'); for (link of links) { console.log(link.href) } // returns: // https://wikipedia.com/ // https://wikipedia.com/
DOM manipulation refers to using JavaScript in the middle of HTML to access to move, rename, show and hide things, update styles. So to impact the HTML.
We use DOM manipulation when we want to modify parts of the page when the user interacts with it. Otherwise, if we feel like the initial page style should be modified, then changing the original code is the better approach. So, in general, we use CSS as much as possible, because it is lighter and faster and it is the indicated technology to style a webpage. However, when we want to modify a page after the initial code was rendered to make it dynamic, that is only possible with DOM manipulation.
- innerText
- textContent
- innerHTML
- difference between
innerText
vsinnerHTML
vstextContent
- attributes & properties
The innerText
property of the HTMLElement interface represents the rendered text content of a node and its descendants.
The innerText property returns just the text, without spacing and inner element tags.
Simply, the text that we see as a user showing up between the opening and closing tags.
-
we can modify the inner text of an HTML
-
syntax:
document. + selector + ('element') + .innerText
-
eg.:
retrieve the<p>
text
HTML<h3>Inner Text</h3> <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod.</p> <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod.</p>
JS
document.querySelector('p').innerText; // output: // "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod."
This paragraph element is just a JavaScript object, so we can change that into text value.
So let's change the value of all the<p>
to another text by iterating overconst paragraphs = document.querySelector('p'); for (let par of paragraphs) { par.innerText = "Text has been overwritten!"; } // output - all the <p> tags of the document (see the HTML below): //'Text has been overwritten!'
now see the HTML
<h3>Inner Text</h3> <p>Text has been overwritten!</p> <p>Text has been overwritten!</p>
-
💡 document.querySelectorAll('p') is similar to an array, so to get all the (
<p>
) elements, we can use either indexing or using iteration:-
indexing:
document.querySelectorAll('p')[0].innerText;
-
iteration:
const paragraphs = document.querySelector('p'); for (let par of paragraphs) { par.innerText = "Text has been overwritten!"; }
-
👈 go back or 👆go up to JS DOM manipulation
The textContent
property is very similar to the innerText
property. But it's not the same.
The textContent
property returns every element, every piece of content inside the selected HTML element (shows everything which is in the markup).
But innerText
property doesn't show everything what is in the markup (eg. if we had something by setting something to display: none
, innerText
doesn't show it).
-
So
textContent
is give us everything, whereasinnerText
is actually sensitive to what is showing at the moment. -
textContent
returns every element in thenode
. In contrast,innerText
is aware of styling and won't return the text of "hidden" elements. -
syntax:
document.querySelector('p').textContent
-
eg.:
let's take the<b>
tag which is inside of the<p>
tag. Let's set it todisplay:none
. So the word (<b>consectetur</b>
) is hidden now
But withtextContent
we can still see it
HTML<styles> b { display: none; } </stlyes> ... <h3>Text Content</h3> <p>Lorem ipsum dolor sit amet, <b>consectetur</b> adipiscing elit, sed do eiusmod.</p>
JS
document.querySelector('p').textContent; // 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod.' // 👈 consectetur word is there
but,
innerText
doesn't show the hidden<b>
element's content (consectetur)document.querySelector('p').innerText; // Lorem ipsum dolor sit amet, adipiscing elit, sed do eiusmod. // 👈 consectetur word is missing
👈 go back or 👆go up to JS DOM manipulation
The Element property innerHTML gets or sets the HTML markup contained within the element.
innerHTML
gives us the entirety of the markup contained within some element.
-
so
innerHTML
retrieves the full content, including the tag names, of basically everything inside of an element between the opening and closing tag. -
innerHTML
is the only one we can use to add elements inside of some other element compared toinnerText
andtextContent
. -
eg.:
When we do innerHTML, we get all of the tags included
HTML<h1>My Inner HTML</h1> <p>LLorem ipsum dolor sit amet, consectetur adipiscing elit, labore et dolore magna aliqua. <br>Ut enim ad minim</b> veniam. <span>Excepteur sint occaecat</span> cupidatat non proident, sunt in culpa. </p>
JS
document.querySelector('p').innerHTML; // output: we get all the HTML tags as well❗️ // <p>LLorem ipsum dolor sit amet, consectetur adipiscing // elit, labore et dolore magna aliqua. // <br>Ut enim ad minim</b> veniam. // <span>Excepteur sint occaecat</span> cupidatat non // proident, sunt in culpa. // </p>
- eg.:
innerHTML can give us (just) text
HTMLJS<h1>My Inner HTML</h1> <p>Lorem ipsum dolor sit amet, <b>consectetur</b> adipiscing elit, sed do eiusmod.</p>
document.querySelector('h1').innerHTML; // output: // 'My Inner HTML'
-
💡 why is it useful to see all the HTML tags?
BecauseinnerHTML
is more useful when we are updating the content and we want to change the HTML.- eg.:
we want to put the<h1>
title into italic<i>
.
HTMLJS<h1>My Inner HTML</h1>
document.querySelector('h1').innerHTML = '<i>My Inner HTML</i>` // output: the title is in italic // see the HTML: // <h1> // <i>My Inner HTML</i> // </h1>
- eg.:
-
we can add elements to the element, using
+=
- eg.:
adding a superscript to the title
HTMLJS<h1> <i>My Inner HTML</i> </h1>
document.querySelector('h1').innerHTML += '<sup>Superscript</sup>` // output: in HTML // <h1> // <i>My Inner HTML</i><sup>Superscript</sup> // </h1>
- eg.:
The innerText
property returns just the text, without spacing and inner element tags.
The innerHTML
property returns the text, including all spacing and inner element tags.
The textContent
property returns the text with spacing, but without inner element tags.
-
eg.:
HTML<p id="example"> This element has extra spacing and contains <span>a span element</span>.</p>
JS
// innerText document.getElementById("example").innerText; // innerHTML document.getElementById("example").innerHTML; // textContent document.getElementById("example").textContent; // output: // innerText returns: "This element has extra spacing and contains a span element." // innerHTML returns: " This element has extra spacing and contains <span>a span element</span>." // textContent returns: " This element has extra spacing and contains a span element."
getAttribute
&setAttribute
style
classList
setProperty
.parentElement
.children
next/previousSibling
next/previousElementSibling
Document.createElement
appendChild
append
prepend
insertAdjacentElement
after
andbefore
removeChild
remove
Attribute:
When the browser requests a page, it receives the HTML source code. It then reads (parses) this code and builds the DOM from it. During this process, the HTML attributes of the elements are translated into the corresponding DOM properties.
So the attributes are HTML entities and thay can be used to add specific data to elements of HTML code.
-
Attributes are defined by HTML and are used to customize a tag.
-
When writing HTML source code, we can define attributes on your HTML elements. Then, once the browser parses our code, a corresponding DOM node will be created. This node is an object, and therefore it has properties.
-
An attribute extends an HTML element, changing its behavior or providing metadata.
For instance, this HTML element:<input type="text" value="Name:">
has 2 attributes (type
andvalue
). -
The HTML tags may have attributes. When the browser reads the HTML to create DOM objects for tags, it recognizes standard attributes and creates DOM properties from them.
-
We consider the
#ID
and.class
as attributes, an anchor tag<a>
anhref="#"
aninput
is also . -
When a standard attribute changes, the corresponding property is auto-updated, and vice versa.
Properties:
The DOM is built from the HTML code parsed by the browser. During this process, the HTML attributes of the elements are translated into the corresponding DOM properties and these properties are referred to by JavaScript as properties of an object ❗️.
- Properties are the values associated with a JavaScript object.
- (The object here is the DOM node (element).
- So, in contrast to the attributes, which are defined in HTML, properties belong to the DOM.
- Since DOM is an object in JavaScript, we can get and set properties.
- Once the browser parses the HTML code (this eg.:
<input type="text" value="Name:">
), an HTMLInputElement object will be created, and this object will contain dozens of properties.
For a given DOM node object, properties are the properties of that object, and attributes are the elements of the attributes property of that object.
Attribute vs Property:
the HTML representation of a DOM element has attributes, but when represented as a JavaScript object those attributes appear as object properties.
So, for instance we have a text-box: <input id="the-input" type="text" value="Name:">
. If we want to know what is inside currently in this text-box, we have to read the property. If we want to know what the initial value of that text-box was, we should read the attribute.
- eg.:
A property shows what is currently inside of the text-box (what the user typed in, here it's "John").
The attribue shows what was the initial value (value="Name:")
HTMLJS<input id="the-input" type="text" value="Name:">
theInput.value // returns "John" ← property theInput.getAttribute('value') // returns "Name:" ← attribute
👈 go back or 👆 go to top to Attributes & properties
OR 👆go up to JS DOM manipulation
-
eg.:
selecting an image from a page, it has 2 attributes here, anid
and asrc
HTML<img id="myimage" src="https://images.com/">
JS
selecting for example byid
. The 1st attribute is theid
, 2nd is thesrc
document.querySelector('#myimage'); // <img id="myimage" src="https://images.com/">
we can change the
id
with a property. And thanks to that we can impact theid
attribute. (we cahngemyimage
tomyId
)document.querySelector('#myimage').id; // 'myimage' document.querySelector('#myimage').id = 'myId'; // 'myId'
this method returns the value of a specified attribute on the element.
So when we use getAttribute
, the method is getting the content directly from the HTML itself.
- eg.:
get thehref
with thegetAttribute
method
HTMLJS<a href="/wiki/Asia">This is a link</a>
There is a difference:const link = document.querySelector('a'); link.href // 'file://wiki/Asia' ← value by going to the JS object link.getAttribute('href'); // '/wiki/Asia' ← value directly from the HTML
getAttribute
takes the value (/wiki/Asia) directly from the HTML. Whereas when we access a property directly on an element likelink.href
that is going to the JS object.
So, we get different value
Sets the value of an attribute on the specified element. If the attribute already exists, the value is updated; otherwise a new attribute is added with the specified name and value.
-
we have to give 2 arguments:
Element.setAttribute(name, value);
-
eg.:
change the value of thehref
withsetAttribute
.
HTML<a href="/wiki/Asiea">Example</a>
JS
const link = document.querySelector('a'); // <a href="/wiki/Asiea">Example</a> link.setAttribute('href', 'www.mdn.org'); // <a href="www.mdn.org">Example</a>
-
eg.:
change the<input type="checkbox>
with the type attribute to colorpicker
HTML<input type="checkbox" class="toctogglecheckbox">
JS
const input = document.querySelector('input[type = "text"]') // css attribute selector // <input type="text"> input.type = 'color' // it creates a color picker on the selected input element.
set the
<input type="">
back to textinput.setAttribute('type', 'text') // <input type="text">
👈 go back or 👆 go to top to Attributes & properties
OR 👆go up to JS DOM manipulation
the style
property.
-
sets the
style
properties throught the DOM -
If we select an element and we add
.style
, we can see that this object contains a bunch of properties corresponding to all the individual CSS properties (likecolor
orfont-size
, etc). -
But in JS everything is camelcased: so eg.:
fontSize
(and notfont-size
as in CSS)❗️ -
eg.:
select the<h1>
element, and with the.style
we see it's has a bunch of properties
HTML<h1>I'm a title</h1>
JS
const h1 = document.querySelector('h1'); h1.style; // output: properties that are corresponding to all the individual CSS properties. // ‣CSSStyleDeclaration {accentColor: '', additiveSymbols: '', alignContent: '', alignItems: '', alignSelf: '', …}
The style object does not contain styles from our stylesheets ❗️ It's empty. It contains any inline styles we have assigned.
- eg.:
give a color to the
<h1>
with CSS
CSSselect theh1 { color: red; }
<h1>
and use stlye.color, and we see it's empty JSIt contains only inline styles add inline stlyes (normally not recommended!) HTMLh1.style.color; // ""
JS<h1 style="color: red;">I'm a title</h1>
h1.style.color; // "red" ← now it is set to 'red'
❗️We can use the style
property to change the value with JS ❗️
-
eg.:
JS
change the color of the<h1>
, without using the inline stylingh1.style.color = 'green'; // "green"
make the fontSize larger
h1.style.fontSize = '3em'; // "3em"
-
Try to avoid writing styles in line❗️ so there is a better way to make changes to apply new styles to elements, which is to use a class.
-
We can't get the information about the HTML elments' style with the
.style
property.
But to the current styles of any element we can use thewindow.getComputedStyle(element)
object. It gives the computed style (so once the page is loaded and the browser computed all the styles).
It is not a selector!- eg.:
using thewindow.getComputedStyle
method to get style informations
HTMLJS<h1>I'm a title</h1>
we can access the color by specifing itconst h1 = document.querySelector('h1'); window.getComputedStyle(h1); // add the h1 element without paranthesis // output: an CSS style declaration (not an object) // CSSStyleDeclaration {0: 'accent-color', 1: 'align-content', 2: 'align-items', 3: 'align-self'...
window.getComputedStyle(h1).color; // 'rgb(0, 0, 0)' ← black window.getComputedStyle(h1).fontSize; // '32px' // it gives us a string ❗️ So to change it, we have to convert it to number, then change it (eg.: adding a number, etc). // and then convert it back to string.
- eg.:
So the getComputedStyle
method can access the final properties that are being applied to an element without having to manually access the CSS files and/or JavaScript DOM manipulation files.
It is useful, when it is difficult to check what class/selector was applied when multiple selectors are added to the same element. So with window.getComputedStyle(element)
we can know what 'won' the specificity conflicts.
👈 go back or 👆 go to top to Attributes & properties
OR 👆go up to JS DOM manipulation
it is an easy way to get the current classes on an element, but also to manipulate them, to toggle classes, to remove classes, to add classes.
-
classList
is an object that we can interact with to control the CSS classes on an element and also to retrieve them and to manipulate them. -
as opposed to
.getAttribute('class')
wich just just tells us exactly what the class string currently looks like,classList
has nice methods, does more and easier. -
After
classList
object we hit.
(dot) we can add methods (eg.:h2.classList.add('border')
) -
eg.:
we have an<h2>
element, it has no classes. We have 2 CSS classes (.purple
and.border
)
HTML<h2>Contents</h2>
CSS
.purple { color: #7b07ff; } .border { border: 1px solid red; }
JS
we select the<h2>
and we check whether it has a class or notconst h2 = docuemnt.querySelector('h2'); h2.getAttribute('class') // check if 'h2' has a class // null // it has no class
By using
classList
we can add as many classes to our HTML element as we want
So lets make<h2>
purple AND add a border to it.h2.classList.add('purple') h2.classList.add('border')
HTML
Let's see the HTML which looks like this<h2 class="purple border">Contents</h2>
JS
Also, we can remove classesh2.classList.remove('border') // border removed
With `classList.contains('border') we can test if border class exist on the element (true/false)
h2.classList.contains('border') // false h2.classList.contains('purple') // true
We can toggle classes, with
classList.toggle()
It is useful when we don't know if a certain class is activated or not (so if yes, it removes)h2.classList.toggle('purple') // false ← it exist, so it removes the class // call it again h2.classList.toggle('purple') // true ← it doesn't exist, so it ADDS the class h2.classList.toggle('purple') // false ← it exist, so it removes the class h2.classList.toggle('purple') // true ← it doesn't exist, so it ADDS the class
Many times it is used for 'accordian buttons'
A heavy and problematic way to do without
classList
is the examples below: add a class withsetAttribute
and set that class topurple
h2.setAttribute('class', 'purple') // creates a class = "purple" in the HTML //and <h2>'s content becomes red
HTML
<h2 class="purple">Contents</h2>
if we want to apply another class as well to have a
border
it is difficult
JSh2.setAttribute('class', 'purple') h2.setAttribute('class', 'border') // this overrites the class="purple" to class="border", we lose "purple". // output: "border" // but we want to keep it purple plus add border // we can use string template literals to have more classes 👇 let currentClasses = h2.getAttribute('class') // output: "border" h2.setAttribute('class', `${currentClasss} purple`) //in CSS the classes are separated by space, that's why we use 'string template literal'. // now we have class="purple border"
👈 go back or 👆 go to top to
Attributes & properties
OR 👆go up to JS DOM manipulation
It is a JavaScript method that allows us to set or update the value of a CSS property for a given element.
The method takes 2 arguments:
- the name of the CSS property we want to set or update
- the new value we want to assign to that property.
- eg.:
We can usesetProperty
to set the background color of a div element
*So we're selecting the<div>
element with a class ofmy-div
usingdocument.querySelector
, and then usingsetProperty
to set thebackground-color
property of the div to red.
HTMLJS<div class="my-div"> <p> Hello! </p> </div>
const myDiv = document.querySelector('.my-div'); myDiv.style.setProperty('background-color', 'red');
👈 go back or 👆 go to top to Attributes & properties
OR 👆go up to JS DOM manipulation
.parentElemment
: access a parent element
Every element has only 1 parent (can not have 2) (but we can have multiple children)
element.parentElemment
- eg.:
access the parent element of the<b>
bold text
HTMLJS<p> This is a <b>Bold text</b> in the regular <br>text</b> and some <a>anchor</a> <a>tags</a>. </p>
if we call theconst boldText = document.querySelector('b'); boldText.parentElemment; // ▸ <p></p>
boldText.parentElemment
again, and again, it goes higher, to the grandparent, and then great grandparent parent, etc.Can be useful, when a user click on a button and it changes something on the parent elementboldText.parentElemment; // ▸ <p></p> boldText.parentElemment.parentElemment; // ▸ <body></body> boldText.parentElemment.parentElemment.parentElemment ; // <html lang="en"> // <head></head> // <body></body> // </html>
- eg.:
.children
: it gives the parent elements children
gives an HTML collection (looks liek an array but it's not an array! It is iterable!!!)
We can have multiple children!
parentelement.children
- eg.:
lets see theconst boldText
's children
JSlets select the 1st childconst boldText = document.querySelector('b'); const paragraph = boldText.parentElemment; paragraph.children // HTMLCollection(4) [b, b, a, a ] // ← the order as these children are found in the DOM
get the parent of that first child, just for the sake of the exampleparagraph.children[0]; // <b>Bold text</b> // it's an object, eventhough it shows us in this form
paragraph.children[0].parentElemet; // <p></p>
- eg.:
👈 go back or 👆 go to top to Attributes & properties
OR 👆go up to JS DOM manipulation
they allow us to navigate from one element to an adjacent sibling.
They give us the corresponding DOM Node! Not HTML elements.
-
eg.:
We have 3 images, lets select the 1st<img class="firstImg">
HTML<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p> <img class="firstImg" src="https://upload.wikimedia.org/firstImage.png" alt=""> <img class="secondtImg" src="https://upload.wikimedia.org/secondImage.png" alt="">
JS
thefirst.nextSibling
gives is a DOM nodeconst first = document.querySelector('.firstImg') first.parentElement; // its parent is the: <body></body> first.nextSibling; // #text // ← it gives us a text Node, it is not an HTML element.
they allow us to navigate from one element to an adjacent sibling.
gives us the actual element sibling!
-
eg.:
We have 3 images, lets select the 1st<img class="firstImg">
HTML<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p> <img class="firstImg" src="https://upload.wikimedia.org/firstImage.png" alt=""> <img class="secondtImg" src="https://upload.wikimedia.org/secondImage.png" alt="">
JS
thefirst.nextElementSibling
gives the actual element, an image in this casefirst.nextElementSibling; // <img class="firstImg" src="https://upload.wikimedia.org/firstImage.png" alt="">
first.previousElementSibling
gives the<p>
because that is before the<img>
tag.first.previousElementSibling; // <p></p>
👈 go back or 👆go up to JS DOM manipulation
this method can create a new DOM element.
It creates an empty element.
So, when we create an element after we fill it (update it) with content (it can be a text or image, etc).
-
we pass in the type of element we want to create
-
eg.:
We create an imagedocument.createElement('img'); // <img> // it only makes an empty <img> tag
We have an empty tag, so we can give source to not show an image
const newImg = document.createElement('img'); newImg // it is an object console.dir(newImg); // it is missing it's source (src) // img accessKey: "" align: "" alt: "" ariaAtomic: null ariaAutoComplete: null ariaBusy: null ... etc.
💡 remember: the
console.dir()
is the way to see all the properties of a specified JavaScript object in console by which the developer can easily get the properties of the object.Give a source
newImg.src = 'https://en.wikipedia.org/dog.jpg' // but it's still not on the page → 1 way is to appendChild
We have an image, but it's still not on the page
1 way is to append it withappendChild()
go toappendChild
👇
👈 go back or 👆 go to top to
Attributes & properties
OR 👆go up to JS DOM manipulation -
This method appends a node as the last child of a node.
-
eg.:
(continue from the previous example atDocument.createElement
)
We have to select something (<body>
) to append to my new image (we gave tonewImg
).
JSdocument.body.appendChild(newImg); // it appends 'newImg' (the src) as the last child of the 'body'.
To adjust the size to the other images of the page, we can create the same class as the other images have (here its
class="firstImage"
HTML
<body> <img class="firstImage" src="https://wikipedia.org/dogs.png"> <img class="firstImage" src="https://wikipedia.org/cats.png"> <!-- this is our appended image --> <img src="https://en.wikipedia.org/American_Eskimo_Dog.jpg"> </body>
using
classList.add
JSnewImg.classList.add('firstImage'); // The HTML 👇 // <img src="https://en.wikipedia.org/American_Eskimo_Dog.jpg">
👈 go back or 👆 go to top to Attributes & properties
OR 👆go up to JS DOM manipulation
this method is used to insert Node objects after the last child of the ParentNode.
It allows us to insert more than one thing at a time so we can have two different nodes to insert or multiple different elements we have created.
- eg.:
We call.append
and pass a text "Hello there!" in the<p>
, it just puts it to the end of theHTML
JS<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
HTML// select <p> const p = document.querySelector('p'); p.append('Hello there!');
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Hello there!</p>
- add more than 1 thing with
Element.append()
- eg.:
add 2 pieces of texts
JSHTML// select <p> const p = document.querySelector('p'); p.append('Hello there!', 'Goodbye people!'); // both element is appended → see the html
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Hello there!Goodbye people!</p>
- eg.:
👈 go back or 👆 go to top to Attributes & properties
OR 👆go up to JS DOM manipulation
This method allows us to insert something as the first child of some elements (like at the beginning).
- eg.:
we create a bold<b>
element withcreateElement
, and inside that we add some textappend
...
... then, we want the 'Hello!!!' text in the beginning of the<p>
paragraph
HTMLJS<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
HTML// select <p> const p = document.querySelector('p'); // create <b> const newBold = document.createElement('b'); // add text newBold.append('Hello!!!') // prepend the <b> to the beginning of the <p> ❗️ p.prepend(newBold)
With<p><b>Hello!!!</b>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
.prepend
the element became the first child now, instead of the last.
👈 go back or 👆 go to top to Attributes & properties
OR 👆go up to JS DOM manipulation
this method inserts a given element node at a given position relative to the element it is invoked upon.
-
we have to specify the position:
'beforebegin': Before the targetElement itself.
'afterbegin': Just inside the targetElement, before its first child.
'beforeend': Just inside the targetElement, after its last child.
'afterend': After the targetElement itself. -
syntax:
targetElement.insertAdjacentElement(position, element);
-
eg.:
I want to insert something between an<h1>
and an<img>
image. So after the<h1>
befor the<img>
.
HTML<h1>Title</h1> <img class="firstImage" src="https://wikipedia.org/dog.png">
JS
const h2 = document.createElement('h2'); // <h2></h2> //put some text there h2.append("Hello nice people!"); // <h2>Hello nice people!</h2> // lets put that <h2> after the <h1> // select <h1> const h1 = document.querySelector('h1'); // lets place <h2> after the target element( <h1> ) h1.insertAdjacentElement('afterend', h2);
HTML
<h1>Title</h1> <h2>Hello nice people!</h2> <img class="firstImage" src="https://wikipedia.org/dog.png">
👈 go back or 👆 go to top to
Attributes & properties
OR 👆go up to JS DOM manipulation
this method inserts an element after some other element.
- eg.:
we create an<h3>
and add some text
HTMLJS<h1>Title</h1> <img class="firstImage" src="https://wikipedia.org/dog.png">
HTMLconst h3 = document.createElement('h3'); //add text h3.innerText = 'I am h3!!!'; //select h1 const h1 = document.querySelector('h1'); //insert <h3> after <h1> h1.after(h3);
<h1>Title</h1> <h3>I am h3!!!</h3> <img class="firstImage" src="https://wikipedia.org/dog.png">
this method inserts an element before some other element. Basically works the same as .after
method.
👈 go back or 👆 go to top to Attributes & properties
OR 👆go up to JS DOM manipulation
this method removes a child node from the DOM and returns the removed node.
So the way that it works is that we don't actually remove the particular element we have selected. We remove a child from an element❗️
-
call the method on the parents of what we want to remove (hence the name remove child).
-
syntax:
parent.removeChild(child);
-
has full browser support (Internet Explorer too)
-
eg.:
If we want to remove an<li>
, we can't just select the the<li>
andremoveChild
. Instead, we have to select the parent, the<ul>
, and then call remove child and pass in the<li>
.
HTML<ul> <li>List 1</li> <li>List 2</li> <li>List 3</li> </ul>
JS
const ul = document.querySelector('ul'); const li = document.querySelector('li'); ul.removeChild(li) // ← removes the FIRST <li>
a 'one-go-way', commonly used: call the parent of the child
parentElement
, and then remove the child, all in one line.const li = document.querySelector('li'); li.parentElement.removeChild(li)
👈 go back or 👆 go to top to
Attributes & properties
OR 👆go up to JS DOM manipulation
This method removes the element from the tree it belongs to.
So, we call on the actual thing we want to remove, and we don't have to worry about the parent or the child.
-
syntax:
node.remove()
-
has no full browser support (Internet Explorer does not support it)
-
eg.:
remove the image
HTML<body> <img class="image" src="wikipedia.org/dog.png"> </body>
JS
const img =document.querySelector('img'); img.remove(); // images has been removed
👈 go back or 👆 go to top to
Attributes & properties
OR 👆go up to JS DOM manipulation
creating interactive websites: responding to user events (inputs and actions), so running a code when a user does something.
- inline events
addEventListener
this
as event handler.- Event Objects
keyboardEvent
Event.preventDefault
input.value
Change Event
&Input Event
- Event Bubbling &
stopPropagation
- Event Delegation
👈 go back or 👆 go to top to DOM events
Inline events are bound to an element by their attribute name, which starts with the “on” prefix. Not all event types may be bound to all elements.
-
Event references
includes lots of properties we can use.
💡 especially useful for theaddEventListener
's
When a user clicks on a button something will happen. How to do it? There are 3 approaches:
- write a code into the
<tag>
- write a code in a JS file using a selector and function
- using the
addEventListener
method.
1. way (BAD PRACTICE):
write the code in the tag:
(makes our markup longer, makes it difficult to write and so on, in short: it sucks.)
- eg.:
The user clicks on the button. We add an attribute to the<button>
tag which is namedonclick
.
Between the quotes inonclick=""
we can write the code that we want to run when the user clicks.
HTML + JS<button onclick="alert('You clicked!')">Click me!</button> <!-- Alert pops up with the 'You clicked!' message -->
2. way :
we write the code in our JS file.
We need to have elements that we can select (with querySelector
or other selectors).
-
eg.:
When a user clicks on the button, something will happen. In our JS file we select a button using itsid
.
HTML<button id="myBtn">Click me!</button>
JS
Usingonclick
property, often people use an inline function expression
We are setting a property to be a function❗️
The function is never executed, it is called by theonclick
property when the user clicks.const btn = document.querySelector('#myBtn'); //set the property btn.onclick = function() { console.log("You clicked!") } // when we click on the button, it runs the function // 'You clicked!' // to see if it is set to a function, we write: btn.onclick; // f () { console.log("You clicked!") }
we can add an
onmouseenter
property, when a mouse enters the zone, it calls the function.
Also, we can write a function separately (here theshout()
function), and then pass it into theonmouseenter
property.btn.onclick = function(){ console.log("You clicked!") } function shout(){ console.log("HELLLLOOOO!!!") } btn.onmouseenter = shout;
-
💡When we use these properties, what we supposed to do is set that specific property (eg. the
onclick
property) to a function.
So the value should be a function, it should reference a function.- eg.:
we can click on the<h1>Events</h1>
title
JSif we DON'T set the propertydocument.querySelector('h1').onclick = function () { alert('You have clicked on the title'); }
onlcick
into a function, the code will execute right away.
That is why we have to set the propertyonclick
to a function (as we have done above).// this DOES NOT WORK!!!!!! document.querySelector('h1').onclick = alert('You have clicked on the title');
- eg.:
👈 go back or 👆 go to top to DOM events
3. way :
this method sets up a function that will be called whenever the specified event is delivered to the target.
So: we can pass in any sort of event.
If we want to listen for a click, a doubleclick, etc. we can just pass in that string (like 'click', 'doubleclick', etc.) and add the second argument which is our callback function (the code that we want to run when that event actually occurs).
-
syntax:
something.addEventListener(' what to listen for ', function() { call back - the code what we want to run } )
so in a short way (from MDN):
addEventListener(type, listener);
addEventListener(type, listener, options);
-
💡 useful source for event listings
-
eg.:
usingaddEventListener
on a<button id="button2">Click</button>
JS// select the button const btn = document.querySelector('#button2'); // set addEventListener btn.addEventListener('click', function() { alert("Clicked!") })
-
a great advantage of
addEventListener
is that we can combine lines, and it executes them withouth overwriting them (eg. overwrite the first line when it gets to the second):- eg.:
we have two functions, when we click on a<button>
one prints out 'Hello!' the other 'Goodbye!', and we want them to appear one after the other.
Using theaddEventListener
the 'Hello!' will not be overwritten by 'Goodbye!'. It means that we can have as many callbacks as we want!
JSBut with another way, by approaching property on the object directly (as it is below) we can see that it CAN NOT HAVE MORE THAN ONE. on the same// select button const btn = document.querySelector('button'); // create 2 functions function hello() { console.log('Hello!') } function bye() { console.log('Goodbye!') } // pass the function to the addEventListener btn.addEventListener('click', hello) btn.addEventListener('click', bye) // Hello! // Goodbye!
btn
object. Because the second function will overwrite the first function.btn.onclick = hello; btn.onclick = bye; // Goodbye! // ← we only getting 'Goodbye!'
- eg.:
-
the option parameter
An object that specifies characteristics about the event listener.addEventListener(type, listener, options)
option objects:
- once:
A boolean value indicating that the listener should be invoked at most once after being added. If true, the listener would be automatically removed when invoked. - passive
- signal
- once:
-
eg.:
the first function will run the callback only once, then it removes theeventListener
, but the second one stays as we didn't include theonce
option object
JSconst btn = document.querySelector('button'); function hello() { console.log('Hello!') } function bye() { console.log('Goodbye!') } btn.addEventListener('click', hello, { once : true }) btn.addEventListener('click', bye) // click the button 3 times // 1. 'Hello!' // 1. 'Goodbye!' // 2. 'Goodbye!' // 3. 'Goodbye!'
👈 go back or 👆 go to top to DOM events
when a function is used as an event handler, its this
is set to the element on which the listener is placed.
-
eg.:
We have 3 buttons and 3<h1>
titles, and we change their background when we click on them
HTML<button>Click</button> <button>Click</button> <button>Click</button> <h1>Click me!</h1> <h1>Click me!</h1> <h1>Click me!</h1>
JS
// select all the <button>s const buttons = document.querySelectorAll('button'); // select all the <h1>s const titles = document.querySelectorAll('h1'); // loop over all the <button>s for (let btn of buttons){ btn.addEventListener('click', function(){ btn.style.backgroundColor = 'red'; // button's background turns to red btn.style.color = 'blue'; // button's text turns to blue }) } // loop over all the <h1>s for (let h1 of titles){ h1.addEventListener('click', function(){ h1.style.backgroundColor = 'red'; btn.style.color = 'blue'; }) }
Using
this
we can save lines, and put all the repetitive code lines into a separate function (here:colors()
)`We replace the function in the
...addEventListener('click', function() {...})
with the function we have created separetaly (colors()
) → ...addEventListener('click', colors())`function colors() { this.style.backgroundColor = 'red'; // the 'this' becomes the 'btn' and the 'h1' this.style.color = 'blue'; } for (let btn of buttons){ btn.addEventListener('click', colors(){ } for (h1 btn of titles){ btn.addEventListener('click', colors(){ }
👈 go back or 👆 go to top to DOM events
Event Object Represents is an object that contains information about an event that has occurred.
When an event listener’s event occurs and it calls its associated function, it also passes a single argument to the function (a reference to the event object). The event object contains a number of properties that describe the event that occurred.
The event object is automaitcally passed every single time in to our callback function.
So it is basically an object which contains information about the event.
- eg.:
We create a<button>
click event. The event object is automaitcally passed into our callback function. We can capture it as putting a parameter (name itevent
, but it can be anything) into our callback function.
JSdocument.querySelector('button').addEventListener('click', function (event){ console.log(event); //← event is an object, it contains information about the object ❗️ }) // PointerEvent {isTrusted: true, pointerId: 1, width: 1, height: 1, …} isTrusted: true altKey: false altitudeAngle: 1.5707963267948966 azimuthAngle: 0 bubbles: true button: 0 buttons: 0 cancelBubble: false cancelable: true clientX: 28 // ← realtive to the window (coordinates) clientY: 17 // ← realtive to the window (coordinates) type: "click" // event type …etc
👈 go back or 👆 go to top to DOM events
KeyboardEvent objects describe a user interaction with the keyboard.
Each event describes a single interaction between the user and a key on the keyboard.
The event type (keydown, keypress, or keyup) identifies what kind of keyboard activity occurred.
- keydown
- keypress
- keyup
-
eg.:
The user PRESSes DOWN a button on the keyboard, so typing in the<input type="text" >
, and that creates the 'keydown' event, and it prints "You pushed key down!"
JS// select input const input = document.querySelector('input'); input.addEventListener('keydown', function() { console.log("You pushed key down!"); }) // typing randomly: asdf+enter // ← 'keydown' event occurs // ("You pushed key down!") // ("You pushed key down!") // ("You pushed key down!") // ("You pushed key down!") // …etc
Now we use 'key up'. The user press a button on the keyboard and the release it → UP.
input.addEventListener('keyup', function() { console.log("It is a key up!"); }) // 3 times we release the keys // "It is a key up!" // "It is a key up!" // "It is a key up!"
What key is pressed?
To find it out, we use the event
object in the call back function. The keyboardEvent
object has several objects, here we are looking for 2 specific: the 'key' and the 'code'. Using them we can knwo what buttons were used on the keyboard.
- eg.:
If we want to know what key was pressed on the<input>
, we look into the keyboardEvent object
JS
// select 'input'
const input = document.querySelector('input');
input.addEventListener('keydown', function(event) {
console.log(event);
})
// prints out he keydown event object. We pressed "a" button.
// KeyboardEvent {...}
// "KeyA", ....
// ...
// code: "KeyA" // ← the "location" on the keyboard
// detail: 0
// eventPhase: 0
// isComposing: false
// key: "a" // ← the character that was generated
// keyCode: 65
If we want to listen for the global keyboardEvent, we ca nuse window
.
We want to jnow what key is pressed, what the key
object: we add the code
after the event
.
window.addEventListener('keydown', function (event) {
console.log(event.code); // ← it will show the 'code'
})
// we just type on the window somewhere (up, right, left arrows)
// ArrowUp
// ArrowRight
// ArrowLeft
If we want to ignore all keyboard buttons, excepet the arrow button (lets say we make a game), we use the switch statement
window.addEventListener('keydown', function (event) {
switch(event.code){
case 'ArrowUp':
console.log("you pressed UP!");
break;
case 'ArrowLeft':
console.log("you pressed LEFT!");
break;
default:
console.log("Ignored");
}
})
// pressing keyboardbuttons calls the 'default. Pressing the 'up arrow' then 'left arrow':
// "Ignored"
// "Ignored"
// "you pressed UP!"
// "you pressed LEFT!"
👈 go back or 👆 go to top to DOM events
This method cancels the event if it is cancelable, meaning that the default action that belongs to the event will not occur.
(Like clicking on a "Submit" button, prevent it from submitting a form)
-
eg.:
When we click to "Submit" in the<form>
, it leads us to another page:.../local?
Let's say we don't want to lead us to another page
So how does it work? In HTML, when the
action
attribute is set to something (here "local") that is where the data (what we put in the<input>
) will be sent to. And our browser window end up at that location as default behaviour HTML<form action="/local" id="localID"> <input type="text"> <button>Submit</button> </form> <!-- we click on the "Submit" → it goes to another page ".../local?" -->
Let's have a form that a user can submit, but we basically prevent it from changing the page, using the
event.preventDefault
method.
So we stay on the same page and we do something with the form data.
JS// selecting the <form> id const form = document.querySelector('#localID'); // adding eventListener when the form is submitted form.addEventListener('submit', function(event){ event.preventDefault() // ← prevents the default behavior triggered by a given event console.log("Form is submitted!"); }) // result: // the event is fired, the page doesn't go to ".../local?", the page stays. // "Form is submitted!"
-
How can we extract data from the
<form>
? With theinput.value
attribute❗️
👈 go back or 👆 go to top to DOM events
The value attribute for element in HTML is used to specify the initial value of the input element.
- (continued from the previous exmaples eg.:
We want to make a list with the words we write in the<input>
. So when we hit the 'submit' button we want to take that value extracted from the<form>
and appear in a<ul>
list on the page.
So we want to append different items to the<ul>
list.
Our goal is that when we click the 'submit', we want to take that input value and then make a new<li>
. Then clear, reset the<input>
.
HTMLWe select the<!-- we add a <ul> under the <form> where the submitted value will appear --> <form action="/local" id="localID"> <input type="text" id="itemInput" name="itemName"> <button>Submit</button> </form> <h2>List of items</h2> <ul id="list"></ul>
<input>
's value. We useinput.value
, and thevalue
attribute shows the value of the<input>
, shows what is currently in the<input>
.
❗️ If we write in the consoleinput.value
, it shows what is currently in the<input>
.
❗️ When input is empty →input.value = ""
JSNow, we have to make a new// selecting the <form> id const form = document.querySelector('#localID'); // selecting the input const input = document.querySelector('#itemInput'); form.addEventListener('submit', function(event){ event.preventDefault() console.log("Form is submitted!"); console.log(input.value); // ← it shows what is currently in the input in the console (not on the page!) })
<li>
with the value what's in the<input>
.
JSNow, we have to reset the input form so it goes back to empty:// selecting the <form> id const form = document.querySelector('#localID'); // selecting the input 💡 there are 2 ways to select the input. 1.'#id', 2.'name' element const input = document.querySelector('#itemInput') // ← 1. selecting by '#id' // const input = localID.elements.itemName; // ← 2. selecting by 'name': (form's "#id") + (elements) + (input's "name") // selecting the <ul id="list"> const list = document.querySelector('#list'); form.addEventListener('submit', function(event){ event.preventDefault() console.log("Form is submitted!"); // console.log(input.value); const itemType = input.value; //← what's in the <input> we want to save it to a variable const newLI = document.createElement("LI"); // ← creating a new empty `<li>` newLI.innerText = itemType; // ← add the itemType to the empty `<li>` (newLI) list.append(newLI); // ← we have to show the 'item' on the page, we have to append it to the 'const list' (the <ul>) })
input.value = ''
JSThe process we did:form.addEventListener('submit', function(event){ // 1. event.preventDefault() console.log("Form is submitted!"); const itemType = input.value; // 2. const newLI = document.createElement("LI"); // 3. newLI.innerText = itemType; // 4. list.append(newLI); // 5. input.value = "" // 6. reset the input form })
1. We prevented the default behavior so that the form doesn't go to another page.
2. Then we take the value from the input.
3. Then we extracted the value and then we made an empty<li>
.
4. Then we added the value into the empty<li>
.
5. Then we append that to our<ul>
.
6. Then we reset the<input>
form to empty.
- another eg.:
<form>
element contains 2<input>
elements, 1 for quantity and 1 for product. We want to add the product and qulaity next to each other, into a new<li>
after each time we submit.
HTMLJS<h1>Grocery List</h1> <form action="/nowhere"> <label for="item">Enter A Product</label> <input type="text" id="product" name="product"> <label for="item">Enter A Quantity</label> <input type="number" id="qty" name="qty"> <button>Submit</button> </form> <ul id="list"></ul>
const form = document.querySelector('form'); // selecting the <form> const product = document.querySelector('#product'); // selecting the product <input> const qty = document.querySelector('#qty'); // selecting the qunatity <input> const list = document.querySelector('#list'); // selecting the <ul> list form.addEventListener('submit', function(event){ event.preventDefault(); // prevent form const productName = product.value; // we take the value from the product input const qtyName = qty.value; // we take the value from the qunatity input const newList = document.createElement('li'); // we create a new empty <li> newList.innerText = `${productName}, ${qtyName}`; // we using template literals to put product and qty next to eachother + we add them to the <li> list.append(newList); // we append the <li> (newList) to our <ul> (#list) product.value= ""; // we reset the product input qty.value= ""; // we reset the quantity input });
👈 go back or 👆 go to top to DOM events
The change event is fired for <input>
, <select>
, and <textarea>
elements when an alteration to the element's value is committed by the user.
Unlike the input event, the change event is not necessarily fired for each alteration to an element's value.
-
eg.:
An<h1>
and an<input>
. Let's say we want to do something every time this input is updated.
The change input only fires when we actually "blur" the input, which means leaving it the input, not clicking in it, when we click away from the input.
HTML<h1>Type something below: </h1> <input type="text">
JS
We type soemthing into the<input>
nothing happens. We run the code, and it only prints out the "Change event has occured" text when we click away from the input.// selecting the <input> const input = document.querySelector('input'); // listen for the change event❗️ input.adEventListener('change', function(event){ console.log("Change event has occured"); })
Changing the
<input>
means changing the value (what we type in into the<input>
). Change event only runs when we blur out (not when we type in the<input>
).💡 So thinking of it whatever you leave in that
<input>
, and it is different than it was when you entered the<input>
. So not whatever you see changing in that<input>
.💡 The
change
event is very useful when we use a<select>
(HTML Select element).
👈 go back or 👆 go to top to DOM events
The input event fires when the value of an <input>
, <select>
, or <textarea>
element has been changed.
(Clicking away and or in doesn't matter.)
The input event
fires as soon as we type something in the <input>
.
-
eg.:
When we change the value in the<input>
, that fires theevent
.
HTML<h1>Type something below: </h1> <input type="text">
JS
We type soemthing into the<input>
and it fires the event, it prints out the "Input event event has occured".// selecting the <input> const input = document.querySelector('input'); // listen for the input event❗️ input.adEventListener('input', function(event){ console.log("Input event has occured"); })
-
another eg.:
Updating<h1>
with the value what we type into the<input>
HTML<h1>Type something below: </h1> <input type="text">
JS
We have input in a variable and we update the<h1>
, whatever thevalue
is (input.value
).
So whenever the value changes inside of the<input>
(input event) we take that value (input.value
) and update the<h1>
to display it (innerText
).// selecting the `<input>` const input = document.querySelector('input'); // selecting the <h1> const h1 = document.querySelector('h1'); // listen for the input event❗️ input.adEventListener('input', function(event){ h1.innerText = input.value; })
We type "HELLO" into the
<input>
and the<h1>
textt becomes "HELLO" as well.
HTML<h1>HELLO</h1> <!-- we type "Hello" --> <input type="text">
👈 go back or 👆 go to top to DOM events
When an event is triggered first on an element, and that element has a parent element, which also has a parent element, etc, and they all have events, all these event are triggered.
We describe this by saying that the event bubbles up from the innermost element that was clicked. (So the bubbling start from the buttom towards the top.)
- eg.:
We have a<button>
inside of a<p>
paragpraph, which is inside of a<section>
. All the elements have anonclick
event.
HTMLWhen we click on the<section onclick="alert('Section clicked!')"> <p onclick="alert('Paragpraph clicked!')"> <button onclick="alert('Button clicked!')">Click!</button> </p> </section>
<button>
, it creates bubbling, because<button>
triggers the<p>
, and then the<section>
.
a method that stops the bubbling
-
syntax:
event.stopPropagation();
-
eg.:
We have a<button>
inside a<div>
. When we click on the<button>
it should change the background color to blue. When we click on the<div>
it hides the<div>
(so hides also the background color and<button>
)
HTML<div id="container"> Please Hide me! <button id="changeColor">Change Color</button> </div>
We include a CSS class
.hide
which has adisplay: none;
. We use this with theclassList
property in the JS file.
CSS.hide { display: none; }
JS
When we click on the<div>
element (which has the text: "Please Hide me!") it hides the<div>
and its child<button>
. Since the<button>
is inside of a<div>
, when we click on the<button>
it triggers the<div>
instead of changing the background color, it hides all the elements → bubbling.
To stop that bubbling, we include theevent.stopPropagation()
const button = document.querySelector('#changeColor'); const container = document.querySelector('#container'); button.addEventListener('click', function (event) { container.style.backgroundColor = "blue"; // blue background event.stopPropagation(); // stop the bubbling }) container.addEventListener('click', function () { // we get the current class (.hide) on the <div> element container.classList.add('hide'); })
So what happens is that when we click on the
<button>
the last line of its function is theevent.stopPropagation();
which stops the bubbling.
👈 go back or 👆 go to top to DOM events
It adds the event listeners to the parent elements.
So instead of attaching the event listeners directly to the child element(s) (eg. a <button>
), you delegate the listening to the parent element (eg. <div>
).
When a <button>
is clicked, the listener of the parent element catches the bubbling event.
The event delegation is a useful pattern because you can listen for events on multiple elements using one event handler.
- eg.:
Let's say we can add comments by writing a text into an<input>
. Every comment gets a new<li>
elemtnt (that is how we'll see on the page)
By clicking them, we want to remove them (using theelement.remove()
method)
HTML
<form action="/local" id="localID">
<input type="text" id="itemInput" name="itemName">
<button>Submit</button>
</form>
<h2>List of items</h2>
<ul id="list"></ul>
JS
// selecting the <form>
const form = document.querySelector('#localID');
// selecting the <input>
const input = document.querySelector('#itemInput');
// selecting the <input> with its ID
const input = document.querySelector('#itemInput')
// selecting the <ul id="list">
const list = document.querySelector('#list');
form.addEventListener('submit', function(event){
event.preventDefault()
const item = input.value; //← what's in the <input> we want to save it to a variable
const newLI = document.createElement("LI"); // ← creating a new empty `<li>`
newLI.innerText = itemType; // ← add the itemType to the empty `<li>` (newLI)
list.append(newLI); // ← we append <li> to the 'const list' (the <ul>) to show on the page
input.value = "" // reset the input form
})
EVENT DELEGATION (we use the parent:<ul>
, to select the <li>
By clicking on the <li>
, which is just created, we can delete that <li>
. So to do this we select the <li>
's parent the <ul>
.
Since each <li>
is under the <ul>
, we can 'click' on the <li>
and it will work. But we have to specify which <li>
we are clicking on.
To tell the borwser which <li>
we 'click', we have to use the 'target' property (part of the 'event' object).
The 'target' property shows which <li>
we are clicking on
// select <ul>
const list = document.querySelector('#list');
// listening for the 'click' on the <ul>.
list.addEvenetListener('click', function(event){
// we have to make sure that we click on is an <li> type
// then we remove that <li> type
if(event.target.nodeName === 'LI') {
event.target.remove() // the thing which we clicked on + remove it
}
})
💡 the event.target.nodeName
: the way we can get this is by writing the console.dir(event.target)
. Then we can see in the console, all the properties of the <li>
object. Among these properties, we can see the nodeName
.
👈 go back or 👆 go to top to DOM events
Synchronous JS:
JS is a single-threaded programming language which means only one thing can happen at a time. That is, the JS engine can only process one statement at a time in a single thread.
This is why we can’t perform long operations (such as network access) without blocking the main thread.
Asynchronous JS:
using asynchronous JavaScript (such as callbacks, promises, and async/await), we can perform long network requests without blocking the main thread.
- Call Stack
- Single Threaded
- Web API
- Callback hell
- Promise
- The
aync
keyword - The
await
keyword try..catch
statement and error handling
is a mechanism for JS interpreter (in a browser) to keep track of its place (to know where it is) in a script that calls multiple functions.
So, this is how JavaScript knows where it is (like a bookmarker): what function is currently being run and what functions are called from within that function, etc.
In other words, JS keeps track of the history of things that are waiting to be returned, so which functions have been invoked but are not done yet. Call Stack is the structure which stores that (data structure).
Stack:
is a basic data structure in computer science.
LIFO: last in, first out
(the last thing we added to the top, and that's the first thing that we remove)
How the call stack works?
-
When a script calls a function (invoke a function), the interpreter adds it to the call stack and then starts doing (carrying out) the function.
-
Any functions that are called by that function are also added to the call stack further up, and run where their calls are reached.
-
When the current function is returned, the interpreter takes it off the stack and it continues the execution where it left off in the last code listing.
So the most recently invoked function is on the top of the stack.
The bottom of the stack is the first function invoked.
The stack is processed from top to bottom.
-
eg.:
We are calling thesecond()
(on line 9). It calls thefunction second()
on line 5, on line 6 the return doesn't return right away, because it has to callfunction first()
on line 1, which returns the string "First" on line 2. Then it popps off, goes to line 6, return the string, popps off, and at line 10 we have the function invocations's result.
The stack is the following:
Starts at line 9, then line 6 (the return) then line 2. Line 2 returns so it's popped of because the top value of the stack is removed. Then it goes back at line 6, we return the string and then it's popped of and then we are at line 10.
the last thing which is added to the top (function first()
) is the first thing to come off → LIFO1. function first() { 2. return "First" 3. } 4. 5. function second() { 6. return first() + "Second!"; 7. } 8. 9. second(); 10. // "First Second!"
-
eg.:
We have a function that calls another function.
function isRightTriangle(a,b,c)
can't give us an answer, it has to callfunction square(x)
, and this has to call thefunction multiply(x,y)
Thenfunction multiply(x,y)
gives a value and this function get removed from the stack, the value goes tofunction square(x)
then the function removed from the stack, and finally the velue 9 goes toisRightTriangle(9,b,c)
.
And it starts over again.function multiply(x,y) { // ← 1. x=3 * y=3 | 2. x=4 * y=4 | 3. x=5 * y=5 return x * y; // (Note: x and y can be called anything else, like x and x, a & b, c & c, etc.) } function square(x) { // first its x=3 | x=4 | x=5 return multiply(x,x); // ← then 1. returns the value from the multiply(x, y) x=9 | 2. x=16 | 3. x=25 } function isRightTriangle(a,b,c) { // 9 , 16 , 25 return square(a) + square(b) === square(c); } isRightTriangle(3,4,5) // true // because 9 + 16 = 25 ← square(3) + square(4) === square(5)
So how does the stack work?
isRightTriangle(3,4,5)
square(a) + square(b) === square(c);
the 1st thing that has to be evaluated issquare(a)
, which issqaure(3)
. That's added onto the call stack.- call stack:
sqaure(x)
return multiply(x,x);
, where x = 3.
This then callsmultiply(x,y)
(where x = 3 and y = 3). multiply(3,3)
3 * 3
Returns:9
, and there is no new function call, somultiply(3,3)
is removed from the call stack.
So now, it goes back with thea
's value9
:square(a)
has a value9
(not3
anymore) sosquare(a)
is able to return9
as well.
square(9)
is removed from the call stack.isRightTriangle(9,b,c)
square(9) + square(b) === square(c);
Then it moves on, tosquare(b)
and thensquare(c)
.
Then finally it can do the mathsquare(9) + square(16) === square(25);
JavaScript adds the function calls to the call stack and then it removes them whenever a value is returned.
(check it out here)
💡 How to debug?
Use Chrome's Dev Tool to debug. Open the Dev tool and open the Sources panel, and there on Pages, and select the page (here app.js) we want to debug.
Here we can click on the code's line number (on the left), which adds a Breakpoint. This breakpoint stops our code at that line where we clicked.
Now line by line we can go throught the code, and read the code
and the breakpoint
(on the right panel).
👈 go back or 👆 go up to Asynchronous JS
A thread is basically a single process that a program can use to complete tasks. Each thread can only do a single task at once❗️
Single thread means that there is one thing (one line of code) in JavaScript that can run on a process at any given point. It cannot do multitasking, doing things simultaneously.
Even though JavaScript can only run 1 line at a time, that could slow down things → bad user experiences. But there are ways around this issue:
-
setTimeout()
: running a code after a delay-
eg.:
How that code works if JS is singlethreaded?
The execution of the lines should run from the first line to the last. But here the (setTimeout()
) prints out the last. Because JS hands over this task to the browser. That is a web API function ❗️console.log("I am the first line") setTimeout(function() { console.log("I am the second line") }, 3000) console.log("I am the third line") // I am the first line // I am the third line // I am the second line
The browser is doing the work. Browsers are not written in JS, so they can do certain tasks that JS can't. That is how eg.
setTimeout()
is printed on the last line.
-
-
Browseres come with Web APIs that are able to handle certain tasks in the background (like making request or
setTimeout
) -
The JS call stack recognizes these Web API functions and passes off to the browser to take care of
-
Once the browser finishes those tasks, they return and are pushed onto the stack as a callback.
👈 go back or 👆 go up to Asynchronous JS
web APIs are generally methods that we can call from JS and they are handed off to the browser.
(eg.: setTimeout()
, JS can't do it, the browser does it.)
- eg.:
JS passsetTimeout()
off to the Web APIs, to the browser. Because it is a web API function
After the 3 seconds, the JS goes back and runs the next thing the 2nd line which is (console.log("3rd")
).
After 3 seconds,setTimeout()
is printed out.The concept of the callback: passing a function in that is not executed right away, but executed later.console.log("1st") setTimeout(function() { console.log("2nd") }, 3000) console.log("3rd") // 1st // 3rd // 2nd
💡 The setTimeout()
adds to the call stack immediately, the callback passed to the setTimeout()
will be added after the delay.
💡 More about the setTimeout
in relation with the Call Stack
👈 go back or 👆 go up to Asynchronous JS
Because we have to call callbacks inside callbacks, we get a deeply nested functions, which is much harder to read and debug. This is sometimes called "callback hell" or the "pyramid of doom" (because the indentation looks like a pyramid on its side).
-
eg.:
Background-color changing program
We have asetTimeout
, which after 2 sec changes the background to red. In thatsetTimeout
we have anothersetTimeout
which after 2 sec changes the background to orange. In thatsetTimeout
we have another which after 2 sec changes the background to yellow, and anothersetTimeout
which after 2 sec changes the background to green, etc.setTimeout( () => { document.body.style.backgroundColor = 'red'; setTimeout( () => { document.body.style.backgroundColor = 'orange'; setTimeout( () => { document.body.style.backgroundColor = 'yellow'; setTimeout( () => { document.body.style.backgroundColor = 'green'; }, 2000) }, 2000) }, 2000) }, 2000)
We want to re-use this code, maybe we want to change the delays... To avoid writing again this long code, we set it to a function.
In that
colorSwitch
function, we have to pass in the color we want ot change to and pass in the delay (so we can change the time), and pass in a call back wich tells the function what to do next (lets name itnext
).With this technique we can easily add nested callbacks inside of the setTimeout, and it permets us to easily set the callback, like set them to the same delay time. It's much clearer.
const colorSwitch = (setColor, delay, next) => { setTimeout(()=> { document.body.style.backgroundColor = setColor; next && next(); }, delay) } // calling the function // "next" is after the delay time, as an anonymus function colorSwitch('blue', 3000, function() { colorSwitch('black', 2000, function() { }) })
💡 the
next && next
:It checks if the variable
next
does exist (has a value assigned to it). AND (&&) checks if invoking that variable (here that function) returns a truthy value. If both of those checks givestrue
, then proceed with the statement.If we remove the
next && next()
part the code will work, but it will break when it will reach the end.
👈 go back or 👆 go up to Asynchronous JS
Promises are objects that represent the eventual completion, the eventual success or failure of some operation (some async operation). So a promise is a returned object to which you attach callbacks, instead of passing callbacks into a function.
- promises keep the nested branching path, but they make it nicer.
👇 this topic's parts:
Here's an example, when our code is NOT nice, deeply nested → complicated:
-
eg.:
Here we have arequest
function with 3 properties. First is aURL
request (hellowebsite.com/page1), the other 2 are callbacks, 1 forsucces
and 1 forfailure
.
This 2 callbacks: 1 for when the request works, 1 when it doesn't.
In case ofhellowebsite.com/page1
, we need to nest another function forhellowebsite.com/page2
, if 'page2' works or not, and forpage3
, and so on.This example is based on a random number (
Math.random
): it imitates adelay
which is between 500 ms - 4000 ms (if it's more than 4000 ms it's a failure, but less than 4000ms is a success).const request = (url, success, failure) => { const delay = Math.floor(Math.random() * 4500) + 500; setTimeout(() => { if (delay > 4000) { failure('Connection timeout') // it is a function call } else { success(`Here is your URL: ${url}`) // it is a function call } }, delay) } // calling the function. 3 arguments, the last 2 are callBack functions. // The last 2 arguments refer to the failure and success functions // so it looks like this: request('URL', function(response){…2nd request…}, function(error){…message…} ) request('hellowebsite.com/page1', function(response) { // response refers to request = (url,success,failure) => { }... console.log(response); }, request('hellowebsite.com/page2', // this is the second request function(response){ console.log(response); }, function(error){ console.log(error); }), function(error) { // error refers to request = (..,.., failure) => { }... console.log(error); // if at any point failes it stops }) // possible results: // Here is your URL: hellowebsite.com/page2 // Here is your URL: hellowebsite.com/page1 // or: // Here is your URL: hellowebsite.com/page1 // Connection timeout // or: // Connection timeout
That is when we use promise
:
-
promise
is just an object:
it is the eventual guarantee of either a value or an error.
Promises represent this asynchronous value that eventually will be resolved or rejected. -
promise
s have 3 possible mutually exclusive states:- pending: initial state, neither fulfilled nor rejected. (at the beginning) Waiting for something.
- fulfilled: meaning that the operation was completed successfully. (= success)
promise.then(f)
- rejected: meaning that the operation failed. (= failure)
promise.then(undefined, r)
We say that a
promise
is settled if it is not pending, i.e. if it is either fulfilled or rejected. (Being settled is not a state, just a linguistic convenience.) -
promise
s have 2 possible mutually exclusive fates:- resolved: if the
promise
is trying to resolve or reject has no effect:
i.e. the promise has been "locked in" to either follow another promise, or has been fulfilled or rejected. - unresolved: if the
promise
is not resolved:
i.e. if trying to resolve or reject it will have an impact on the promise.
- resolved: if the
-
we can run a code when a
promise
is rejected or when it is fulfilled. -
the way
promise
works:
we are attaching callbacks to the object (to thepromise
itself), rather than passing callbacks into a function.
We wait the function to return a promise object. (We get this object →Promise
{<fulfilled>}
[[Prototype]]: Promise)...
) -
eg.:
Here we are imitating a request, which sometimes works, sometimes doesn't. Inside therequestPromise
function we have 2 callbacks. 1 for when the request fulfilled, 1 when it's rejected.Here the
requestPromise
function doesn't expect any callbacks to pass in (as properties), we only pass the(url)
property.const requestPromise = (url) => { return new Promise((resolve, reject) => { const delay = Math.floor(Math.random() * (4500)) + 500; setTimeout(() => { if (delay > 4000) { reject('Connection Timeout :(') } else { resolve(`Here is your URL: ${url}`) } }, delay) }) } requestPromise('something'); // Promise {<pending>} [[Prototype]]: Promise [[PromiseState]]: "fulfilled" ... const hello = request('hello'); hello; // Promise {<fulfilled>: 'Here is your URL: '} // [[Prototype]]: Promise // [[PromiseState]]: "fulfilled" // [[PromiseResult]]: "Here is your URL: "
👈 go back or 👆 go to beginning of Promises or 👆 go up to Asynchronous JS
The then()
method returns a Promise.
It takes up to two arguments: callback functions for the success and failure cases of the Promise.
- eg.:
The requestPromise functionIf theconst requestPromise = (url) => { return new Promise((resolve, reject) => { const delay = Math.floor(Math.random() * (4500)) + 500; setTimeout(() => { if (delay > 4000) { reject('Connection Timeout :(') } else { resolve(`Here is your URL: ${url}`) } }, delay) }) }
promise
is fulfilled, we attach.then()
and we pass a callback in.then(callback () => ...)
// calling 'requestPromise()' : we pass an actual URL site requestPromise('website.com/api/page1'); //← gives an object // save it to a variable const request = requestPromise('website.com/api/page1'); // if the 'promise' is fulfilled, we pass a callback request.then( function(){ console.log("👍 worked"); }) // 👍 worked // check that the promise is fulfilled request; // [[Prototype]]: Promise // [[PromiseState]]: "fulfilled" // [[PromiseResult]]: "Here is your URL: website.com/api/page1"
The catch()
method returns a Promise and deals with rejected cases only.
-
eg.:
(following the previous example)
We can run code also, when thepromise
is rejected, usingcatch()
. We can pass a callback in tocatch()
.If the promise is fulfilled it runs:
.then(()=>{})
❗️
If the promise is rejected it runs:.catch(()=>{})
❗️// it tells us that the promise is rejected requestPromise; // 1 Uncaught (in promise) Connection Timeout :( // Promise.then (async) // (anonymous) requestPromise //← request here is an object .then( function(){ //← .then() is a method on the object console.log("👍 worked"); }) .catch( function() { //← .catch() is a method on the object console.log("👎 error"); })
-
we can chain the methods (so we don't have to save the function into a variable)
- eg.:
following the previous example(s) chaining.then()
and.catch()
Here we are not passing therequestPromise('website.com/api/page1') //← returns a promise object .then( function(){ console.log("👍 worked"); requestPromise('website.com/api/page2') //← nest a '/page2' }) .then( function(){ //← '/page2' works console.log("👍 worked 2"); }) .catch( function(){ //← '/page2' doesn't work onsole.log("👎 error 2"); }) .catch( function() { console.log("👎 error"); }) // if it is fulfilled '/page1' and '/page2' // 👍 worked // 👍 worked 2
requestPromise(function(){})
function into therequestPromise(function(){}) function
, instead we are calling it.then()
and.catch()
methods on the returned promise object.
- eg.:
- So what is the point of using
promise
?
Instead of nesting, we can chain events on ❗️ (So there is no need to nest → avoiding the callback hell)
- eg.:
We return a promise from within the.then(callback)
. That allows us to chain things together.
Note that, we are passing adata
in the function, because in real, a promise can be rejected and resolved with a value passed to it.We have only oneconst requestPromise = (url) => { return new Promise((resolve, reject) => { const delay = Math.floor(Math.random() * (4500)) + 500; setTimeout(() => { if (delay > 4000) { reject('Connection Timeout :(') } else { resolve(`Here is your URL: ${url}`) } }, delay) }) } requestPromise('website.com/api/page1') //← returns a promise object .then( function(data){ console.log(data) // ← data = `Here is your URL: ${url}` console.log("👍 worked 1"); return requestPromise('website.com/api/page2') //← we RETURN the promise }) .then(function(data){ console.log(data) console.log("👍 worked 2"); return requestPromise('website.com/api/page3') }) .catch( function(error){ // ← data = 'Connection Timeout :(' console.log(error) console.log("👎 error 1"); }) // Here is your URL: website.com/api/page1 // 👍 worked 1 // Connection Timeout :( // 👎 error 1
.catch()
, so if at any point one of the promises is rejected, it hits the.catch()
on the end of the code.
👈 go back or 👆 go to beginning of Promises or 👆 go up to Asynchronous JS
The Promise constructor is primarily used to wrap functions that do not already support promises.
-
Syntax:
new Promise(executor)
-
eg.:
Thenew Promise
expects us to pass in a function, which has 2 parameters.The 1st parameter is a function that resolves the promise, the 2nd rejects it.
If at any point we call
resolve
the promise object will be resolved, and the same logic withreject
.fulfilled
new Promise(function(resolve, reject) { resolve(); }) // Promise {<fulfilled>: undefined} // [[Prototype]]: Promise // [[PromiseState]]: "fulfilled"
rejected
new Promise(function(resolve, reject) { reject(); }) // Promise {<rejected>: undefined} // [[Prototype]]: Promise // [[PromiseState]]: "rejected"
pending (until we call resolve or reject inside of the function)
new Promise(function(resolve, reject) { }) // Promise {<pending>} // [[Prototype]]: Promise // [[PromiseState]]: "pending"
Let's make a request, which will be resolved after 1000 ms.
Process: wereturn
ournew Promise
function, inside we give asetTimeout
and inside we callresolve()
, which will resolve after 1000 ms, if theconst num
variable is true or falsefunction myRequest(url) { return new Promise((resolve, reject) => { const num = Math.floor(Math.random() * 10) setTimeout(function() { if (num < 7) { resolve(`Your site is: ${url}`); } reject('Request error'); }, 1000) }) } //call myRequest with a URL and add a '.then()' and '.catch()' myRequest('mysite.com/page1') .then((data) => { console.log('Fulfilled the request!'); console.log(data); }) .catch((error) => { console.log('It is an error!'); console.log(error); }) // Promise {<pending>} // Fulfilled the request! // Your site is: mysite.com/page1
-
eg.:
Let's see that in real practice
The background-color changing program
HTML<body> <script src="app.js"></script> </body>
JS
Here we will not callreject
, because we are only resolving the colours// creatng a function with a new promise function delayedColorChange (color, delay) { return new Promise((resolve, reject) => { setTimeout(()=>{ document.body.style.backgroundColor = color; resolve(); }, delay) }) } // calling the promise, chainging the '.then()' delayedColorChange('red', 1000) .then(()=>{ return delayedColorChange('orange', 1000) }) .then(()=> { return delayedColorChange('green', 1000) }) .then(()=> { return delayedColorChange('blue', 1000) })
👈 go back or 👆 go to beginning of Promises or 👆 go up to Asynchronous JS
-
async functions make our code cleaner (syntactic sugar, means make code easier to read).
-
async functions make promises prettier
-
the
async
keyword:
we use theasync
key word to declare a function, an async function.
(It stands for asynchronous.) -
when we declare the
async
function, it automatically returns apromise
❗️- if the function returns a value → the
promise
will be fulfilled - if the function throws an exception → the
promise
will be rejected
- if the function returns a value → the
-
syntax:
async function name() { … }
-
eg.:
async
returns a promise automatically. We don't have to writereturn new Promise…
// declaring function as an async function async function hi() { } //calling hi() hi(); // returns a promise (automatically): // [[Prototype]]: Promise // [[PromiseState]]: "fulfilled" // [[PromiseResult]]: undefined // undefined because there is no value
-
eg.:
Declaringasync
function and returning a value. When our function returns a value, then the promise will be fulfilled with that value.const name = async() => { return 'John Doe'; } name(); // Promise {<fulfilled>: 'John Doe'}
Promise fulfilled
- eg.:
We can chain theasync
function, because technically it is a promise.const name = async() => { return 'John Doe'; } name().then((data) => { console.log('async function resolved: ',data); }); // async function resolved: John Doe // Promise {<fulfilled>: undefined}
Promise rejected
-
throw
ing an error:
Thethrow
keyword should be used when we want to handle an error, and nothing else should be run after that. -
So, the
throw
statement displays a user-defined exception (the technical term for errors that occur when the application is already running). It can be helpful to debug the issue and/or indicate to the user what s/he might be doing wrong that is causing that error.
The way it works: is that the execution of the current function stops (the statements afterthrow
won't be executed), and control will be passed to the firstcatch
block in the call stack. If there is nocatch.
the program will terminate.- More about
throw
- More about
-
eg.:
Throwing an error is a way to reject apromise
The error can be: a syntax error, or our defined error.// we can just throw a string as an error. const name = async() => { throw 'Sorry for this error!' return 'John Doe'; } name(); // it's still a promise, and we get an error // Uncaught (in promise) Error: Sorry for this error! // at name (<anonymous>:2:11) // at <anonymous>:6:3
-
eg.:
A login example (however there is nothing asynchronous in our code here).
chaining using the.catch
and the.then
!// login function takes 2 parameters async function login(username, password) { if (!username || !password) // if there is no username OR password throw message throw 'No username or no password' if (password === 'Password') return 'Hello!' } login('User01', 'Password') .then(message => { console.log('You logged in!!') console.log(message) }) .catch(error =>{ console.log('Error') console.log(error) }) // You logged in!! // Hello!
👈 go back or 👆 go to beginning of Promises or 👆 go up to Asynchronous JS
the await
keyword pauses the execution of our async
function, and wait for a promise
to be fulfilled before continuing on.
-
we can only use the
await
keyword inside of a function declared withasync
. Forawait
we need to get anasync function
, and inside that we have ourawait
. Soawait
does the same job as.then()
, but cleaner. -
async is an easy to use and understand approach of returning promises. It is used to return the new promise in a simpler way.
-
then
andcatch
methods belong to the Promise. When usingawait
the value gets unwrapped from thepromise
. We can imagine that a promise is some sort of container/box data type. If we remove theawait
, it returns apromise
which then can be "then
able" or "catch
able". -
eg.:
The background-color changing programBut now, we use the
async
function declaration andawait
.Whe we use the
await
keyword, it's going to wait for a promise to be fulfilled. That means it will just pause until a promise is fulfilled after X second.// creatng a function with a new promise const delayedColorChange = (color, delay) => { return new Promise((resolve, reject) => { setTimeout(() => { document.body.style.backgroundColor = color; resolve(); }, delay) }) } // calling it with async function declaration async function colorChange(){ await delayedColorChange('red', 1000) // ← returns a promise. await = pause await delayedColorChange('orange', 1000) // ← returns a promise await delayedColorChange('green', 1000) await delayedColorChange('blue', 1000) return "Now, the promise is fulfilled" // ← promise isn't fulfilled until it gets to this return } // it's fulfilled because it returned a value // call the function colorChange();
We can console.log a message after
colorChange()
is done.
ThecolorChange()
's await promises are only fulfilled until they reach thereturn "Now, the promise is fulfilled"
.async function printColorChange() { await colorChange(); // waits until the promise is fulfilled console.log("End of color changes"); } // call the function printColorChange();
-
When promises are resolved with information we use mostly
await
. Weawait
some request.-
eg.:
We use this request modell, when a promise is fulfilled with some data (request). (You can see the previous version here)
The data fromrequestPromise
isawait
ed, stored in a variablelet data
.const requestPromise = (url) => { return new Promise((resolve, reject) => { const delay = Math.floor(Math.random() * (4500)) + 500; setTimeout(() => { if (delay > 4000) { reject('Connection Timeout :(') } else { resolve(`Here is your URL: ${url}`) } }, delay) }) } // here we await for the request async function waitingRequest() { let data = await requestPromise('/page1'); console.log(data); } waitingRequest(); // Here is your URL: /page1
👈 go back or 👆 go to beginning of Promises or 👆 go up to Asynchronous JS
-
in the case of a rejection, await
promise throws the error.
→ we can catch them with try...catch
.
The try..catch
statement:
The try...catch
statement marks a try block and a catch block.
If the code in the try block throws an exception then the code in the catch block will be executed.
-
eg.:
Howtry..catch
works.
In thetry
block we have an error in the console.log, so it goes to thecatch
block, and prints thecatch
block's error message..try { console.log(hello); //← it should be "hello" with quotes. } catch { console.error("Catch the error!"); //← Note: the console.error() method outputs an error message to the Web console. } //❌ ▸ Catch the error!
-
eg.:
We make the time longer, to have an error for sure :const delay = 4500;
.
If thetry
block's code has an error (throws an error), we can catch it with thecatch
, so we can handle that error.
Theerror
in thecatch(error)
refers to the error itself (here "Connection Timeout").const requestPromise = (url) => { return new Promise((resolve, reject) => { const delay = 4500; setTimeout(() => { if (delay > 4000) { reject('Connection Timeout') //← the value that our promise was rejected with. } else { resolve(`Here is your URL: ${url}`) } }, delay) }) } // here we await for the request async function waitingRequest() { try { // ← what we are trying to do let data = await requestPromise('/page1'); console.log(data); //← data = "Here is your URL: /page1" let data2 = await requestPromise('/page2'); console.log(data2); } catch(error) { console.log('We catch the error!') console.log('error is: ', error) // ← error message itself: Connection Timeout } } waitingRequest(); // We catch the error! // error is: Connection Timeout
👈 go back or 👆 go to beginning of Promises or 👆 go up to Asynchronous JS
AJAX = Asynchronous JavaScript And XML
(XML = Extensible Markup Language)
(Correctly would be AJAJ, since XML is depricated, we use JSON instead)
-
AJAX refers to:
a) making requests to load information (behind the scenes)
b) to send information
c) to save something on a given website
d) interacting with a server -
So the idea of AJAX is: creating applications where by using JavaScript we can load data / we can fetch information / we can send data somewhere, to save something to a database. And all this happens behind the scenes.
-
There's no need to refresh the page, because we can make a request to the API every X minutes.
What the API sends back is pure information in a JSON format. -
eg.:
The infinite scroll on instagram, reddit, etc.
The live searching (google) it gives suggestions while typing -
dev.tool → network tab:
shows requests that have been made on a given page, new information that has been loaded. -
API
JSON
API management tools
HTTP Verbs
HTTP response status codes
Query Strings
HTTP Headers
XHR - XMLHttpRequest
Fetch API
Axios
A search app using Axios
Application Programming Interfaces
API is very broad term that refers to any interface for one computer to interact or communicate with another software.
- An API allows to create complex functionality more easily.
An API abstracts more complex code away from you, providing some easier syntax to use in its place.
Web API
Web apps are interfaces that are Web based (HTTP based). With a Web API we are referring to an interface that occurs over HTTP.
So we can make requests to particular URLs, which we usually call endpoints.
💡An endpoint is the URL where your service can be accessed by a client application (eg.: https://catfact.ninja/fact
).
Web APIs are like a portal into a different application or database.
Interface not for humans, but for applications!
- When we make requests using JavaScript, AJAX requests, we are looking the data (not the HTML, CSS).
JavaScript Object Notation
JSON is just a format for sending data for, for sending information from an API to, our browser for instance.
It is a data-interchange format.
-
JSON possible values are not exactly the same as in JS:
- object, array, string, number, "true", "false", "null"
- ❗️ undefined is not a valid JSON (it's valid in JavaScript)
-
It's a way of formatting data that is consistent and predictable and it's very similar to JavaScript.
-
Syntax:
- key-value pairs
- curly braces
{}
- every key has to be a "double quoted string"
-
eg.:
{ "stringKey" : "string value", "intigerKey" : 2022, "booleanKey" : true, "arrayKey" : [ "array01", "array02", "array03" ] }
-
JSON.parse()
:
TheJSON.parse()
method to turn JSON into a valid JavaScript object.- eg.:
turning a JSON into a JavaScript object with theJSON.parse()
methodTo extract the dataconst json = '{"result":true, "count":42}'; JSON.parse(json); // result: true, count: 42
const json = '{"result":true, "count":42}'; // save it to a variable const obj = JSON.parse(json); obj.count; // 42
- eg.:
-
JSON.stringify()
:
The JSON.stringify() method converts a JavaScript object or value to a JSON string.-
It is useful when we want to send information to an API and API needs the data as JSON.
-
eg.:
Turn a JavaScript object into a valid JSONconst object = {phone : 'mobile', color : 'silver', isWorking : true, number : undefined} // calling the method JSON.stringify(object); // '{"phone":"mobile","color":"silver","isWorking":true}'
-
We can use tools to make requests, to save requests, helps us make API calls and to test out different API requests.
postman
it is an API platform for building and using APIs.
hoppscotch
it is the same as Postman, but it is opensource and free.
- eg.:
We send a URL with a base API (.../api) →https://catfact.ninja/fact
via Postman (we place the URL into the GET and click send).
What we get, is a JSON, it is the response
It looks like this...{ "fact": "Fossil records from two million years ago show evidence of jaguars.", "length": 67 }
HTTP defines a set of request methods to indicate the desired action to be performed for a given resource.
-
💡 a very useful article about the topic of HTTP Requests.
-
Each verb is a different type of request.
-
GET request :
to get or retrieve information, it's just a way of getting stuff from an API. -
POST request :
to send data somewhere, trying to post data. The data we are sending is going to be saved somewhere / stored in a database / have an impact on a server. -
DELETE request :
to delete something via an API.
-
HTTP response status codes indicate whether a specific HTTP request has been successfully completed.
So it is a code that comes back in an HTTP response, and it has a meaning.
-
they are grouped in 5 classes:
-
Informational responses (100–199)
-
Successful responses (200–299)
-
Redirection messages (300–399) :
do soemthing with rederiction.
Eg.: google.co instead of google.com → redirect to google.com -
Client error responses (400–499):
These are all status codes that indicate something the user or the client side did wrong.- 400 : Bad request. It means that the server cannot process the request due to something that is perceived to be a client error
- 401 : Unauthorized. It means that the client request has not been completed because it lacks valid authentication credentials for the requested resource.
- 404 : Not found. It means that the server cannot find the requested resource.
-
Server error responses (500–599) :
these are the server side errors. When somethig went wrong on the server side, rather than on the client side. (Something is wrong on the API side).
-
-
eg.:
Client error responses 404
When we asking for an end point that doesn't exist (like a misspelled URL).
It means that the API doesn't know what we are asking → it sends an HTML 404.
By opening the DEV tool:we can see on thedevtool/netwrok
part that status is 404. -
eg.:
Client error responses 405
It means that endpoint exists, but it doesn't support whatever we tried to do a post instead of a get (method not allowed).
A query string is the portion of a URL where data is passed to a web application and/or back-end database.
So we can include a lot of information in a URL, and each endpoint means something different.
- eg.:
https://example.com/over?field1=value1&field2=value2
The UR:https://example.com/over
The query string :?field1=value1&field2=value2
Some API endpoints have variables in the URL, something that can change in the URL.
-
parts of the URL
-
id
(can be:id
or{{id}}
or<id>
):
it is a common variable, it tells us to replace this with an actual ID.- eg.:
:id
indicting that we can replace it with something, here like a number.
Many times there is a documentation of the given API, where they explain what is what.we can replace thehttps://swapi.dev/api/people/:id/
:id
with a numberand it gives us something (here a spceific resource, the 7th person eg).https://swapi.dev/api/people/7
- eg.:
-
?
at the and of the URL there is?
(question mark) starts the query string, where a lots of key-value pairs can be found:
key=
value
These key-value pairs are sparated by&
(ampersands).- eg.:
https://example.com/over?color=blue&material=wood
- eg.:
-
eg.:
https://developer.mozilla.org/en-US/?q=react
The server of mozzilla, is looking for a valueq
and use what it finds underq
as the search term (query).
Here the API finds a lot of React search results.
HTTP headers are a lot of key-pair information shared between the client and the server to set up a lot of stuff behind the scenes, like caching-related data (which helps make data persistent in your browser even if you reload the page), authorization data (which proves you are who you're saying you are when accessing restricted-access webpages) and so on.
So, headers are an additional way of passing information with a given request and also with a response.
Sort of metadata, little add on details of your request.
-
HTTP headers are key-value pairs
-
eg.: the Accept- headers indicate the allowed and preferred formats of the response. Other headers can be used to supply authentication credentials (e.g. Authorization), to control caching, or to get information about the user agent or referrer, etc.
-
dev.tool → network tab : in the headers tab → :
-
Request Headers:
- here are the request headers that were sent
- we can see key-value pairs, which are pairs of information that goes along with the request.
(eg.:cookie: user=E1011034406231; remember=true;
)
-
-
information comming back from the server
-
so an HTTP response which doesn't relate to the content of the message
(eg.: Age, Location or Server are used to give a more detailed context of the response). -
the server that tells us more about the content, the language, the data type, the format (HTMLor JSON), etc.
-
-
-
eg.:
Using Postman and custom Headers
From this API, we type API endpoints (URL) to the GET request.
To adjust the response formatting, so if we want JSON (or HTML, or plain text), we need to specify the Header called 'Accept'
→ we add the key and then the value information:
we set thekey: Accept
|value: application/json
-
some APIs can require from us to send a Header or multiple Headers along with our request. We can do this via code or using Postman.
Is a way of sending request via JavaScript.
- There's no support for
promises
, so we have lots of callbacks, so it is messy. - The capitalization makes it difficult (like here: XMLHttpRequest)
- clunky syntaxes
- eg.:
The way that we make a request is by creating a newXMLHttpRequest
object and save it to a variable (request
).
Then we add theopen
method with the("GET", "URL")
GET request and the URL.
After that we opened it, we have to send it (variable.send();
). But before we add 2EventListener
callbacks:variable.onload
andvariable.onerror
.Weconst request = new XMLHttpRequest(); //← creating a request object request.onload = function() { console.log("Loaded!"); console.log(this); //← 'this' is the request object } request.onerror = function() { console.log("ERROR!"); console.log(this); //← 'this' is the request object } request.open("GET", "https://swapi.dev/api/planets/1/"); //← open a request request.send(); //← send the request // we send it, and the result: // Loaded! // ▸ XMLHttpRequest //← XMLHttpRequest object response:... 🔹 responseText:... //← it is a text (string), we have to convert it to JS object responseType: ""
console.log
theresponseText
→ we get a long string.
We have turn it into a JS object by parsing because it is a string (we can not access the value such as eg.:request[population]
, this doesn't work!!!)Note JSON.parse() method.const request = new XMLHttpRequest(); //← creating a request object request.onload = function() { console.log("Loaded!"); console.log(this.responseText); const variable = JSON.parse(this.responseText); //← variable is a JS object here | JSON.parse() is a method object } request.onerror = function() { console.log("ERROR!"); console.log(this); //← "this" is the request object } request.open("GET", "https://swapi.dev/api/planets/1/"); // ← open a request request.send(); //← send the request // we send it and the result is: // Loaded! // {"name":"Tatooine","rotation_period":"23",,"population":"200000",...} //← it's an object: key-value pairs → now we can extract the values.
After theconst variable = JSON.parse(this.responseText);
we received a JS object, key-value pairs.
❗️We can extract the value:console.log(variable.name, variable.population)
. . . request.onload = function() { console.log("Loaded!"); const variable = JSON.parse(this.responseText) console.log(variable.name, variable.population) ; } // extracting the values by using the key . . . // Loaded! // Tatooine 200000
it allows us to make requests using a the fetch()
function.
-
it supports
promises
and async functions (compared to XMLHttpRequest, which doesn't really.) -
syntax:
fetch("https://here we place the URL")
-
eg.:
We just callfetch("our URL")
, and it turns back a pending promise.fetch("https://swapi.dev/api/planets/1/"); // ▸ Promise {<pending>}
We add
.then()
and.catch()
fetch("https://swapi.dev/api/planets/1/") //← we send a request with 'fetch', and we wait for a response from the server .then(response => { //← we handle the response with this .then() wich is chained to the 'fetch()' console.log("Fulfilled!!!", response); //← if the response status is ok, it will show "Fulfilled!!!" and we display the "response" }) .catch(error => { console.log("Error!!!", error); }) // Fulfilled!!! ▸ Response //← Response is an object // 💡 ▸ body: ReadableStream //← normally it should contain the value (JSON) // // here it is an incomplete body → we don't have the data, because the HTTP request was made before this is resolved. // bodyUsed: false // ▸ headers: Headers {} //← these are the response headers // ok: true // redirected: false // status: 200 // statusText: "" // type: "cors" // url: "https://swapi.dev/api/planets/1/" // ▸ [[Prototype]]: Response
fetch()
sends a request: we wait for a response from the server and then we handle it with the.then()
which is chained to thefetch()
call.
So, inside of the.then()
we can access the server's response. We get the response back the moment the server sends back headers. (Here we don't have the data in thebody
because thepromise
is fulfilled as soon asfetch("https://...)
receives any headers.)
💡Note: theReadableStream
means that we actually need to read it and pass it. There's a method on the Response calledresponse.json()
..
So theresponse.json()
method is added on to thefetch()
's response object, and it also returnes apromise
.
Since theresponse.json()
method returnes apromise
, we can chain a.then()
method.fetch("https://swapi.dev/api/planets/1/") //← we send a request with 'fetch', and we wait for a response from the server .then(response => { //← we handle the response with this .then(). Inside the 'then()' we can access the server's response... console.log("Fulfilled!!!", response); //...we get back the response the moment the server sends back headers response.json().then(data => console.log("JSON is DONE!!!", data)); //← json() method | we can use any name instead of the "data" }) .catch(error => { console.log("Error!!!", error); }) // Fulfilled!!! Response {type: 'cors', url: 'https://swapi.dev/api/planets/1/', redirected: false, status: 200, ok: true, …} // JSON is DONE!!! {name: 'Tatooine', rotation_period: '23', orbital_period: //← we get back the data'304', diameter: '10465', climate: 'arid', …}
A NICER refactured way for the above code is to not to use the
response.json()
in a single line.
We put areturn
before theresponse.json()
, and place the.then(data =>...)
below, outside of the other.then()
method.fetch("https://swapi.dev/api/planets/1/") .then(response => { console.log("Fulfilled!!!", response); return response.json() }) .then(data => { console.log("We got back these data: ", data); }) .catch(error => { console.log("Error!!!", error); }) // Fulfilled!!! Response {type: 'cors', url: 'https://swapi.dev/api/planets/1/', redirected: false, status: 200, ok: true, …} // We got back these data: {name: 'Tatooine', rotation_period: '23', orbital_period: '304', diameter: '10465', climate: 'arid', …}
So what happens:
1. as a startfetch()
sends a request to this ("https://URL") URL first, then it returns a promise, which promise either fulfilled or rejected.
2. if it is fulfilled, we get into the 1st.then()
, we call it and return theresponse.json()
method.
3.response.json()
method reads the response object (ReadableStream), and then thisresponse.json()
method returns a promise
4. after the return, we chain on the.then()
and.catch()
.
Clear explanation:
So when we send a request with fetch
we wait for a response from the server, and then we handle that response from the server with the 1st .then()
(its chained to the fetch()
call).
Inside of the 1st .then()
we can access the server's response. We get response back the moment the server sends back headers. (Some servers might send back only headers, or headers and some data other than what we originally expected (like an error message)).
If the response status is ok (200 status code) then we can expect to get back some data (in the body property) from the server.
fetch()
gives us the body property as a ReadableStream
(from the Streams API), which gives us a lot of options for how we can interpret that data:
- we can get the data back as a blob, text, json, etc.
(For certain kinds of data this can give us the ability to process it as it comes in, such as a video. In the past we had to wait for the entire video file to be sent over, but now we can process theReadableStream
and that allows us to play the video as its data becomes available = the Streams API).
Most common use cases, we are just expecting back JSON data that we can parse and render to the page or do some sort of update to the page based on the data we get back.
When we want a 2nd request, we don't have to do complicated nesting.
The 1st request has to be fulfilled inorder to get to the 2nd request.
(We can make these requests independently, so they don't depend on each other, thus the 2nd request can be fulfilled without worrying about the 1st request.)
We just add a fetch(URL2nd Request)
below the response.json()
's .then()
method.
fetch("https://swapi.dev/api/planets/1/") //← 1st request
.then( response => {
console.log("1st request!");
return response.json()
})
.then( data => {
console.log("1st response: ", data);
return fetch("https://swapi.dev/api/planets/2/"); //← 2nd request, only runs if the 1st request fulfilled
})
.then( response =>{
console.log("2nd request!!!");
return response.json()
})
.then( data => {
console.log("2nd response: ", data);
})
.catch( error => {
console.log("Error!!!", error);
})
// 1st request
// 1st response {name: 'Tatooine', rotation_period: '23', orbital_period: '304', diameter: '10465', climate: 'arid', …}
// 2nd request!!!
// 2nd response {name: 'Alderaan', rotation_period: '24', orbital_period: '364', diameter: '12500', climate: 'temperate', …}
Let's refactor the code above using the async()
function and try..catch
.
const loadStarWarsPlanets = async () => {
try {
const response = await fetch("https://swapi.dev/api/planets/1/"); // ← 1st request
const data = await response.json()
console.log(data);
const response2 = await fetch("https://swapi.dev/api/planets/2/"); // ← 2nd request
const data2 = await response2.json()
console.log(data2);
} catch (error) {
console.log("Error!!!", error);
}
}
// call the function
loadStarWarsPlanets();
// {name: 'Tatooine', rotation_period: '23', orbital_period: '304', diameter: '10465', climate: 'arid', …}
// {name: 'Alderaan', rotation_period: '24', orbital_period: '364', diameter: '12500', climate: 'temperate', …}
it's a separate library, made for HTTP requests (creating requests and working with them), to make it easy and simple.
-
behind the scenes, it uses
fetch()
in the browser -
we can install this library (see on the gitHub), or we can just use this CDN script in our HTML:
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
-
the big advantage, that it is already parses the JSON (
response.json()
). So in the promise object, we already have data (it's not empty). -
Axios has methods
-
eg.:
Make aget
request we use.get
+URL
HTML<body> <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script> <script src="app.js"></script> </body>
JS
Instead of callingfetch()
, we callaxios.get(URL)
, which makes a request AND parses the JSON.
We chain the.then()
to see what is the response.
We add a.catch()
if there is an error.axios .get("https://swapi.dev/api/planets/1") .then((response) => { console.log("Our response: ", response); }) .catch((e) => { console.log("ERROR! ", e); }); // it gives us a promise! See the response object below, filled with data (because it parses the JSON behind the scence) // Our response: ▼{data: {…}, status: 200, statusText: '', headers: {…}, config: {…}, …} // ▼ data: // climate: "arid" // created: "2014-12-09T13:50:49.641000Z"
Refactoring to
async
function format!const getStarWarsPlanets = async () => { const response = await axios.get(`https://swapi.dev/api/planets/1/`); console.log(response.data); }; getStarWarsPlanets(); // {name: 'Tatooine', rotation_period: '23', orbital_period: '304', diameter: '10465', climate: 'arid', …} // climate: "arid" // created: "2014-12-09T13:50:49.641000Z"
We make it with an
id
, so we can write our numbers there, when we call the function.const getStarWarsPlanets = async (id) => { const response = await axios.get(`https://swapi.dev/api/planets/${id}/`); console.log(response.data); }; getStarWarsPlanets(3); //← we can add our number //► { name: 'Yavin IV', rotation_period: '24', orbital_period: '4818', diameter: '10200', climate: 'temperate, tropical', …}
Best practice: wrap it into a
try..catch
.const getStarWarsPlanets = async (id) => { try{ const response = await axios.get(`https://swapi.dev/api/planets/${id}/`); console.log(response.data); } catch(error){ console.log("Error!", error); } }; getStarWarsPlanets(4); //← we can add our number //► { name: 'Hoth', rotation_period: '23', orbital_period: '549', diameter: '7200', climate: 'frozen', …} // climate: "frozen"
// axios
axios.get('https://api.github.com/orgs/axios')
.then(response => {
console.log(response.data);
}, error => {
console.log(error);
});
// fetch()
fetch('https://api.github.com/orgs/axios')
.then(response => response.json()) // one extra step
.then(data => {
console.log(data)
})
.catch(error => console.error(error));
A video about Axios.
Another Axios video "crash course"
- eg.:
We are making a search form, where we use an API of TV shows, so we can search for TV shows. The image of the searched show will be displayed on our page.
HTMLJS<head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>TV Show Search</title> <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script> </head> <body> <h1>TV Show Search</h1> <form id="searchFrom"> <input type="text" placeholder="TV Show Title" name="query"> <button>Search</button> </form> <script src="app.js"></script> </body>
Listening for thesubmit
event object. Theevent.preventDefault()
prevents the default behavior of the page being reloaded when we submit the form. So if we don't useevent.preventDefault()
on a formsubmit
event listener, then when we click the submit button (to submit the form) it would reload the page.We want to get theconst form = document.querySelector('#searchForm'); form.addEventListener('submit', function (event){ event.preventDefault() console.dir(form) }) // ► form#searchForm // 0: input // 1: button // ... // ► elements: HTMLFormControlsCollection(2) //← elements property // 0: input // 1: button // query: input //← we gave the name "query" in the <form> (name ="query") // length: 2 // [[Prototype]]: HTMLFormControlsCollection
input
value, so we use the elements property (what we can see in the above example). We get the input value with that lineform.elements.query.value
.We are doing the API call We addconst form = document.querySelector('#searchForm'); form.addEventListener('submit', function (event){ event.preventDefault() const searchTerm = form.elements.query.value; }) // searchTerm gives back what we type in the searchbar, eg. we type "hello" // output is: // hello
axios.get('URL')
So the API eg.:https://api.tvmaze.com/search/shows?q=girls
, where theq
is the query string.
Our plan is that what the user types in the searchbar, it will be theq
's value (eg.q = ${what the user types here}
).
We make it anasync
function so we can use theawait
.We acces the the 'show' property's image.const form = document.querySelector('#searchForm'); form.addEventListener('submit', async function (event){ event.preventDefault() const searchTerm = form.elements.query.value; const response = await axios.get(`https://api.tvmaze.com/search/shows?q=${searchTerm}`) console.log(response.data); }) // we type 'chicken' in the search bar, the output is: //► (10) [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}] //← an array with 10 different shows // open the first one [0]: // ► 0: // score: 0.70269716 // ► show: //← 'show' is a property // averageRuntime: 15 // dvdCountry: null // ended: null // externals: {tvrage: 5027, thetvdb: 75734, imdb: 'tt0437745'} // genres: (2) ['Comedy', 'Science-Fiction'] // id: 686 // image: {medium: 'https://static.tvmaze.com/uploads/images/medium_portrait/5/14886.jpg', original: 'https://static.tvmaze.com/uploads/images/original_untouched/5/14886.jpg'} // ► [[Prototype]]: Object
To get all the images for the search show, we need to use a loop.const form = document.querySelector('#searchForm'); form.addEventListener('submit', async function (event){ event.preventDefault() const searchTerm = form.elements.query.value; const response = await axios.get(`https://api.tvmaze.com/search/shows?q=${searchTerm}`) const img = document.createElement('img'); img.src = response.data[0].show.image.medium //← this gives back the image's url document.body.append(img) //← append the img to the body }) // it adds the searched show's image on the page
We create a separate function (we cal itmakeImages
) for the loop.
So for each show we make a new image and then we set its source. Instead of theresponse.data
we setresult.shows
, where theshow
is comming from the API itself.There are cases when there is no image (image is Null). We add some logic for these cases, usingconst form = document.querySelector('#searchForm'); form.addEventListener('submit', async function (event){ event.preventDefault() const searchTerm = form.elements.query.value; const response = await axios.get(`https://api.tvmaze.com/search/shows?q=${searchTerm}`) makeImages(response.data) }) // a loop function const makeImages = (shows) => { //← shows: each element in the array for (let result of shows){ const img = document.createElement('img'); img.src = result.show.image.medium //← show is from the API document.body.append(img) } }
if
.
Also, we empty theinput
!const form = document.querySelector('#searchForm'); form.addEventListener('submit', async function (event){ event.preventDefault() const searchTerm = form.elements.query.value; const response = await axios.get(`https://api.tvmaze.com/search/shows?q=${searchTerm}`) makeImages(response.data) form.elements.query.value = ''; // empty the input }) // a loop function const makeImages = (shows) => { for (let result of shows){ if(result.show.image){ // if there is image, the block runs, otherwise ignore. const img = document.createElement('img'); img.src = result.show.image.medium document.body.append(img) } } }
Making API calls with multiple query strings
eg.:
multiple query strings
https://api.tvmaze.com/schedule?country=US&date=2014-12-01
-
With Axios we can set a so called config object:
{ params: { q: searchTerm } }
-
{ params : { own object with key-value pairs}, headers: { own object with key-value pairs} }
is set it to its own object →{ q: searchTerm }
. We can add several things, like headers, etc. -
Every key value pair in the
{ q: something }
will be added to the query string -
let see in practice:
We create aconst config
where we store the config params.const form = document.querySelector('#searchForm'); form.addEventListener('submit', async function (event){ event.preventDefault() const searchTerm = form.elements.query.value; const config = { params: { q: searchTerm } }; //← we have the params const response = await axios.get(`http://api.tvmaze.com/search/shows`, config); //← we added the params makeImages(response.data) form.elements.query.value = ''; // empty the input }) // a loop function const makeImages = (shows) => { for (let result of shows) { if (result.show.image) { // if there is image, the block runs, otherwise ignore. const img = document.createElement('IMG'); img.src = result.show.image.medium; document.body.append(img) } } }
A little explanation about this example:
- The name
config
is a variable name that we arbitrarily chose. config
it holds an object with theparams
key which the axios request will understand when we pass it as an argument.- The key
q
is something that the particular API that we are using expects. - To know what
params
we can pass to an API, we need to study the API documentation for the route/address that we are sending a request to - that way we can know of all the options/params supported by the API endpoint in question.
- The name
💡 about the params
, how to use, etc. we can read the details in the Axios doc.
Prototypes are the mechanism by which JavaScript objects inherit features from one another.
-
Prototypes are template objects (a bunch of methods).
-
We can create multiple objects that share the same prototype (so they all have acces to the same methods). This is the key conept of it, that brings us to OOP.
-
Objects can have a prototype object which acts as a template object. So it means is that certain objects eg.: an array, have a a lot of methods.
-
__proto__
: it is a property that references the array prototype.
So a prototype is the template object for in this case, arrays. It contains a bunch of methods typically.
Every array have access to all of these methods❗️ -
So rather than having a separate method on every single array called push, filter, concat, find, etc. , there is one prototype. Each array has a reference to that prototype, with a special property called
__proto__
❗️ -
__proto__
is a reference to the blueprint objet: the prototype. -
eg.:
lets examine an other type of JS object thedocument.body
.const body = document.body; console.dir(body); // ► body //← we can see properties that are specific to this body // ... // background: "" // baseURI: "chrome://newtab/" // bgColor: "" // ... // ► __proto__: HTMLBodyElement //← at the bottom there is __proto__ // aLink: (...) //← __proto__ is an HTML body element, it has different methods and properties // background: (...) // bgColor: (...) // link: (...) // ...
-
eg.:
Array.prototype
With capital 'A', it is an individual instance of an array. We can see everything on that array prototype.Array.prototype; // ► [constructor: ƒ, concat: ƒ, copyWithin: ƒ, fill: ƒ, find: ƒ, …] // ► at: ƒ at() // ► concat: ƒ concat() // ► constructor: ƒ Array() // ► copyWithin: ƒ copyWithin() // ...
-
eg.:
String.prototype
We cen see a bunch of string methods.String.prototype; // ► String {'', constructor: ƒ, anchor: ƒ, big: ƒ, blink: ƒ, …} // bunch of string methods // ► anchor: ƒ anchor() // ► at: ƒ at() // ► big: ƒ big() // ...
We can add our own methods
-
eg.:
Adding our own methods,foobar
toString.prototype
.String.prototype.foobar = () => { console.log("Hello!") } //nothing happened. //But if we look the String.prototype, it has a property 'foobar' String.prototype; // ► String {'', constructor: ƒ, anchor: ƒ, big: ƒ, blink: ƒ, …} // ► 🔹foobar // ► anchor: ƒ anchor() // ► at: ƒ at() // ...
Lets make a new string and add our new string method 'foobar()'.
const dog = "Black"; dog.foobar(); // "Hello!"
Adding and defining our own method on the
String.prototype
-
eg.:
Creating a methodhello
on the String.prototype object, so it will be in the String.prototype.String.prototype.hello = function() { console.log(`Good day ${this}!!!`); }; // just typing a string and adding our method "John Doe".hello(); // "Good day John Doe!!!"
Overwriting our method to an Array.prototype(same process is valed for other prototypes, like String.prototype)
- eg.:
Overwriteiny an existingpop
method on the Array.prototypeArray.prototype.pop = function() { return "Say hi to this array"; }; // calling an array [3, 4, 5].pop; // instead of popping // "Say hi to this array"
-
💡 The difference between String.prototype
or Array.prototype
vs. __proto__
:
String.prototype
is the actual prototype object, where __proto__
inside a string or array or etc. is a reference to the String. prototype
(or Array.prototype
, or other stuff).
An Object is a unique entity that contains property and methods.
- the characteristics of an Object are called as Property, in Object-Oriented Programming and the actions are called methods.
- an Object is an instance of a class. Objects are everywhere in JavaScript almost every element is an Object whether it is a function, array, and string.
What is an object:
-
In OOP we combine a groupd related functions and variables into a unit, and this unit is called an object.
-
We refer the object's variables as properties.
-
We refer the object's functions as methods.
function() variable
→ function() is method and variable is property. -
eg.:
A car:
it has properties: color, model.
it has methods: start(), stop(), move()
Creating Objects
Value vs Reference Type
Factory Functions
Constructor Functions
Classes
extends
and super
(the basics of inheritence)
creating an object using the object literal syntax
, referred as {}
❗️
(An object in JS is a collection of key:value pairs.)
- eg.:
Creating object with the: object literal syntax.
Creating a circle object.
This object has 3 members:radius
,location
anddraw
.
draw
is a function, so we refer to it as a method.radius
andlocation
are properties.We can access theconst circle = { radius : 1, //← property location : { //← property x : 1, y : 1 }, draw : function() { //← method console.log("Drawing a circle"); } };
circle
's members with the.
dot notation.
Here we are calling thedraw()
method.circle.draw(); // Drawing a circle
We can pass by value and by reference in JS.
The main difference between the two is that passing by value happens when assigning primitives while passing by reference when assigning objects.
- we have 2 categories of types:
- Value Type (aslo called primitives)
- number, string, boolean, symbol, undefined, null
- Reference Types
- object
- function
- array
- Value Type (aslo called primitives)
💡 Primitives are copied by their value.
💡 Objects are copied by their reference.
So technically in JS we have primitives and objects.
-
eg.:
We define 2 primitives,x
andy
, they are 2 independent variables ❗️The value (
10
) is stored inx
. When we copy thex
, we copy the value whatx
stores, it is copied ont they
variable.
So they are completely independent from eachother.let x = 10; let y = x; x = 20; x; // 20 y; // 10
Now, we are using a reference type (an object).
When we use the {value: 10} object, the object is not stored in thex
variable.
{value: 10}
object is stored somewhere else in the memory.
The address of the memory location is stored in thex
variable (not the value).
When we copyx
into they
, it is the address (reference) what is copied. →x
&y
are pointing to the same object in the memory.let x = {value: 10}; //← an object, key-value pairs let y = x; x.value = 20; x; // {value: 20}; y; // {value: 20};
-
eg.:
Primitives are copied by their value.
When we callincrease(number)
, its value is copied on to the function's parameterfunction increase(number)
. Thenumber++
's scope is local in the function.
Thenumber++
inside the function is independent from thelet number = 10
.
Theconsole.log(number);
is basically dealing with thelet number = 10;
and not the function'snumber++
. Because they are independent.let number = 10; function increase(number) { number++; } increase(number); //← call increase() and pass the number as an argument console.log(number); // 10;
Now lets use an object
let obj = {value: 10};
function increase(obj)
is pointing to the same object (reference to the same object) aslet obj = {value: 10};
.
So these are not independent copies.let obj = {value: 10}; function increase(obj) { obj.value++; } increase(obj); console.log(obj); // {value: 11}
In object-oriented programming (OOP), a factory is an object for creating other objects – formally a factory is a function that returns objects of a varying prototype or class from some method call.
-
if an Object has 1 or more method, we call it as object has behaviour (like a person).
-
when a function creates and returns a new object, it is called a factory function. By using the factory function, we can create any number of the eg.:
circle
objects without duplicating code, and it could be several different circle objects depending on some parameter. -
eg.:
The circle object (which has no behaviour).
const circle = {
radius : 1,
};
We use factory function to create the circle object.
We add the draw()
method.
//Factory Function
function createCircle(radius){
return {
radius, //← normally it is "radius : radius", if key and value are the same name, we can just use one name.
draw: function(){
console.log("Drawing a circle");
}
};
}
const circle = createCircle(1); // (1) is the radius
circle.draw;
// ƒ (){
// console.log("Drawing a circle");
// }
Other, more in-depth example:
eg.:
This code converts RGB color to HEX colors
function hex(r, g, b) {
return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
}
// takes rgb values in the r, g, and b
hex(255, 100, 25);
//gives back:
// "#ff6419"
//another function for rgb
function rgb(r, g, b){
return `rgb(${r}, ${g}, ${b})`
}
rgb(255,100,25)
//gives:
//"rgb(255, 100, 25)"
Instead of having 2 functions and each time we have to type the R, G, B numbers, we can createa a factory function. We can pass in R, G and B.
// factory function
function makeColor(r, g, b){
// color is an empty object, so we can add stuff to it
const color = {};
// the values of the makeColor(r, g, b) we can pass in
color.r = r;
color.g = g;
color.b = b;
return color
}
makeColor(40, 50, 60);
//▶︎ Object { r: 40, g: 50, b: 60 }
We add a method in our factory function
The factory name comes from this: we pass in some values, the factory makes an object and returns it at the end, so we can use it.
// factory function
function makeColor(r, g, b){
const color = {};
color.r = r;
color.g = g;
color.b = b;
// adding a method
color.rgb = function() {
// instead of hard-coding, we add the `this ` keyword
//`this` refers to the object `const color = {};`
return `rgb(${this.r}, ${this.g}, ${this.b})`;
// or simply just destructure `const {r, g, b} = this;`
}
return color
}
const firstColor = makeColor(100,200,250);
firstColor.rgb();
//"rgb(100, 200, 250)"
💡 Check out Destructuring:
this expression makes it possible to unpack values from arrays, or properties from objects, into distinct variables.
Lets give the hex
function to our factory function
// factory function
function makeColor(r, g, b){
const color = {};
color.r = r;
color.g = g;
color.b = b;
// rgb
color.rgb = function() {
// using the destructuring assignment
const {r, g, b} = this;
return `rgb(${r}, ${g}, ${b})`;
};
//hex
color.hex = function() {
const {r, g, b} = this;
return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
}
return color
}
const firstColor = makeColor(100,200,250);
firstColor.hex();
// "#64c8fa"
It is also possible to chage the value, like the R's value
const firstColor = makeColor(100,200,250);
firstColor.r = 10
// let's check the firstColor.rgb();
firstColor.rgb();
//"rgb(10, 200, 250)"
So the factory function is a way of making objects based on a pattern or a recipe.
The factory function makes us an object const color = {}
.
const color = {}
object starts empty, but the we add some properties color.r = r; color.g = g; color.b = b;
.
Then we add some methods color.rgb = function() { ... }
and color.hex = function() { ... }
.
Then we return the color
object.
The new
operator❗️:
to create an instance of a user-defined object type or of one of the built-in object types that has a constructor function.
So Constructor functions are used to define a blueprint or a template for creating multiple objects with the same properties and methods.
-
function name starts with capital letter
function Color()
→ just indicating that it is a constructor function (function to create objects) -
inside the function no return value
-
referencing directly in the function when using
this
-
eg.:
function Color(r, g, b) { this.r = r; this.g = g; this.b = b; } Color(255, 0, 0) // undefined
But, when we call the
Color
function withnew
before the function call, it creates an object.function Color(r, g, b) { this.r = r; this.g = g; this.b = b; } // it sets the constructor of `this` object to another object // it creates a new object, set `this` // so we give R B G to that new object // return `this` at the end new Color(255, 0, 0) // we get an object: b: 0 g: 0 r: 255 //► { r: 255, g: 0, b: 0 } → that object has properties because we assigned them using the `this` (this.r = r, etc.) // ►<prototype>: → the `new` created a constructor in the __proto__ (as below Step 2. describes) // ►constructor: function Color(r, g, b)
If we don't use the
new
keyword,this
referes to the window object (check it by writingconsole.log(this)
in thefunction Color(r,g,b){...}
).When we call a function with
new
before the function call, that function will be used as a constructor.The
new
do the following things: -
step 1. Creates a blank, plain JavaScript object.;
-
step 2. Links (sets the constructor of) this object to another object → this allows us to add methods not to the individual objects, instances, but to the prototype;
-
step 3. Passes the newly created object from Step 1 as the
this
context; -
step 4. Returns
this
if the function doesn't return its own object.;
So if we call new Color(255, 0, 0)
and save that to a variable const color1
, we have an object that has RGB, but not only that. It does not have that method RGB defined on the actual object (or instance), it's defined on the prototype
. (See Step 2. above)
function Color(r, g, b) {
this.r = r;
this.g = g;
this.b = b;
}
const color1 = new Color(255, 0, 0)
color1
//►Object { r: 255, g: 0, b: 0 }
// b: 0
// g: 0
// r: 255
// ►<prototype>: Object { … }
// constructor: function Color(r, g, b) //← the RGB method is defined on the prototype
We can add different methods to the Color
prototype: we define that method outside of the constructor function (outside of the function Color(r,g,b){...}
).
So that the methods are only defined once (rather than on each individial color as in the factory function ).
Also, we can have several color obejcts
function Color(r, g, b) {
this.r = r;
this.g = g;
this.b = b;
}
//we define the method outside of the the constructor function❗️
Color.prototype.rgb = function() {
const {r, g, b} = this;
return `rgb(${r}, ${g}, ${b})`;
};
const color1 = new Color(255, 0, 0);
const color2 = new Color(10, 10, 10);
const color3 = new Color(0, 0, 0);
We define the method outside of the constructor function, so the method will be defined on the protoype..
Lets define another method, an rgba
, where a
is the opacity.
We set a
as a default of 1.0 in the function parameter.
Note that a
is the argument passed to the class method because we are not defined it on the constructor
.
In case if we want to use the a
value anywhere else then we can consider that as a
class variable and set it the constructor
.
function Color(r, g, b) {
this.r = r;
this.g = g;
this.b = b;
}
Color.prototype.rgb = function() {
const {r, g, b} = this;
return `rgb(${r}, ${g}, ${b})`;
};
Color.prototype.rgba = function(a = 1.0) {
const {r, g, b} = this;
return `rgb(${r}, ${g}, ${b}, ${a})`;
};
const color1 = new Color(255, 0, 0);
color1.rgb();
// rgb(255, 0, 0)"
color1.rgba();
// rgba(255, 0, 0, 1)
In short, constructor method is more efficient than the factory approach where we return a new object every time it is called.
Class is a template for creating objects.
We use class when we need many objects of the same type.
So class is a template for an object in the code. It makes it easier, quicker and more reliable to build several objects of the same type (called instances of the same class).
An object may be different in several ways, and each realized variation of that object is an instance. The creation of a realized instance is called instantiation.
-
Defining a Class, using the
class
keyword.- Using uppercase letter for the name
- Always add in a
constructor
. It is like aconstructor
function (we can add arguments to it).
In this example, theconstructor
is a function which executes immediately when a new color is created (instantiate a new instance). Arguments here are the constructor's(r, g, b)
- Usualy in the
constructor
we use the.this
. Sothis
refers to anew Object
(that we instantiate, like thecolor1
). - We can also add methods to the constructor, like
this.someKindOfMethod();
, so not only values likethis.value = value
.
class Color { constructor(r, g, b, name){ console.log("Constructor runs first") this.r = r; //* these are properties this.g = g; //* on the `new Color()` object this.b = b; this.name = name; //* this.r, this.name can be called anything else. Usually we give the same name. } } // new Color() object we get returned const color1 = new Color(255, 0, 0, 'red-color'); // "Constructor runs first" // ← constructor(r, g, b)
Lets run the
color1
We can see it is an object.
The properties we added in theconstructor
are storing the value on the object.color1; //► Object { r: 255, g: 0, b: 0, name: "red-color" } // b: 0 // g: 0 // name: "red-color" // r: 255 // ► <prototype>: Object { … } // constructor: class Color { constructor(r, g, b, name) }
Lets add methods! The real advantage of making classes.
The method we add (mymethod()
) is now on every color, but not on the instances. (So it's only on theprototype
, not on thecolor1
.)
In other words, there is only one copy of themymethod
method across all instances ofcolor1
, or if we have several instances, likecolor2
, etc.class Color { constructor(r, g, b, name){ this.r = r; this.g = g; this.b = b; this.name = name; } mymethod(){ return `Hello color ${this.name}!`; } } const color1 = new Color(255, 0, 0, 'red-color'); color1; //▶︎ Object { r: 255, g: 0, b: 0, name: "red-color" } // b: 0 // g: 0 // name: "red-color" // r: 255 // ▶︎<prototype>: Object { … } // constructor: class Color { constructor(r, g, b, name) } // mymethod: function mymethod()
Lets execute
color1.mymethod();
color1.mymethod(); // "Hello color red-color!"
Lets make another color
const color1 = new Color(255, 0, 0, 'red-color'); const color2 = new Color(50, 50, 50, 'grey-color'); color2.mymethod(); // "Hello color grey-color!"
Lets create another method instead of
mymethod
, and this new method gives back the RGB colors
Thethis
when we're inside of a class, it refers to the instance of the class, the individualcolor1
,color2
, etc. object..class Color { constructor(r, g, b, name){ this.r = r; this.g = g; this.b = b; this.name = name; } rgb(){ return `rgb(${this.r}, ${this.g}, ${this.b})`; } } const color1 = new Color(255, 0, 0, 'red-color'); color1.rgb(); // "rgb(255, 0, 0)"
We can add another method next to the
rgb
method, anrgba
.class Color { constructor(r, g, b, name){ this.r = r; this.g = g; this.b = b; this.name = name; } rgb(){ return `rgb(${this.r}, ${this.g}, ${this.b})`; } rgba(a = 1.0) { // set the a=1.0 as a default, since it`s not in the constructor return `rgb(${this.r}, ${this.g}, ${this.b}, ${a})`; } } const color1 = new Color(255, 0, 0, 'red-color'); color1.rgba(0.5); // "rgba(255, 0, 0, 0.5)"
Call a method within a method:
since rgb
and rgba
methods are almost the same we can refactor it, so we can create an inner function which we can call in these methods.
(We use also destructor for the sake of simplicity)
Note: the this
keyword points to that individual object (eg. color1) that was created, and any properties and methods inside it.
Since the innerRGB
is a method from that object, when we use this.innerRGB
, even inside other methods, the this
still points to the object itself, then looks for any methods with that name, finding a match.
class Color {
constructor(r, g, b, name){
this.r = r;
this.g = g;
this.b = b;
this.name = name;
}
innerRGB(){
const {r, g, b} = this; // destructor
return `${r}, ${g}, ${b} `
}
rgb(){
return `rgb(${this.innerRGB()})`; //`this` still points to the object itself
}
rgba(a = 1.0) {
return `rgb(${this.innerRGB()}, ${a})`;
}
}
So here we defined the pattern for every colour.
We are assigning properties to each color object (color1, etc.
), not to the prototype, but to the color object.
The methods inside the Class are added to the prototype automatically.
Useful links to better understand classes:
Classes in JS
Classes basics
extends
keyword is used in class declarations or class expressions to create a class that is a child of another class.
super
keyword is used to access properties on an object literal or class's [[Prototype]
], or invoke a superclass's constructor.
-
This 2 keyewords are both dealing with subclasses.
-
To understand this 2 keywords, let's go back to the basics.
eg.:
Let's create 2 very basic classes Cat
and Dog
class Cat {
constructor(name, age) {
this.name = name;
this.age = age;
}
eat() {
return `${this.name} is eating`;
}
}
const lucy = new Cat('Lucy', 9);
lucy;
//► Object { name: "Lucy", age: 9 }
lucy.eat();
// "Lucy is eating"
//other class
class Dog {
constructor(name, age) {
this.name = name;
this.age = age;
}
eat() {
return `${this.name} is eating`;
}
}
const bingo = new Dog("Bingo", 10);
bingo;
//► Object { name: "Bingo", age: 10 }
bingo.eat()
//"Bingo is eating"
Lets add some more methods meow
and bark
class Cat {
constructor(name, age) {
this.name = name;
this.age = age;
}
eat() {
return `${this.name} is eating`;
}
meow(){
return 'Meow meow!'
}
}
class Dog {
constructor(name, age) {
this.name = name;
this.age = age;
}
eat() {
return `${this.name} is eating`;
}
bark(){
return 'Woof!!!'
}
}
There is a lot of duplicated code in the 2 classes.
One option is to create a parent class (lets name it Pets
), which holds the functionalaties of the Cat
and Dog
classes.
But: we have to extend the functionality of Pets
, we have to include it to the Cat
and Dog
class, otherwise the instances of Cat
and Dog
are empty.
class Pets {
constructor(name, age) {
this.name = name;
this.age = age;
}
eat() {
return `${this.name} is eating`;
}
}
class Cat extends Pets{ // extending the Pets class functionality
meow(){
return 'Meow meow!'
}
}
class Dog extends Pets{
bark(){
return 'Woof!!!'
}
}
lucy.eat();
// "Lucy is eating"
bingo.eat();
// "Bingo is eating"
So extends
use the constructor of the "parent" class if it doesn't find a constructor on the "child" class.
If extends
find a method on the "child" class with the same name as in the "parent" class, the "child" class's methods will run.
eg.:
class Pets {
constructor(name, age) {
this.name = name;
this.age = age;
}
eat() {
return `${this.name} is eating`;
}
}
class Cat extends Pets{
meow(){
return 'Meow meow!'
}
eat() {
return `${this.name} wants some cat food.`;
}
}
lucy.eat();
// "Lucy wants some cat food."
super
to references the class that we are extending from.
When we want to add some additional property to the constructor for the "child" class, which the "parent" calss doesn't have.
eg.:
We add a color
property to the Car
class's constructor.
But we don't want to repeat the this.name
, and this.age
. We just need the this.color
. This is when we can use super
.
class Pets {
constructor(name, age) {
this.name = name;
this.age = age;
}
eat() {
return `${this.name} is eating`;
}
}
class Cat extends Pets{
constructor(name, age, color) {
super(name, age); // using super
this.color = color
}
meow(){
return 'Meow meow!'
}
eat() {
return `${this.name} wants some cat food.`;
}
}
const lucy = new Cat('Lucy', 9, 'black');
lucy;
//► Object { name: "Lucy", age: 9 , "black}