Hoisting***
JavaScript Hoisting refers to the process whereby the interpreter appears to move the declaration of functions, variables or classes to the top of their scope, prior to execution of the code.
var a = "first";
var b = "second";
var c = "third";
var d;
console.log(a);
console.log(b);
console.log(c);
console.log(d);
// first
// second
// third
// undefined
'd' is undefined because there is no value assigned to it here.
var a = "first";
var b = "second";
var c = "third";
console.log(a);
console.log(b);
console.log(c);
console.log(d);
var d = "fourth";
// first
// second
// third
// undefined
However here, 'd' has a value assigned to it but it's still undefined. WHY? It's because it only takes declarations and take them to the top of the document.
var a = "first";
var b = "second";
var c = "third";
d = "fourth"
console.log(d);
var d;
// fourth
Here, it console.log 'd' because when we declared variable, it gets hoisted to the top and because we also assigned a value to it before console.log() happens.
So it sees this as a valid code. This is what we call 'Hoisting'
Declarations in JS get hoisted. (Declaration happens first then saved in the memory after).
+ Let's see what happens with function as well.
console.log(add1(10, 20));
console.log(add2(10, 20));
// The function below goes all the way up because of hoisting!
function add1(x, y) {
return x + y;
}
Because it's read from top to bottom in JavaScript, you might think the console.log() won't be executed. Because it's written before the function. However, the function add1(x,y)
goes to the top because of hoisting and console.log() will be executed.
Named function in the same way as a variable gets taken out of the code and get placed to the top or hoisted to the top of the file. So we can use it at any point inside our code.
console.log(multiply(10, 20));
let multiply = function (a, b) {
return a * b;
};
// Uncaught ReferenceError: multiply is not defined (error)
But if you use function expression like above code (assign function to variable), only the variable will go to the top because of hoisting(read what I wrote above. It takes variables to the top only not the values). So, the function won't go up.(won't hoist the function in this case because it's assigned to variable as a value). Therefore, the console.log() won't be executed. It's the same with arrow function.
console.log(multiply(10, 20));
const multiply = (a, b) => a * b;
This also gets an error because arrow function works like function expression above.
Video that helps to understand Hoisting
About hosting on MDN
How we avoid hoisting?
Don't create variables out in the root of file as long as it's unnecessary and instead, put them inside function if you are going to use the variables inside the function.
Then, how do we avoid function being hoisted?
So it's where it comes 'anonymous function'. Anonymous function doesn't have a name attached to it. So, the way we do it is by creating variable and assigning it to the function.
const sayA = function() {
let a = 20;
return a;
}
console.log(sayA());
console.log(sayA());
const sayA = function() {
let a = 20;
return a;
}
// if you move the function to below, like this then you can check that it gets an error. because the browser doesn't know if there is a function because it's not named function.
IIFE(Immediately Invoked Function Expression)
IIFE is a function that runs as soon as it is defined(like its name).
Syntax
(function () {
statements
})();
You just have to wrap the function with '()' -parentheses to create IIFE.
Pros using IIFE
- Self(auto)-invocation (It runs as soon as it's defined)
- It's function scope. (function or variable defined inside IIFE, cannot be accessed outside the IIFE block)
- It's memory efficient.
// Anonymous IIFE.
(function () {
let a = 1;
let b = 2;
return a+ b;
}, ());
// It a code that starts the first.
// Usually use this when there is code that needs to be start as soon as it renders.
// Named IIFE
(function foo() {
let a = 3;
let b = 5;
return a * b;
}());
// This does NOT work
foo(); // ReferenceError: foo is not defined
Call by value / Call by reference
When you call function and pass argument to it, it doesn't pass the original value itself but pass copied version of the value as function. Thus, when the value get changed within the function, it doesn't affect the original value.
let x = [10, 20, 30];
function test(a) {
// 'a' is parameter
// when 'a' received the value, it gets the copy of address of x value
// 'a' points [10, 20, 30]
a[0] = 10000;
}
test(x); // 'x' is argument
x;
//[(10000, 20, 30)];
let x = 10;
function test(a) { // 'x' points 10
a = 10000; // but 'a' points 10000
}
test(x); // x is argument
x;
// 10
// 'x' points 10 so it returns 10
///
Video for better understanding: JavaScript Value vs Reference Types
map()
map() method creates a new array populated with the results of calling a provided function on every element in the calling array.
Example
let array = [1, 4, 9, 16];
let valueOne = array.map((x) => x * 2);
array;
//[ 1, 4, 9, 16]
valueOne;
// [2, 8, 18, 32]
function square(x) {
return x ** 2;
}
valueTwo = array.map(square);
// [1, 16, 81, 256]
array;
// [1, 4, 9, 16]
valueTwo;
// [1, 16, 81, 256]
Using map() with object
let data = [
{
class: 1,
number: 1,
name: "Chloe",
"test result": 91,
},
{
class: 1,
number: 2,
name: "James",
"test result": 70,
},
{
class: 1,
number: 3,
name: "Kate",
"test result": 64,
},
{
class: 1,
number: 4,
name: "Mark",
"test result": 89,
},
{
class: 1,
number: 5,
name: "Jenny",
"test result": 100,
},
];
data.map((num) => num["test result"]);
// [91, 70, 64, 89, 100]
// 0: 91
// 1: 70
// 2: 64
// 3: 89
// 4: 100
let sum = 0;
data.map((x) => x["test result"]).forEach((y) => (sum += y));
sum;
// 414
Using map() with array
let array = [1, 2, 3, 4];
let result = [];
let data = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
];
let newData = data.map((a) => a.map((n) => n * 2));
newData;
// [Array(3), Array(3), Array(3)]
// 0: [2, 4, 6]
// 1: [8, 10, 12]
// 2: [14, 16, 18]
More about map()
filter()
filter() method creates a new array with all elements that pass the test implemented by the provided function.
const words = [
"good",
"nice",
"magnificent",
"awesome",
"spectacular",
"amazing",
"cool",
];
const result = words.filter((word) => word.length > 7);
console.log(result);
// ['magnificent', 'spectacular']
let x = [2, 6, 5, 4, 8, 5, 4, 3, 2, 9];
// numbers that are greater than 5 from array x
const result = x.filter((num) => num > 5);
result;
// [6, 8, 9]
Method chaining
Method chaining is the mechanism of calling a method on another method of the same object. This ensures a cleaner and readable code. Method chaining uses this keyword in the object's class to access its methods. In javascript, the this keyword refers to the current object in which it is called
'1001001010'.replace('1', '#').replace('0', ' ')
// '# 01001010'
'1001001010'.replace(/1/g, '#').replace(/0/g, ' ')
// '# # # # 's
// let array = [1, 4, 9, 16]
// method chaining inside map()
// square root: Math.sqrt()
array.map(Math.sqrt);
// [1, 2, 3, 4]
array.map(Math.sqrt).map((x) => x ** 3);
//(4) [1, 8, 27, 64]
array;
// (4) [1, 4, 9, 16]
array.map((x) => x ** 0.5);
// (4) [1, 2, 3, 4]
method chaining practice
let userInfo = [
{
id: "jjang",
pw:
"5dbfbded05f34de754e8f265448be17934580556b488c0461fd12d211a87aaa5",
gender: "male",
phone: "010-5004-0000",
email: "hojun1@gmail.com",
"joined date": "2020-12-02",
location: "125.242.161.149",
},
{
id: "jjang2",
pw:
"5dbfbded05f34de754e8f265448be17934580556b488c0461fd12d211a87aaa5",
gender: "male",
phone: "010-5004-0000",
email: "hojun2@gmail.com",
"joined date": "2021-12-02",
location: "125.242.161.149",
},
{
id: "jjang3",
pw:
"5dbfbded05f34de754e8f265448be17934580556b488c0461fd12d211a87aaa5",
gender: "female",
phone: "010-5004-0000",
email: "hojun3@gmail.com",
"joined date": "2021-12-02",
location: "125.242.161.149",
},
{
id: "jjang4",
pw:
"5dbfbded05f34de754e8f265448be17934580556b488c0461fd12d211a87aaa5",
gender: "female",
phone: "010-5004-0000",
email: "hojun4@gmail.com",
"joined date": "2020-12-02",
location: "125.242.161.149",
},
];
// filter male only
userInfo.filter((el) => el.gender === "male");
// filter user who's male and joined in 2021
userInfo.filter(
(el) => el.gender === "male" && el["joined date"].split("-")[0] === "2021");
// filter who's id is "jjang"
// userInfo.filter((el) => el.id === "jjang");
userInfo.find((user) => user.아이디 === "jjang");
// find stops when it finds the one looking for (good for memory efficiency)
** When id or password doesn't match
Do not let the user know "ID doesn't match" or "PW doesn't match". Because if you do, then you are letting someone know which one isn't matching. This is not good for security.
Instead, do it like "ID or PW don't match" so the person who sees this doesn't know which one isn't matching exactly.(This is for security reason)
more practice
let blogs = [
{
id: 1,
title: "title1",
content: "content1",
section: "daily life",
},
{
id: 2,
title: "title2",
content: "content2",
section: "hobby",
},
{
id: 3,
title: "title3",
content: "content3",
section: "web development",
},
{
id: 4,
title: "title4",
content: "content4",
section: "web development",
},
];
let sectionName = "web development";
let data = "web development" ? blogs.filter((el) => el.section === sectionName) : blogs;
data;
// [{…}, {…}]
// 0: {id: 3, title: 'title3', content: 'content3', section: 'web development'}
// 1: {id: 4, title: 'title4', content: 'content4', section: 'web development'}
// when there is error or can't find, you should show a message saying you can't find or return all the contents
Object
let obj = {
keyA: "value of A",
keyB: "value of B",
};
Object.entries(obj);
// [Array(2), Array(2)]
// 0: (2) ['keyA', 'value of A']
// 1: (2) ['keyB', 'value of B']
Object.keys(obj);
// ['keyA', 'keyB']
// 0: "keyA"
// 1: "keyB"
Object.values(obj);
// ['value of A', 'value of B']
// 0: "value of A"
// 1: "value of B"
Object.is(0, -0);
// false
for (let i of obj) {
console.log(i);
}
// Uncaught TypeError: obj is not iterable.
// You cannot iterate object in JS. You can do that only with for...in
for (let i in obj) {
console.log(i);
}
// keyA
// keyB
Object.entries()
Object.keys()
Object.values()
Map & Set
The Map() constructor creates Map objects.
Till now, we’ve learned about the following complex data structures:
- Objects are used for storing keyed collections.
- Arrays are used for storing ordered collections.
Map
Map is a collection of keyed data items, just like an Object. But the main difference is that _**Map allows keys of any type.
Methods of Map
- new Map(): creates the map.
- map.set(key, value): stores the value by the key.
- map.get(key): returns the value by the key, undefined if key doesn’t exist in map.
- map.has(key): returns true if the key exists, false otherwise.
- map.delete(key): removes the value by the key.
- map.clear(): removes everything from the map.
- map.size: returns the current element count.(length)
let map = new Map();
map.set('1', 'str1'); // a string key
map.set(1, 'num1'); // a numeric key
map.set(true, 'bool1'); // a boolean key
// remember the regular Object? it would convert keys to string
// Map keeps the type, so these two are different:
console.log(map.get(1)); // 'num1' // using index
console.log(map.get('1')); // 'str1' // using key
console.log(map.size); // 3
As you can see above, unlike objects, keys are not converted to strings. Any type of key is possible.
NOTE: map[key] isn’t the right way to use a Map
Although map[key] also works, e.g. we can set map[key] = 2, this is treating map as a plain JavaScript object, so it implies all corresponding limitations (only string/symbol keys and so on).
So we should use map methods: set, get and so on.
Iteration over Map
For looping over a map, there are 3 methods:
- map.keys() – returns an iterable for keys,
- map.values() – returns an iterable for values,
- map.entries() – returns an iterable for entries [key, value], it’s used by default in for..of.
Set
A Set is a special type collection – "set of values" (without keys), where each value may occur only once.
Method of Set
- new Set(iterable): creates the set, and if an iterable - object is provided (usually an array), copies values from it into the set.
- set.add(value): adds a value, returns the set itself.
- set.delete(value): removes the value, returns true if value existed at the moment of the call, otherwise false.
- set.has(value): returns true if the value exists in the set, otherwise false.
- set.clear(): removes everything from the set.
- set.size: is the elements count.
The main feature is that repeated calls of set.add(value) with the same value don’t do anything. That’s the reason why each value appears in a Set only once.
For example, we have visitors coming, and we’d like to remember everyone. But repeated visits should not lead to duplicates. A visitor must be "counted" only once.
Set is just the right thing for that:
let set = new Set();
let hana = { name: "Hana" };
let lilly = { name: "Lilly" };
let hailey = { name: "Hailey" };
// visits, some users come multiple times
set.add(hana);
set.add(lilly);
set.add(hailey);
set.add(lilly);
set.add(hailey);
//Set(3) {{…}, {…}, {…}}
// set keeps only unique values
console.log(set.size);
// 3
for (let user of set) {
console.log(user.name);
}
// Hana
// Lilly
// Hailey
Iteration over Set
We can loop over a set either with for..of or using forEach
let set = new Set(["blue", "black", "white"]);
for (let value of set) console.log(value);
// blue
// black
// white
// the same with forEach:
set.forEach((value, valueAgain, set) => {
console.log(value);
});
// blue
// black
// white
practice map
let m = new Map();
m.set("first", "one");
m.set("second", "two");
m.set("third", "three");
m;
// Map(3) {'first' => 'one', 'second' => 'two', 'third' => 'three'}
// [[Entries]]
// 0: {"first" => "one"}
// 1: {"second" => "two"}
// 2: {"third" => "three"}
m.get("first");
// 'one'
m.get("second");
// 'two'
m.get("third");
// 'three'
m.keys();
// MapIterator {'first', 'second', 'third'}
// [[Entries]]
// 0: "first"
// 1: "second"
// 2: "third"
m.set("first", "one!!");
// Map(3) {'first' => 'one!!', 'second' => 'two', 'third' => 'three'}
// [[Entries]]
// 0: {"first" => "one!!"}
// 1: {"second" => "two"}
// 2: {"third" => "three"}
m.values();
// MapIterator {'one!!', 'two', 'three'}
// [[Entries]]
// 0: "one!!"
// 1: "two"
// 2: "three"
let n = new Number(5);
n;
//Number {5}
n = 5;
n;
// 5
let a = new Array([1, 2, 3]);
a;
// [Array(3)]
typeof a;
// 'object' // let a = new Array([1, 2, 3]);
typeof n;
// 'number' // assigned number, n = 5;
typeof m;
// 'object' // let m = new Map();
m.has("first");
// true
m.delete("first");
// true
m;
// Map(2) {'second' => 'two', 'third' => 'three'}
m;
// Map(2) {'second' => 'two', 'third' => 'three'}
for (let i of m) {
console.log(i);
}
//(2) ['second', 'two']
//(2) ['third', 'three']
// You can't iterate object with for of but m is object created with new Map() and it's iterable as you can see above.
let obj1 = {
keyA: "value of A",
keyB: "value of B",
};
Object.entries(obj1);
Object.length(obj1)
// Uncaught TypeError: Object.length is not a function
Object.keys(obj1).length;
// 2
m.size; // it's to find out the length
// 2
m.entries();
// MapIterator {'second' => 'two', 'third' => 'three'}
let temp = new Map([
[1, 10],
[2, 20],
[3, 30],
[4, 40],
]);
temp;
// Map(4) {1 => 10, 2 => 20, 3 => 30, 4 => 40}
temp.size;
// 4
let temp = new Map(Object.entries(obj1));
temp;
// Map(2) {'keyA' => 'value of A', 'keyB' => 'value of B'}
temp.size;
// 2
let mm = new Map();
mm.set("one", 1);
// Map(1) {'one' => 1}
mm.set("two", { one: 1, two: 2 });
// Map(2) {'one' => 1, 'two' => {…}}
// [[Entries]]
// 0: {"one" => 1}
// 1: {"two" => Object}
mm;
// Map(2) {'one' => 1, 'two' => {…}}
// [[Entries]]
// 0: {"one" => 1}
// key: "one"
// value: 1
// 1: {"two" => Object}
// key: "two"
// value:
// one: 1
// two: 2
mm.set([1, 2, 3], "three");
// Map(3) {'one' => 1, 'two' => {…}, Array(3) => 'three'}
// [[Entries]]
// 0: {"one" => 1}
// 1: {"two" => Object}
// 2: {Array(3) => "three"}
practice set
let s = new Set("abcdeeeeeeeee");
console.log(s);
// {'a', 'b', 'c', 'd', 'e'}
console.log(s.size);
// 3 5
s.add("f");
// Set(6) {'a', 'b', 'c', 'd', 'e', …}[[Entries]]0: "a"1: "b"2: "c"3: "d"4: "e"5: "f" size: 6[[Prototype]]: Set
// Set is not an ordered abstract data structure.
s.delete("b");
//true
s;
// Set(5) {'a', 'c', 'd', 'e', 'f'}
s.has("c");
// true
s.size;
// 5
s.forEach((i) => console.log(i));
// a
// c
// d
// e
// f
let a = new Set("abc");
let b = new Set("cde");
a;
// Set(3) {'a', 'b', 'c'}
b;
// Set(3) {'c', 'd', 'e'}
// Intersection
[...a].filter((value) => b.has(value));
// ['c']
a | b;
// 0
a & b;
// 0
a || b;
// Set(3) {'a', 'b', 'c'}[[Entries]]0: "a"1: "b"2: "c"size: 3[[Prototype]]: Set
a && b;
// Set(3) {'c', 'd', 'e'}
a + b;
// '[object Set][object Set]'
let union = new Set([...a].concat(...b));
union;
// Set(5) {'a', 'b', 'c', 'd', 'e'}
[...a, ...b];
// (6) ['a', 'b', 'c', 'c', 'd', 'e']
more about Map & Set
Spread
let numbers = [0, 1, 2];
let newNum = 12;
numbers = [...numbers, newNum];
numbers;
// [0, 1, 2, 12]
Rest
function checkRest(x, y, z) {
console.log(x, y, z);
}
let numbersArr = [0, 1, 2];
checkRest(numbersArr);
// [0, 1, 2]
checkRest(...numbersArr);
// 0, 1, 2
function restFunc(v, w, x, y, z) {
console.log(v,w,x,y,z);
}
let arr = [0, 1];
restFunc(-1, ...arr, 2, ...[3]);
// -1 0 1 2 3
more about spread & rest
Top comments (0)