Udemy - Complete Javascript Course by Jonas Schmedtmann 강의를 듣고 각 Section별 요약을 기록.
JS 기본 문법에 대한 section.
변수 선언시 let 과 const 중 기본적으로 const 사용하는 것에 익숙해질 것. Javascript로 웹앱을 짤 때
DOM을 컨트롤하는 방식은 대부분 Event Listener를 정의, Publisher - Subscriber 패턴을 이용하므로
const로 선언. 무분별한 let 변수 사용은 코드를 지저분하게 함.
"use strict"
를 통해 strict mode activate.
accidental error 등의 에러 발견을 쉽게 해주고 디버깅을 쉽게 해줌.
show visible error. 항상 strict mode를 on 할 것!!
Expression과 Declaration은 같은 방식으로 작동한다. 그러나 가장 큰 차이점은 Declaration 방식은 함수의 정의 전에 함수를 call 할 수 있고, expression 방식은 function 값을 변수에 할당하는 방식이므로 정의 이전에 call 할 경우 not defined 문제가 발생함.
그렇다면, 둘 중 어느방식이 더 좋은가? -> 본인은 Function Expression 방식을 더 선호. (Looks more structured)
Javascript 에서는 function도 그냥 하나의 값이라고 생각하면 됨.
Function Expression 방식의 조금 더 짧은 version.
Arrow function과 this
-> Arrow function에서는 자신을 둘러싸고 있는 상위 환경의 this를 그대로 계승하는 Lexical this를 따름. 그래서 Arrow function을 객체의 method
로서 정의하면 문제가 발생함.
마찬가지의 이유로 Constructor
, addEventListener의 콜백 함수
로 사용하는 것도 불가능하다.
js 에서의 object는 key-value pair.
해당 key를 []를 사용하여 접근할 경우에는 key가 string이어야 함.
property 이름을 명확히 아는 경우에는 dot notation을 사용, property 이름을 compute 해서 얻어야 하는 경우에는 bracket notation 사용.
const exObj = {
name: 'Dohyun',
age: 28,
hobby: 'BodyBuilding',
}
const interested = prompt("What do you want to know about exObj?);
console.log(exObj[interested]); // work well.
console.log(exObj.interested); // doesn't work.
Object의 method를 정의하는 것은 일반적인 함수 정의와 같음. 다만 function 변수가 object의 key(property) 일 뿐.
this keyword 를 사용 -> Arrow function은 this나 super에 대한 바인딩이 없음. (method로 사용될 수 없다!!!)
이런식으로 method에서 새로운 property를 생성하고, 변경하는 것도 가능.
calcExFunc_Advanced: function() {
this.newProp = blahblah(calc logic) // Create new property (newProp)
return;
}
Value types - Primitive vs Reference
Primitive type
let lastName = 'Kim';
let oldLastName = lastName;
lastName = 'Moon';
console.log(lastName);
console.log(oldLastName);
결과는 다르게 출력됨. -> String 은 Primitive value.
Reference type
const bomi = {
firstName: 'Bomi',
lastName: 'Park',
age: 28,
};
const marriedBomi = bomi;
marriedBomi.lastName = 'Kim';
console.log('Before marriage', bomi);
console.log('After marriage', marriedBomi);
결과는 똑같이 출력. -> Object는 Reference value.
그렇다면 Object는 어떻게 copy? -> Object.assign
을 이용.
const bomiCopy = Object.assign({}, bomi2); // Shallow Copy. (Only works first level)
// if( restaurant.openingHours && restaurant.openingHours.mon)
// console.log(restaruant.openingHours.mon.open);
Conosle.log(restaurant.openingHours.mon?.open);
// mon이 존재하면 open 출력. (위 두줄을 간편화)
// = 기준 Right Value는 SPREAD
const arr = [1, 2, ...[3, 4]];
// = 기준 Left Value는 REST
const [a, b, ...others] = [1, 2, 3, 4, 5];
// 동시에 활용
const [pizza, salad, ...otherFoods] = [
...restaurant.mainMenu,
...restaurant.starterMenu,
];
const add = function (...numbers) {
const reducer = (acc, cur) => acc + cur;
console.log(numbers.reduce(reducer));
};
add(2, 3);
add(5, 3, 7, 2);
const x = [23, 5, 7];
add(...x); // Spread Operator
// add function에 들어가는 순간, 다시 rest 연산자에 의해 배열로 압축됨.
// Spread operator는 배열을 요소요소로 반환.
// Rest operator는 반대로 요소요소를 배열로 반환.
const wholeMenu = [...restaurant.starterMenu, ...restaurant.mainMenu];
const restaurant = {
starterMenu: ['salad', 'bread', 'soup'],
mainMenu: ['pizza', 'pasta', 'goguma'],
name: `Bonny's Pasta`,
openingHours: '9am to 8pm',
categories: ['blah', 'blahblah'],
orderDelivery({
starterIndex = 1, // default values
mainIndex = 1,
time = '19:00',
address = 'funky town',
}) {
console.log(
'Order received!',
this.starterMenu[starterIndex],
this.mainMenu[mainIndex],
time,
address
);
},
};
const deliveryObj = {
time: '22:30',
address: 'Via del Sole, 21',
mainIndex: 2,
starterIndex: 2,
};
restaurant.orderDelivery(deliveryObj);
// Object 하나만 보낸다. 그러면 fucntion에서 자체적으로 destructuring.
restaurant.orderDelivery({
mainIndex: 0,
});
const { name, openingHours, categories } = restaurant;
console.log(name, openingHours, categories);
// With new variable names
const {
name: restaurantName,
openingHours: hours,
categories: tags,
} = restaurant;
console.log(restaurantName, hours, tags);
이 외에도 Logical Operator Short Circuiting 등 여러가지 fancy한 문법이 있으나, 다른 언어에서도 활용되는 것들은 생략하였음.
First-class function은 단지 "Function도 하나의 value이다" 라는 개념을 뜻함. (JS의 특성)
Higher-Order function은 고차함수로, 하나의 함수에서 parameter로 다른 callback function을 부르거나, 함수가 다른 새로운 함수를 return 하는 경우를 뜻함.
First-class function은 그냥 concept의 개념임. (no practice!!),
High-order function은 JavaScript가 First-class function을 지원하기 때문에 가능.
JS uses callback all the time
Why??? -> PROS of ABSTRACTION!!!
const high5 = function () {
// Callback function
console.log('🖐');
};
document.body.addEventListener('click', high5); // High-order function
Closure란, Execution Context(function 실행 중)에 생성된 function은,
그 function을 생성시킨 execution context(모함수)의 variable에 접근할 수 있다!!
// Closure function
cosnt greet = function (greeting) {
return function(name) {
console.log(`${greeting} ${name}`);
};
};
// Arrow를 이용해서도 표현 가능.(but more confusing)
const greetArrow = greeting => name => console.log(`${greeting} ${name}`);
변수의 Scope와 관련되어 활용할 수 있는 영역이 많다. (함수를 여러 번 호출 시 상태가 연속적으로 유지되어야 할 때, React의 hook API가 Closure를 통해 구현됨.)
const secureBooking = function () {
let passengerCount = 0;
return function () {
passengerCount++;
console.log(`${passengerCount} passengers.`);
};
};
const booker = secureBooking(); // 이 때, Closure 발생!!! (booker function으로 모든 변수들이 넘어옴.)
booker();
위의 경우, booker function은 secureBooking의 Execution Context(즉 실행 중) 생성되었다.
따라서 booker function은 passengerCount 변수에 접근할 수 있다.
secureBooking 함수가 종료되면, execution context는 사라지고 끝나지만, variable environment (변수 등)은 booker 함수에게로 넘어간다.
이러한 연결을 Closure 라고 한다.
const Asiana = {
airline: 'Asiana',
iataCode: 'KR',
bookings: [],
// book: function(flightNum, name) {} //아래와 같은 표현.
book(flightNum, name) {
console.log(
`${name} booked a seat on ${this.airline} flight ${this.iataCode}${flightNum}`
);
this.bookings.push({flight: `${this.iataCode}${flightNum}`, name});
},
};
Asiana.book(415, 'Dohyun Kim');
// Call Method
cosnt book = Asiana.book;
book.call(315, 'Dohyun Kim'); // Doesn't Work!! (Regular function call 에서는 this가 undefined.)
book.call(Asiana, 315, 'Dohyun Kim'); // Works well. call method의 첫번째 인자로 this가 가리킬 것을 전달함.
// Apply Method (Call Method와 동일하나, this keyword 이후 param에 대해 배열로 전달한다.)
const flightData = [315, 'Dohyun Kim'];
book.apply(Asiana, flightData);
// Apply method는 ES6에서 거의 쓰지 않음. 대신, 아래와 같은 방식으로 spread operator 활용.
book.call(Asiana, ...flightData);
// Bind Method (중요!!)
const bookAsiana = book.bind(Asiana); // Asiana를 this keyword에 바인딩.
bookAsiana(315, 'Dohyun Kim'); // Looks like normal function call. this keyword를 바인딩 시켜놨음.
const bookAsianaMore = book.bind(Asiana, 315); // bind method는 this keyword 외에도 추가적으로 parameter를 binding 할 수 있음.
bookAsianaMore('Dohyun Kim'); // Only need a Name.
Bind method의 좋은 사용 예
const btnClick = Asiana.buyPlane.bind(Asiana);
document.querySelector('.buy').addEventListener('click', btnClick);
// this keyword가 바인딩 되어 전달됨. (this === Asiana object)
IIFE pattern은 최초 실행 후 딱 한번만 호출해야 할 경우 사용.
(function() {
console.log('This code Only run Once');
})();
// or
(() => {
console.log('This code Only run Once');
})();
Abstraction (추상화)
ignore or to hide detail.
Encapsulation (캡슐화)
keep some properties and methods Private inside the class. (Not accessible outside of class)
but some methods can be exposed as a public interface (API)
Inheritance
Polymorphism
Classical OOP: Class -> instances (Instaciation)
OOP in Javascript : Prototype <- Object (delegated)
Constructor functions : Technique to create obejcts from a function
절대로 Constructor function 안에서 새로운 method를 생성하지 말 것!!! (Terrible performance)
const dohyun = new Person('Dohyun', 1995);
1. New {} is created
2. function is called, this = {}
3. {} linked to prototype
4. function automatically return {}
ES6 Classes : Do not behave like classes in "Classical OOP" (Just nicer syntax)
ES6 의 Class는 그저 특별한 종류의 function 이라고 생각하면 됨.
const PersonCl = class {}
// class Declaration (Nicer)
class PersonCl {
constructor(fullName, birthYear) {
this.fullName = fullName;
this.birthYear = birthYear;
}
// Methods
calcAge() {
} ...
}
Object.create() : Easiest, most straight-forward. (But not usual)
const PersonProto = {
calcAge() {
console.log(2037 - this.birthYear);
},
init(firstName, birthYear){
this.firstName = firstName;
this.birthYear = birthYear;
},
};
const dohyun = Object.create(PersonProto)
dohyun.init = ('Dohyun', 1995);
dohyun.calcAge();
AJAX 는 Async Javascript And Xml
그러나 xml은 옛 방식이고 요즘은 전부 JSON으로 주고받음. ..-> AJAJ ...?😅
흔히 착각할 수 있는 것
Callback function이나 EventListener는 그 자체로 절때 Async를 유발하지 않는다!!
(예를 들어 이미지 로드 후 Load에 따른 이벤트리스너를 정의한다면 이미지가 로딩되는 자체가 async이지, 이벤트리스너가 async인 것이 아님!)
const exAJAXCall = function (param) {
// AJAX call 1
const request = new XMLHttpRequest();
request.open('GET', `https://blahblah.com/blah/blah/${queryData}`);
request.send();
request.addEventListener('load', function() {
const [data] = JSON.parse(this.responseText);
// 이후 data를 활용.
const request2 = new XMLHttpRequest();
// AJAX call 2
request2.open('GET', `https://blah~~/${queryData}`);
request2.send();
request2.addEventListener('load', function() {
const data2 = JSON.parse(this.responseText);
// 이후 data2를 활용.
});
});
};
// Works all Parallel
exAJAXCall('data1');
exAJAXCall('data2');
exAJAXCall('data3');
AJAX call은 이런식으로 Chaining 기법을 활용한다. (AJAX call 1 -> 2이 순차적으로 실행됨.)
그러나 Old School Way 에서는 Callback 함수 안에 또다른 Callback 함수가 들어가 있는 방식을 사용하며,
이러한 방식은 Nested callback 구조가 10개던 20개던 끝없이 반복되고
Callback hell 이라 불리우는 지저분한 코드를 유발한다.
(코드의 가독성도 떨어지고 보기 좋지 않음. 그러나 동작 또는 실행에 문제가 발생하는 것은 아님.)
-> ES6 에서는 다행히도 이에 대한 대처방안으로 fetch
와 Promise
라는 것을 통해 callback을 탈출할 수 있게끔 하였다.
const request = fetch('https://blahblah.com/blah/blah/${queryData}');
console.log(request); // fetch는 promise 를 리턴함.
promise 는 event handler의 callback 지옥으로부터 벗어나게 해준다.
nested callback 대신 promise를 chaining 할 수 있음.
Promise 단계
1. Pending ( Before the future value is available)
2. Settled (Async task has finished) only settled one
3-1. Fullfilled (Success! The value is now available)
3-2. Rejected (An error happend! The value is now unavailable)
const exAJAXCall = function(param) {
fetch(`https://blahblah/${param}`)
// then은 promise의 결과가 fullfilled일 때 실행됨.
.then(function (response) {
if(!response.ok) throw new Error('Problem with blahbalh');
return response.json(); // All of the resolved value from fetch function.
// 이것을 return 함으로써, 그 뒤에 또 then 메서드를 달아 chaining 할 수 있다.
})
.then(function (data) {
//data 활용
})
.catch(err => renderError(err)); // catch는 rejected 상태일때 실행됨.
.finally(() => {Element.style.opacity = 1;}); // finally는 fullfilled일 때나 reject 이거나 관계없이 실행됨.
};
fetch는 promise를 리턴. 그로부터 response를 받는 then 메서드 사용. response를 data화 할 수 있는 json 메서드 사용.
then 메서드가 또다시 promise를 return 하기 때문에 이것을 연결하여 또다시 then 메서드 사용. (Chaining)
Await은 Async 함수 내부에서만 사용할 수 있다.
Await을 사용하면, then 메서드처럼 promise를 받아올 때 까지 기다려줌. (순차적 진행이 가능하다.)
Even better & easier way to consume Promise!
const exFunc1 = function() {
return new Promise(function (resolve, reject) {
// resolve, reject 일 경우 각각 처리
});
};
const exAsyncFunc = async function() {
try {
const exval = await exFunc1();
const exval2 = await fetch(`https://blahblah~`);
if (!exval2.ok) throw new Error(`err message`);
} catch (err) {
renderError(err)
}
Other Promise Combinators: race, allSettled, and any
async function() {
const res = await Promise.race([
getJSON(`${url}/data1`);
getJSON(`${url}/data2`);
getJSON(`${url}/data3`);
]);
}
fullfilled 인지 reject 인지 관계없이 제일 빠르게 응답한 순서대로 리턴됨.
사용 예시
const timeout = function(sec) {
return new Promise(function (_, reject) {
setTimeout(function () {
reject (new Error(`Request took too long!!`));
}, sec * 1000);
});
};
Promise.race([
getJSON(`${url}/data`),
timeout(5),
]).then(res => console.log(res[0])
.catch(err => console.error(err));
query한 결과가 5초가 지나도 응답 없으면 timeout으로 처리하고 error를 발생시킴.
간단히 생각해서 5초 타이머랑 getJSON 메서드랑 경주 시킨것임.
Promise.allSetteled([
Promise.resolve('Success'),
Promise.reject('Error'),
Promise.resolve('Another Success'),
]).then(res => console.log(res))
.catch(err => console.log(err));
전부 다 출력.
npx parcel index.html
로컬에서 빌드 없이 그냥 netlify에서 바로 배포할 수 있음.
package.json 파일만 만들고, git repository에 푸시해둔 다음,
Netlify에서 repo 연동하고
base directory: ./15-Mapty/starter
build command: package.json
에 명시된 build command 그대로 입력(parcel build index.html --dist-dir ./dist) (build command 입력하면 CI/CD 환경에서 package.json 파일을 보고 의존성 패키지들을 알아서 설치.)
publish directory: ./15-Mapty/starter/dist
NPM(Node Package Manager) 라는 것을 통해 package와 모듈들을 관리할 수 있음.
Packaging 하는 과정은 다음과 같다.
npm init
-> package.json 파일이 생성됨.npm install (node module명)
(ex-leaflet, lodash-es, ...)npm install
을 입력하면 package.json파일에 적혀있는 dependency 항목의 패키지/모듈들을 알아서 설치.