diff --git "a/hyunmin/ZeroBase/\354\236\205\353\254\270\354\236\220 \354\236\220\353\260\224\354\212\244\355\201\254\353\246\275\355\212\270/01. JS-\353\215\260\354\235\264\355\204\260.md" "b/hyunmin/ZeroBase/\354\236\205\353\254\270\354\236\220 \354\236\220\353\260\224\354\212\244\355\201\254\353\246\275\355\212\270/01. JS-\353\215\260\354\235\264\355\204\260.md" new file mode 100644 index 0000000..6cef046 --- /dev/null +++ "b/hyunmin/ZeroBase/\354\236\205\353\254\270\354\236\220 \354\236\220\353\260\224\354\212\244\355\201\254\353\246\275\355\212\270/01. JS-\353\215\260\354\235\264\355\204\260.md" @@ -0,0 +1,307 @@ +> 제로베이스 자바스크립트 기초개념 JS 데이터 부분 정리 +> 축약된 부분이 존재할 수 있습니다. + +# 원시형 데이터 + +### 문자 데이터 + +JS에서 문자 데이터를 만드는 방법은 세 가지가 존재한다. + +> " ", ' ', \` \` + +백틱 같은 경우에는 문자 데이터 중간에 변수를 보간할 수 있다. + +```js +const s1 = "황현민"; +const s2 = "18살"; + +// 템플릿 리터럴 +// 이런식으로 중간에 값을 넣는 것을 데이터 보간이라고 +const s3 = `안녕하세요! 제 이름은 ${s1}이고 나이는 ${s2}입니다.`; +``` + +### 숫자 데이터 + +JS에서 숫자 데이터는 정수 및 부동소수점 숫자를 나타낸다. + +```JS +const n1 = 123 +const n2 = 123.1233 + +// 만약 산술연산을 다른 데이터와 하게 된다면? +const n3 = 123 + "안녕" // 문자 데이터를 우선해서 문자 데이터가 된다. +const n4 = 123 + undefined // NaN (숫자가 아닌 숫자)가 나온다. +``` + +### 불린(boolean) 데이터 + +boolean은 true와 false를 가지는 참/거짓 논리 데이터이다. + +```js +const a = true; +const b = false; +``` + +### null 데이터 + +null 데이터는 존재하지 않는(noting), 비어 있는(empty), 알 수 없는(unknown) 값을 명시적으로 나타낸다. + +```JS +let age = null +``` + +### undefined 데이터 + +undefined 데이터는 값이 할당되지 않은 상태를 나타낼 때 사용한다. +변수는 선언했지만, 값을 할당하지 않았다면 해당 변수에 undefined가 암시적으로 사용된다. + +```js +let age; +console.log(age); +``` + +# 참조형 데이터 + +### 배열 데이터 + +배열(array) 데이터는 순서가 있는 여러 데이터의 집합이다. +배열에 포함 각 데이터는 아이템(Item)혹은 요소(Element)라고 부른다. + +```js +const f = ["사과", "수박", "메론"]; +const n = [123, 456, 789]; + +// 배열으 길이 확인 +console.log(f.length); // 3 +console.log(n.length); // 5 + +// 배열의 아이템 번호(index)로 아이템을 확인 (indexing) +// 숫자는 0부터 시작한다. (Zero-Base-Numbering) +console.log(f[2]); // 메론 +console.log(n[1]); // 456 + +// 배열의 모든 아이템을 순회하고 싶다? +for (let i = 0; i < f.length; i += 1) { + console.log(f[i]); +} +for (let i = 0; i < n.length; i += 1) { + console.log(n[i]); +} +``` + +### 객체 데이터 + +객체(object) 데이터는 순서가 없는 Key(키)와 Value(값)의 쌍으로 어우러진 데이터 집합이다. +객체에 포함된 각 데이터를 속성(Property)라고 부르고, +만약 그 데이터가 함수인 경우에는, 메소드(method)라고 부른다. + +```js +const user = { + name: "현민", + age: 18, + isValid: true, + email: "gyejeongjin@gmail.com", + hello: function () { + return `내 이름은 ${this.name}이다. 내 나이는 ${this.age}`; + }, +}; + +// 점 표기법(Dot Notation)을 사용해, 객체의 속성이나 메소드에 접근할 수 있다. +console.log(user.name); +console.log(user.age); +console.log(user.isValid); +console.log(user.email); +console.log(user.hello()); + +// 대괄호 표기법(Bracket Notation)을 사용해, 객체의 속성이나 메소드에 접근할 수 있다. +console.log(user["name"]); +console.log(user["age"]); +console.log(user["isValid"]); +console.log(user["email"]); +console.log(user["hello"]()); + +// 대괄호 표기법은 좀 더 동적으로 사용 할 수 있다. +const key = "name"; +console.log(user[key]); +``` + +### 함수 데이터 + +함수(Function) 데이터, 어떤 작업을 수행하기 위해 필요한 여러 코드의 집합으로, 코드를 추상화하고 재사용성을 확보한다. +이 함수를 자바스크립트에서는 하나의 데이터 종류로 취급한다. + +```js +// 함수 선언문(Declaration) +function add(a, b) { + // console.log(a); + // console.log(b); + return a + b; +} + +console.log(add); +console.log(add(1, 2)); +console.log(add(31, 14)); +console.log(add(5, 8)); + +// 함수 표현식 +const sub = function (a, b) { + return a - b; +}; + +console.log(sub); +console.log(sub(2, 1)); +console.log(sub(14, 10)); +console.log(sub(8, 5)); +``` + +# 변수 + +변수(Variable)란, 데이터(값)의 이름을 지정한 것이다. +이름이 있으면, 그 이름으로 언제든지 데이터를 재사용 할 수 있다. + +```js +// const 키워드는 상수(Constant)를 의미하며, 한 번 선언하면 다른 값으로 변경 할 수 없다. +const c = 12; +console.log(c); +console.log(c); +// c = 34; // Error: Assignment to constant variable. +// JS는 Error가 발생하면 아래 코드를 싹다 무시한다. + +// let 키워드는 선언한 값을 다른 값으로 바꿀 수 있다. +let l = 12; +console.log(l); +console.log(l); +l = 13; +console.log(l); +console.log(l); +``` + +일단 기본적으로 const를 사용하고 값을 바꿔야 한다면 let으로 바꿔서 사용하면 된다. + +# 형 변환 + +형 변환(Type Convension)이란, 데이터가 상황에 따라 적절한 데이터 타입(자료형)으로 변환되는 것을 말한다. + +```js +const a = 1; +const b = "1"; + +// == 동등 연산자 +console.log("동등", a == b); // true +// === 일치 연산자 +cnsole.log("일치", a === b); // false + +// 다음 코드는 모두 true를 출력합니다. +console.log("================="); +console.log(123 == "123"); +console.log(1 == true); +console.log(0 == false); +console.log(null == undefined); +console.log("" == false); +// 동등연산자는 비교하는 개념에서는 명확하지 않아 쓰는걸 권장하지 않는다. + +// 다음 코드는 모두 false 출력합니다. +console.log("================="); +console.log(123 === "123"); +console.log(1 === true); +console.log(0 === false); +console.log(null === undefined); +console.log("" === false); +``` + +# 참(Truthy)과 거짓(Falsy) + +```js +// '참'으로 평가되는 값 (Truthy) +if (true) { + console.log("참!"); +} +if ({}) { + console.log("참!"); +} +if ([]) { + console.log("참!"); +} +if (42) { + console.log("참!"); +} +if ("0") { + console.log("참!"); +} +if ("false") { + console.log("참!"); +} +if (new Date()) { + console.log("참!"); +} +if (-42) { + console.log("참!"); +} +if (12n) { + console.log("참!"); +} +if (3.14) { + console.log("참!"); +} +if (-3.14) { + console.log("참!"); +} +if (Infinity) { + console.log("참!"); +} +if (-Infinity) { + console.log("참!"); +} +// ... + +// '거짓'으로 평가되는 값 (Falsy) +if (false) { + console.log("거짓.."); +} +if (null) { + console.log("거짓.."); +} +if (undefined) { + console.log("거짓.."); +} +if (42) { + console.log("거짓.."); +} +if (0) { + console.log("거짓.."); +} +if (-0) { + console.log("거짓.."); +} +if (NaN) { + console.log("거짓.."); +} +if (0n) { + console.log("거짓.."); +} +if ("") { + console.log("거짓.."); +} +// ... +``` + +참인 값은 너무 많기 때문에, 거짓인 값만 외워주면 된다. + +# 데이터 타입 확인 + +지금까지 배운 데이터 타입을 코드상에서 확인하는 방법 알아보기 + +```JS +const data = { +  string: "123", +  number: 123, +  boolean: true, +  null: null, +  undefined: undefined, +  array: [1, 2, 3], +  object: { a: 1, b: 2, c: 3 }, +  function: function () {}, +}; +``` + +이런 객체가 존재할 때, 어떤식으로 데이터 타입을 확인할 수 있을지 알아보자. diff --git "a/hyunmin/ZeroBase/\354\236\205\353\254\270\354\236\220 \354\236\220\353\260\224\354\212\244\355\201\254\353\246\275\355\212\270/02. JS-\354\227\260\354\202\260\354\236\220\354\231\200 \352\265\254\353\254\270.md" "b/hyunmin/ZeroBase/\354\236\205\353\254\270\354\236\220 \354\236\220\353\260\224\354\212\244\355\201\254\353\246\275\355\212\270/02. JS-\354\227\260\354\202\260\354\236\220\354\231\200 \352\265\254\353\254\270.md" new file mode 100644 index 0000000..30e05c4 --- /dev/null +++ "b/hyunmin/ZeroBase/\354\236\205\353\254\270\354\236\220 \354\236\220\353\260\224\354\212\244\355\201\254\353\246\275\355\212\270/02. JS-\354\227\260\354\202\260\354\236\220\354\231\200 \352\265\254\353\254\270.md" @@ -0,0 +1,297 @@ +> 제로베이스 자바스크립트 기초개념 연산자와 구문 부분 정리 +> 축약된 부분이 존재할 수 있습니다. + +# 연산자 + +## 산술 연산자 + +```js +// 산술 연산자 (Arithmetic operator) + +console.log(1 + 2); +console.log(5 - 7); +console.log(3 * 4); +console.log(10 / 2); +console.log(7 % 5); +``` + +## 할당 연산자 + +```js +// 할당 연산자 (Assignment Operators) + +let a = 3; +console.log(a); + +// a = a + 2 +a += 2; +console.log(a); + +// a = a - 2 +a -= 2; +console.log(a); + +// a = a * 2 +a *= 2; +console.log(a); + +// a = a / 2 +a /= 2; +console.log(a); + +// a = a % 2 +a %= 2; +console.log(a); +``` + +## 증감 연산자 + +```js +// 증감 연산자 (Increment & Decrement Operators)는 변수를 1씩 더하거나 빼는 연산자이다. + +// ++ 기호가 뒤에 있는 경우 +let b = 3; +console.log(b++); +console.log(b); + +// ++ 기호가 앞에 있는 경우 +let c = 3; +console.log(++c); +console.log(c); + +// -- 기호가 뒤에 있는 경우 +let d = 3; +console.log(d--); +console.log(d); + +// -- 기호가 앞에 있는 경우 +let f = 3; +console.log(--f); +console.log(f); + +// 증감 연산자 보다는, 할당 연산자를 사용하는 것을 추천한다. +let e = 3; +e += 1; +console.log(e); +e -= 1; +console.log(e); +``` + +## 부정 연산자 + +```js +// 부정 연산자(Negation Operator)는 참과 거짓의 반대값을 불린 데이터로 반환합니다. + +console.log(!true); // false +console.log(!false); // true + +console.log("중첩 사용!!"); +console.log(!0); // true +console.log(!!0); // false +console.log(!!!0); // true +console.log(!!!!0); // false + +console.log("거짓(Falsy)!!"); +console.log(!null); // true +console.log(!NaN); // true +console.log(!undefined); // true +console.log(!""); // true + +console.log("참(Truthy"); +console.log(!{}); // false +console.log(![]); // false +console.log(!"A"); // false +``` + +## 비교 연산자 + +```js +// 비교 연산자(Comparison Operators)는 두 데이터를 비교할 때 사용한다. +const a = 1; +const b = 3; + +// 동등(형 변환!) +console.log(a == b); + +// 부등(형 변환!) +console.log(a != b); + +// 일치 +console.log(a === b); + +// 불일치 +console.log(a !== b); + +// 큼 +console.log(a > b); + +// 크거나 같음 +console.log(a >= b); + +// 작음 +console.log(a < b); + +// 작거나 같음 +console.log(a <= b); +``` + +## 논리 연산자 + +강의가 현재 이상해서 다음껄로 + +## 삼항 연산자 + +```js +// 삼항 연산자(Ternary Operator) +// 조건 ? (조건이 참일 때 실행) : (조건이 거짓일 때 실행) + +const f = ["사과", "바나나", "체리"]; + +// IF 조건문 +if (f.length > 0) { +  console.log("과일이 존재합니다."); +} else +  console.log("과일이 존재하지 않습니다."); +} + +// 삼항 연산자 +const message = +  f.length > 0 ? "과일이 존재합니다." : "과일이 존재하지 않습니다"; +console.log(message); +``` + +## 전개 연산자 + +```js +// 전개 연산자(Spread Operator) + +// 배열 데이터 +const numbers = [1, 2, 3]; +console.log(numbers); // [1, 2, 3] +console.log(...numbers); // 1, 2, 3 + +const n1 = [1, 2, 3]; +const n2 = [2, 3, 4]; +const n3 = n1.concat(n2); +const n4 = [...n1, ...n2]; +console.log(n3); // [1, 2, 3, 2, 3, 4] +console.log(n4); // [1, 2, 3, 2, 3, 4] + +// 객체 데이터 +const o1 = { a: 1, b: 2, c: 3 }; +const o2 = { b: 99, c: 100, d: 101 }; +const o3 = Object.assign({}, o1, o2); +const o4 = { ...o1, ...o2 }; +console.log(o3); // { a: 1, b: 99, c: 100, d: 101} +console.log(o4); // { a: 1, b: 99, c: 100, d: 101} +``` + +# 구문 + +## 조건문 -if + +```js +const age = 20; +if (age <= 20) { + console.log("성인"); +} + +const num = 7; +if (num % 2 === 0) { + console.log("짝수"); +} else { + console.log("홀수"); +} + +const score = 100; +if (score >= 90) { + console.log("A"); +} else if (score >= 80) { + console.log("B"); +} else if (score >= 70) { + console.log("C"); +} else if (score >= 60) { + console.log("D"); +} else { + console.log("F"); +} +``` + +## 조건문 -switch + +```js +// switch (조건) { +//  case 값1: +//    break; +//  case 값2: +//    break; +//  case 값3: +//    break; +//  default: +// } + +const prod = "스마트폰"; +switch (prod) { + case "노트북": + break; + case "스마트폰": + break; +} +``` + +## 반복문 -for + +```js +// for +// for (초기화; 조건; 증감) {} +for (let i = 0; i < 10; i += 1) { + console.log(i); +} +``` + +## 반복문 -for of + +```js +// for of 문 +// for (const 아이템변수 of 배열) {} +const fs = ["사과", "바나나", "체리"]; +for (let i = 0; i < fs.length; i += 1) { + const f = fs[i]; + console.log(f); +} +for (const f of fs) { + if (f === "체리") { + continue; + } + console.log(f); +} +``` + +## 반복문 -for in + +```js +// for in 문 +// for (const 키변수 in 객체) {} +const user = { + name: "이백원", + age: 85, + isValid: true, + email: "gyejeongjin@gmail.com", +}; +for (const key in user) { + if (key === "age") continue; + console.log(key, user[key]); +} +``` + +## 반복문 -While + +```js +// while 문 +// while (조건) {} +let count = 1; +while (count <= 10) { + count += 1; + console.log(count); +} +``` diff --git "a/hyunmin/ZeroBase/\354\236\205\353\254\270\354\236\220 \354\236\220\353\260\224\354\212\244\355\201\254\353\246\275\355\212\270/03. JS-\355\225\250\354\210\230.md" "b/hyunmin/ZeroBase/\354\236\205\353\254\270\354\236\220 \354\236\220\353\260\224\354\212\244\355\201\254\353\246\275\355\212\270/03. JS-\355\225\250\354\210\230.md" new file mode 100644 index 0000000..b3660d1 --- /dev/null +++ "b/hyunmin/ZeroBase/\354\236\205\353\254\270\354\236\220 \354\236\220\353\260\224\354\212\244\355\201\254\353\246\275\355\212\270/03. JS-\355\225\250\354\210\230.md" @@ -0,0 +1,285 @@ +> 제로베이스 자바스크립트 기초개념 함수 부분 정리 +> 축약된 부분이 존재할 수 있습니다. + +# 함수 + +## 함수 - 데이터와 호출 + +함수 데이터를 만들고 사용하기위해서는 `호출`을 해야한다. +이 둘의 차이를 알아보자. + +```js +function hello() { + return "Hello"; +} + +// 함수 데이터(Data) +console.log(hello); // 함수 덩어리(코드) 그 자체가 출력된다 +console.log(typeof hello); // function 타입이 나오게 된다. +// 이렇게 사용하면 함수안에 코드를 사용하는게 아니라 함수 안 코드를 확인만 하는 것이 된다. + +// 함수 호출(Call) +console.log(hello()); // 반환 데이터인 Hello가 출력된다. +console.log(typeof hello()); // 반환하는 데이터의 타입인 string이 출력된다. +``` + +## 기명 함수 vs 익명 함수 + +```js +const h1El = document.querySelector("h1"); + +// 기명 함수 - function 이름() {} +function handler() { + console.log(h1El.textContent); +} +h1El.addEventListener("click", handler); + +// 익명 함수 - function () {} +h1El.addEventListener("click", function () { + console.log(h1El.textContent); +}); + +// 이름있으면 기명 함수, 없으면 익명 함수 +// 상황에 맞게 사용하면 된다. +``` + +## 함수 - 선언과 표현 + +```js +// 함수 선언문(Declaration) +function hello() { + console.log("Hello~"); +} + +// 함수 표현식(Expression) +const world = function () { + console.log("World"); +}; + +// 일반적으로는 어떻게 만들어서 사용하든 상관이 없음 +``` + +## 호이스팅 + +하지만 함수 선언문과 표현식의 결정적인 차이는 호이스팅 가능 여부이다. + +일반적으로 자바스크립트 코드는 위에서 아래로 읽어진다. +원래는 world처럼 선언되기 전에 사용하면 에러가 나는 것이 맞지만, 호이스팅이라는 개념이 적용되면 hello함수처럼 호출을 하고 정의를 하는 것이 가능해진다. + +호이스팅은 끌어올려지다라는 의미를 지니고 있다. +함수 선언문으로 만들어진 함수는 자바스크립트가 위로 끌어올린다. +하지만 함수 표현식은 위로 끌어올려지지 않기 때문에 선언문처럼 사용될 수 없다. + +```js +// 호이스팅(Hoisting) +hello(); // OK! +world(); // Error.. + +// 함수 선언문(Declaration) +function hello() { + console.log("Hello~"); +} + +// 함수 표현식(Expression) +const world = function () { + console.log("World"); +}; +``` + +## 함수 - 반환과 종료 + +```js +function sayHi(name) { + return `Hi, ${name}`; // return시 함수는 종료된다. + console.log("동작하지 않음"); +} +const h = sayHi("200woni"); // 반환 +console.log(h); // "Hi, 200woni" + +console.log(sayHi("200woni")); // "Hi, 200woni" +``` + +또한, 우리가 반환을 할 때 반환값없이 해줄 수도 있다. + +```js +function sayHi(name) { + return `Hi, ${name}`; // return시 함수는 종료된다. + console.log("동작하지 않음"); +} +const h = sayHi("200woni"); // 반환 +console.log(h); // "Hi, 200woni" + +console.log(sayHi("200woni")); // "Hi, 200woni" +//===============================================// +function a() { + // return // undefined +} +function b() { + return; // undefined +} +function c() { + return undefined; +} + +console.log(a()); // undefined +console.log(b()); // undefined +console.log(c()); // undefined + +// 결국 3가지가 다 똑같은 명시하느냐 안하느냐의 차이 +``` + +## 인수와 매개변수 + +함수 호출시에 넣어주는 값은 인수, 함수 정의할 때 인수를 받아줄 매개체가 되어주는 변수가 매개변수이다. + +```js +// 인수(Argument)와 매개변수(Parameter) + +function add(a, b) { + return a + b; +} +console.log(add(2, 1)); +console.log(add(7, 4)); +console.log(add("A", "B")); +``` + +### 매개변수의 기본값 + +```js +// 매개변수의 기본값(Default Value) +function add(a, b = 1) { + return a + b; +} +console.log(add(2)); +console.log(add(7, undefined)); // 위랑 같음 +console.log(add()); +``` + +### 나머지 매개변수 + +```js +// 나머지 매개변수(Rest Parameter) + +function add(a, b, ...rest) { + console.log(a, b, rest); + return rest.reduce((acc, cur) => acc + cur, 0); +} +const res = add(1, 2, 3, 4, 5, 6, 7, 8); +console.log(res); +``` + +## 화살표 함수 + +```js +// 일반 함수 +function hello1() { + return "Hello~"; +} +const add1 = function (a, b) { + return a + b; +}; + +const log1 = function (c) { + console.log(c); +}; + +// 화살표 함수 +const hello2 = () => "Hello~"; +const add2 = (a, b) => a + b; +const log2 = (c) => { + console.log(c); +}; +``` + +나중에 this라는 개념을 배우게 되면 일반 함수와 화살표 함수에서 사용하는 것이 다르기 때문에 화살표가 있으면 무조건 화살표 함수라는 것을 기억해두자! + +## 즉시실행함수 + +```js +// 함수 정의(표현) +const double = () => { + // ... +}; +// 함수 실행(호출) +double(); + +// 함수 정의 및 실행 +// 즉시실행함수(IIFE, Immediately Invoked Function Expression) +(() => { + // ... +})(); + +// '즉시실행함수'의 다양한 사용법 +(() => {})(); +(function () {})(); +(function () {})()(); +!(function () {})(); ++(function () {})(); +``` + +## 콜백 + +callback 그대로 뒤에서 호출되는 함수라는 것이다. +간단하게 생각하면 함수의 인수로 사용되는 함수이다. 인수로 사용되었다는 것은 어디선가는 호출이 된다는 것이기에 뒤에 호출되는 함수인 콜백 함수 인 것이다. + +```js +// 콜백 + +const a = (callback) => { + console.log("A"); + callback(); +}; +const b = () => { + console.log("B"); +}; + +a(b); +// "A" +// "B" + +// ============================== // + +function add(a, b, cb) { + // 1초 뒤 실행 + setTimeout(() => { + cb(a + b); + }, 1000); +} +add(3, 7, (result) => { + console.log(result); +}); +``` + +## 호출 스케줄링 + +setTimeout + +```js +// 콜백을 호출하는 타이머 설정 +const timeout = setTimeout(() => { + console.log("Hello~"); +}, 5000); + +// 타이머를 취소 +const btnEl = document.querySelector("button"); +btnEl.addEventListener("click", () => { + console.log("타이머 취소"); + clearTimeout(timeout); +}); +``` + +setInterval + +```js +// 콜백을 반복 호출하는 타이머 설정 +const timeout = setInterval(() => { + console.log("Hello~"); +}, 3000); + +// 타이머를 취소 +const btnEl = document.querySelector("button"); +btnEl.addEventListener("click", () => { + console.log("타이머 취소"); + clearInterval(timeout); +}); +``` diff --git "a/hyunmin/ZeroBase/\354\236\205\353\254\270\354\236\220 \354\236\220\353\260\224\354\212\244\355\201\254\353\246\275\355\212\270/04. JS-\355\221\234\354\244\200\353\202\264\354\236\245\352\260\235\354\262\264.md" "b/hyunmin/ZeroBase/\354\236\205\353\254\270\354\236\220 \354\236\220\353\260\224\354\212\244\355\201\254\353\246\275\355\212\270/04. JS-\355\221\234\354\244\200\353\202\264\354\236\245\352\260\235\354\262\264.md" new file mode 100644 index 0000000..fe5c6f3 --- /dev/null +++ "b/hyunmin/ZeroBase/\354\236\205\353\254\270\354\236\220 \354\236\220\353\260\224\354\212\244\355\201\254\353\246\275\355\212\270/04. JS-\355\221\234\354\244\200\353\202\264\354\236\245\352\260\235\354\262\264.md" @@ -0,0 +1,889 @@ +> 제로베이스 자바스크립트 기초개념 표준 내장 객체 부분 정리 +> 축약된 부분이 존재할 수 있습니다. + +# 표준 내장 객체 함수 + +간단하게 우리가 어떤 데이터에서 필요할 때, 바로바로 쓸 수 있는 기능이라고 생각하면된다. (자주 사용하는 함수만 적어두었음) + +## String + +String(문자) 표준 내장 객체(Built-in Object) + +### .length + +문자의 길이를 반환한다. + +```js +const msg1 = "안녕하세요 반갑습니다."; +console.log(msg1.length); + +const msg2 = + "저는 부산에서 살고 있습니다. 저는 부산소프트웨어마이스터고등학교를 다니고 있습니다."; +console.log(msg2.length); +``` + +### .includes() + +문자에서 특정 문자 포함되어 있는지 확인합니다. + +```js +const msg1 = "Hello World!"; +const msg2 = "안녕하세요"; + +console.log(msg1.includes("!")); +console.log(msg2.includes("안녕")); +``` + +### .replace() + +문자에서 특정 문자를 다른 문자로 바꾼 새로운 문자를 **반환**합니다. +하지만, 첫 번째로 조건에 맞는 문자를 만나면 그 문자만 변경한다. + +```js +const msg1 = "Hello World!"; +const msg2 = "안녕하세요"; + +console.log(msg1.replace("World", "월드")); +console.log(msg2.replace("안녕하", "안녕히계")); +``` + +### .replaceAll() + +문자에서 특정 문자를 다른 문자로 모두 바꾼 새로운 문자를 반환합니다. +즉, replace의 전체 버전이라고 보면 된다. + +```js +const msg3 = "안 녕 하 세 요"; + +console.log(msg1.replaceAll(" ", "")); +``` + +### .slice() + +문자에서 일부를 추출해 새로운 문자를 반환합니다. + +```js +const msg1 = "Hello World!"; + +console.log(msg1.slice(0, 5)); // "Hello" +console.log(msg1.slice(6, -1)); // "World" +console.log(msg.slice(6)); // "World!" +``` + +### .split() + +문자를 구분자로 나누어 배열로 반환합니다. + +```js +const msg1 = "Hello World!"; + +console.log(msg1.split(" ")); // ["Hello", "World!"] +// 아래처럼 응용도 가능 +console.log(msg1.split("").reverse().join("")); // "!dlroW olleH" +``` + +### .toLowerCase() + +문자를 영어 소문자로 바꾼 새로운 문자를 반환합니다. + +```js +const msg1 = "Hello World!"; + +console.log(msg1.toLowerCase()); +``` + +### .toUpperCase() + +문자를 영어 대문자로 바꾼 새로운 문자를 반환합니다. + +```js +const msg1 = "Hello World!"; + +console.log(msg1.toUpperCase()); +``` + +### .trim() + +문자에서 앞뒤 공백을 제거한 새로운 문자를 반환합니다. + +```js +const msg4 = "   Hello!!!    "; + +console.log(msg4.trim()); +``` + +## Number + +Number(숫자) 표준 내장 객체(Built-in Object) + +### .toFixed() + +숫자에서 지정된 자릿수까지 표현하는 새로운 문자를 반환합니다. + +```js +const num = 3.141592; + +console.log(num.toFixed(2)); // "3.14" +console.log(Number(num.toFixed(2))); // 3.1 +``` + +### .toLocalString() + +숫자에서 현지 언어 형식으로 바꾼 새로운 문자를 반환합니다. + +```js +const num1 = 1000; +const num2 = 100000000; + +console.log(num1.toLocaleString()); +console.log(num2.toLocaleString()); +``` + +### Number(), Number.parseInt(), Number.parseFloat() + +이 함수는 위 함수들과 다르게 클래스 메소드이기 때문에 데이터.메소드의 프로토타입 메소드와 다르게 정적 메소드라고 불린다. (Number는 클래스를 호출하는 방식) +지금은 데이터에서 사용되냐 클래스에서 사용되냐만 알면 되고, 나중에 클래스파트에서 자세히 알아볼 것이다. + +**Number()** 같은 경우는 광범위하게 해석을 하고, 유연하게 사용할 수 있다. +`Number(데이터)` +하지만 숫자와 불리언형정도가 제대로 변환이 되고 다른 것들은 NaN이 뜬다. +즉, Number()는 간단하게 숫자 변환을 할 때는 괜찮지만 명확하게 변환하고 싶다고 한다면 어울리지 않는다. + +**Number.parseInt()** 같은 경우에는 숫자를 추출해 명확하고 에측가능하게 동작하고, 진법지정이 가능하고, 정수만 처리한다. +`Number.parseInt("문자", 진수)` +즉, 문자를 정수로 변환하는 기능을 한다. + +```js +console.log(Number.parseInt(123abc, 10)) // 123 +``` + +Number보다는 조금 더 정확하고 예측가능하게 변환하고 싶다면 사용하면 된다. + +**Nuber.parseFloat()** 같은 경우 parseInt + 부동소수점 실수 처리 가능이라고 보면 된다. +`Number.parseFloat("문자")` + +```js +console.log(Number.parseFloat("123.123")); +``` + +그 외에 parseInt와 다른점은 없다. + +### Number.isInteger() + +데이터가 정수(숫자 데이터)인지 확인합니다. +`Number.isInteger(데이터)` +위 처럼 사용하고 Boolean값으로 반환해준다. + +### Number.isNaN() + +데이터가 'NaN'인지 확인합니다. +`Number.isNaN()` +위 처럼 사용하고 Boolean값으로 반환해준다. + +## Math + +Math(수학) 표준 내장 객체(Built-in Object) + +### Math.abs() + +주어진 숫자의 절댓값을 반환합니다. + +```js +console.log(Math.abs(7)); // 7 +console.log(Math.abs(-7)); // 7 +console.log(Math.abs(3.14)); // 3.14 +console.log(Math.abs(-3.14)); // 3.14 +``` + +### Math.ceil() + +주어진 숫자를 올림해 **정수**를 반환합니다. + +```js +console.log(Math.ceil(3.14141411)); // 4 +console.log(Math.ceil(3.14141441 * 100) / 100); // 3.15 +``` + +### Math.floor() + +주어진 숫자를 내림해 **정수**를 반환합니다. + +```js +console.log(Math.floor(3.14141411)); // 3 +console.log(Math.floor(3.14141441 * 100) / 100); // 3.14 +``` + +### Math.round() + +주어진 숫자를 반올림해 **정수**를 반환합니다. + +```js +console.log(Math.round(3.1)); // 3 +console.log(Math.round(3.6)); // 4 +``` + +### Math.max() + +주어진 숫자중 가장 큰 숫자를 반환합니다. + +```js +console.log(Math.max(10, 128, 12, 49, 1)); // 128 +``` + +### Math.min() + +주어진 숫자중 가장 작은 숫자를 반환합니다. + +```js +console.log(Math.min(10, 128, 12, 49, 1)); // 1 +``` + +### Math.random() + +'0' 이상 '1' 미만의 난수(무작위 수)를 반환합니다. + +```js +console.log(Math.random()); + +// 원하는 범위의 랜덤값 뽑기 +function Random(min = 0, max = 10) { + return Math.floor(Math.random() * (max - min)) + min; +} + +console.log(Random()); // 0~9 +console.log(Random(0, 100)); // 0~99 +console.log(Random(101, 200)); // 101~199 +``` + +## Date + +Date(날짜) 표준 내장 객체(Built-in Object) + +일단 Data는 날짜를 뽑는 클래스이다. + +```js +// 'new Date()'를 통해서 반환된 인스턴스를 '타임 스탬프(timestamp)'라고 한다. +let date = new Date(); +console.log(date); // 현재 날짜가 나옴 (타임 스탬프) + +// 우리가 원하는 날짜로 생성 할 수도 있다. +date = new Date(2006, 4, 8, 0, 0, 0); +console.log(date); + +// 타임 스탬프에서 각 정보를 얻을 수 있다. +console.log(date.getFullYear); +console.log(date.getMonth + 1); // 1~12가 아닌 0~11을 반환함 +console.log(date.getDate); +console.log(getDayKo(date.getay)); // 애도 0부터 시작해서 일요일 = 0 토요일 6이다. (함수 만드는 것도 좋은 선택) +console.log(date.getHours); +console.log(date.getMinutes); +console.log(date.getSeconds); + +function getDayKo(day) { + switch (day) { + case 0: + return "일요일"; + case 1: + return "월요일"; + case 2: + return "화요일"; + case 3: + return "수요일"; + case 4: + return "목요일"; + case 5: + return "금요일"; + case 6: + return "토요일"; + } +} +``` + +### .getTime() + +유닉스 타임(UNIX Time)으로부터 경과한 시간(ms)을 반환합니다. +유닉스 타임은 1970.01.01 00:00:00 시간을 의미합니다. + +```js +const date1 = new Date(); +console.log(date1.getTime()); +``` + +### Date.now() + +현재 시간을 유닉스 타임으로 변환합니다. +유닉스 타임은 1970.01.01 00:00:00 시간을 의미합니다.\ + +```js +const date2 = Date.now(); +console.log(date2); +``` + +### .toISOString() + +날짜 인스턴의 협정 세계시(UTC)를 'ISO 8601' 포맷으로 변환합니다. +'ISO 8601'은 날짜와 시간을 표현하는 국제 표준 규격입니다. + +```js +console.log(new Date().toISOString); +``` + +우리가 데이터를 저장할 때, 데이터베이스에 저장해야하는 경우가 생길 수도 있는데 이러한 상황에서 전세계로 사용되는 서비스라면 한국 표준시보다는 국제 표준 포멧으로 바꿔서 저장할 떄 유용할 것이다. + +## Array + +Array(배열) 표준 내장 객체(Built-in Object) + +### .length + +배열의 길이(숫자)를 반환한다. + +```js +const fruits = ["Apple", "Banana", "Cherry"]; + +console.log(fruits.length); +``` + +### .at + +배열을 인덱싱하며, 만약 음수를 사용하면 뒤에서부터 인덱싱합니다. + +```js +const fruits = ["Apple", "Banana", "Cherry"]; + +console.log(fruits.at(1)); +console.log(fruits.at(-1)); // 마지막 요소, 뒤에서부터 인덱싱 가능 +``` + +### .concat() + +배열에서 주어진 배열을 병합해 새로운 배열을 반환합니다. + +```js +const name = ["현민", "황현민"]; +const age = ["18", "19"]; +const na = this.name.concat(age); +console.log(na); +``` + +### .every() + +배열의 모든 요소가 콜백 테스트를 통과하는지 확인합니다. +만약 테스트가 하나라도 실패하면, 이후 테스트를 진행하지 않고 'false'를 반환합니다. + +```js +const numbers = ["12", "456", "132", "60", "1"]; +const isValid = numbers.every((item) => item < 200); +console.log(isValid); + +const users = [ + { name: "현민", age: 18 }, + { name: "200원", age: 200, email: "200@gmail.com" }, + { name: "황현민", age: 19, email: "gyejeongjin@gmail.com" }, +]; +console.log(users.every((user) => user.email)); +console.log(users.every((user) => user.age)); +``` + +### .filter() + +배열에서 콜백 테스트를 통과하는 모든 요소로 새로운 배열을 만들어 반환합니다. +만약 모든 요소가 테스트를 통과하지 못하며 빈 배열을 반환합니다. + +```js +const numbers = ["12", "456", "132", "60", "1"]; +const filterNumbers = numbers.filter((number) => number < 30); +console.log(filterNumbers); + +const users = [ + { name: "현민", age: 18 }, + { name: "200원", age: 200, email: "200@gmail.com" }, + { name: "황현민", age: 19, email: "gyejeongjin@gmail.com" }, +]; +const youngUsers = users.filter((user) => user.age < 30); +console.log(youngUsers); +const userWithEmail = users.filter((user) => user.email); +console.log(userWithEmail); +const userWithPhone = users.filter((user) => user.phone); +console.log(userWithPhone); +``` + +### .find() + +배열에서 콜백 테스트를 처음으로 통과하는 요소를 반환합니다. +만약 테스트를 통과하면, 이후 테스트는 진행하지 않습니다. +만약 모든 테스트가 실패하면, 'undefined'를 반환합니다. + +```js +const numbers = ["12", "456", "132", "60", "1"]; +const foundNumbers = numbers.find((number) => number < 30); +console.log(foundNumbers); + +const users = [ + { name: "현민", age: 18 }, + { name: "200원", age: 200, email: "200@gmail.com" }, + { name: "황현민", age: 19, email: "gyejeongjin@gmail.com" }, +]; +const foundUser = users.find((user) => !user.email); +console.log(foundUser); +``` + +### .findIndex() + +배열에서 콜백 테스트를 처음으로 통과하는 요소의 인덱스를 반환합니다. +만약 테스트가 통과하면, 이후 테스트를 진행하지 않습니다. +만약 모든 테스트가 실패하면, -1을 반환합니다. + +```js +const numbers = ["12", "456", "132", "60", "1"]; +const foundIndex = numbers.findIndex((number) => number < 30); +console.log(foundIndex); + +const users = +  { name: "현민", age: 18 }, +  { name: "200원", age: 200, email: "200@gmail.com" }, +  { name: "황현민", age: 19, email: "gyejeongjin@gmail.com" }, +]; +const foundUserIndex = users.findIndex((user) => !user.email); +console.log(foundUserIndex); +``` + +### .forEach() + +배열의 각 요소에 대해 콜백을 호출합니다. +만약 배열이 비어있다면, 아무런 동작도 하지 않습니다. +만약 반복을 종료하고 싶다면, for반복문을 사용하여야 합니다. + +```js +const numbers = ["12", "456", "132", "60", "1"]; +numbers.forEach((number) => { + console.log(number); +}); + +let sum = 0; +numbers.forEach((number) => { + sum += number; +}); +console.log("합계: ", sum); + +for (const number of numbers) { + if (number > 100) { + break; + } + console.log(number); +} +``` + +### .includes() + +배열에서 특정 요소가 포함되어 있는지 확인합니다. + +```js +const f = ["사과", "바나나", "체리"]; +console.log(f.includes("사과")); +console.log(f.includes("버나나")); // false + +const n = [10, 20, 30, 40]; +console.log(n.includes(10)); +console.log(n.includes(123123)); +``` + +### .join() + +배열의 모든 요소를 연결해 하나의 문자열로 만듭니다. + +```js +const f = ["사과", "바나나", "체리"]; +console.log(f.join()); +console.log(f.join("")); +console.log(f.join(", ")); +console.log(f.join("/")); + +const msg = "Hello World"; +console.log(msg.split("").reverse().join("")); +``` + +### .map() + +배열의 모든 요소에 대해 각 콜백을 호출하고 반환된 결과로 새로운 배열을 반환한다. + +```js +const numbers = [17, 20, 100, 5, 200]; +const doubleNumbers = numbers.map((number) => number * 2); +console.log(doubleNumbers); +console.log(numbers); + +const fruits = ["apple", "banana", "cherry"]; +const capitalizedFruits = fruits.map((fruit) => fruit.toUpperCase()); +console.log(capitalizedFruits); +console.log(fruits); + +const users = [ + { name: "현민", age: 18 }, + { name: "200원", age: 200, email: "200@gmail.com" }, + { name: "황현민", age: 19, email: "gyejeongjin@gmail.com" }, +]; + +const userEmails = users.map((user) => user.email); +console.log(userEmails); +console.log(userEmails.filter((email) => email)); + +const f = ["사과", "바나나", "체리"]; +const UpperF = f.map((e, i) => { + console.log(i); +}); +``` + +### .push() + +배열의 마지막에 하나 이상의 요소를 추가하고, 배열의 새로운 길이를 반환합니다. +배열 원본이 변경됩니다. + +```js +const fruits = ["apple", "banana", "cherry"]; +console.log(fruits.push("durian")); +console.log(fruits.length); +console.log(fruits); + +const numbers = [17, 20, 100, 5, 200]; +console.log(numbers.push(9, 10, 11)); +console.log(numbers.length); +console.log(numbers); +``` + +### .reduce() + +배열의 각 요소에 대해 콜백을 호출하고, 각 콜백의 반환 값을 다음 콜백으로 전달해 마지막 콜백의 반환 값을 최종 반환합니다. + +```js +const numbers = [10, 19, 29, 13, 120, 123]; + +let sum1 = 0; +numbers.forEach((number) => { + sum += number; +}); +console.log(sum); + +// number가 배열안에 값, accumulator가 누적값 +const sum2 = numbers.reduce((accumulator, number) => { + return accumulator + number; +}, 0); // 콜백 함수, 초기값 +console.log(sum2); + +const users = [ + { name: "현민", age: 18 }, + { name: "200원", age: 200, email: "200@gmail.com" }, + { name: "황현민", age: 19, email: "gyejeongjin@gmail.com" }, +]; +const sum3 = users.reduce((acc, user) => acc + user.age, 0); +console.log(sum3); +``` + +### .reverse() + +배열의 순서를 반전합니다. +배열 원본이 변경됩니다. + +```js +const fruits1 = ["APPLE", "BANANA", "CHERRY"]; +console.log(fruits1.reverse()); +console.log(fruits1); + +// 원본이 변경 안되었으면 좋겠다? +const fruits2 = ["APPLE", "BANANA", "CHERRY"]; +console.log([...fruits2].reverse()); +console.log(fruits2); +``` + +### .slice() + +배열의 일부를 추출해 새로운 배열로 반환합니다. + +```js +const numbers = [100, 200, 300, 400, 500, 600, 700, 800, 900]; + +console.log(numbers.slice(0, 3)); +console.log(numbers.slice(4, -1)); +console.log(numbers.slice(4)); +console.log(numbers.slice(-4)); +console.log(numbers.slice(-4, -1)); +console.log(numbers); +``` + +### .some() + +배열의 요소 중 콜백 테스트를 통과하는 요소가 하나라도 있는지 확인합니다. +만약 테스트가 통과하면, 이후 테스트는 진행하지 않습니다. +.every의 하나인 버전이라고 생각하면 된다. + +```js +const numbers = [17, 20, 199, 1, 40]; +const isValid = numbers.some((number) => number > 200); +console.log(isValid); + +const users = [ + { name: "현민", age: 18 }, + { name: "200원", age: 200, email: "200@gmail.com" }, + { name: "황현민", age: 19, email: "gyejeongjin@gmail.com" }, +]; +console.log(users.some((user) => user.email)); +console.log(users.some((user) => user.phone)); +``` + +### .sort() + +배열의 요소를 콜백의 반환 값에 따라 정렬합니다. +만약 콜백을 제공하지 않으면, 요소를 유니코드 포인트 순서대로 정렬합니다. +배열 원본이 변경됩니다. + +```js +const numbers = [17, 20, 199, 1, 40]; + +numbers.sort(); +console.log(numbers); + +numbers.sort((a, b) => a - b); +console.log(numbers); + +numbers.sort((a, b) => b - a); +console.log(numbers); + +const users = [ +  { name: "현민", age: 18 }, +  { name: "200원", age: 200, email: "200@gmail.com" } +  { name: "황현민", age: 19, email: "gyejeongjin@gmail.com" } +] + +users.sort((a, b) => a.age - b.age); +console.log(...users); + +users.sort((a, b) => b.age - a.age); +console.log(...users); + +// 콘솔창에서 볼 떄는 sort라는 것이 딱 한번만 일어난 것이기 때문에 users를 펼쳐서 볼 때 값이 바뀌게 된다. +// 그래서, 배열이나 객체 데이터를 출력할때, 내용이 많아서 숨겨진다면 ...users처럼 숨겨지지 않는 상태로 바꿔서 출력해야 한다. +``` + +### .splice(인덱스, 삭제개수, 추가요소) + +배열의 요소를 추가하거나 삭제하거나 교체합니다. +배열 원본이 변경됩니다. + +```js +// splice(인덱스, 삭제개수, 추가요손) + +// 요소 추가 +const f1 = ["사과", "바나나", "체리"]; +f1.splice(2, 0, "두리안"); +console.log(f1); + +// 요소 삭제 +const f2 = ["사과", "바나나", "체리"]; +f2.splice(1, 1); +console.log(f2); + +// 요소 교체 +const f3 = ["사과", "바나나", "체리"]; +f3.splice(1, 1, "두리안", "오렌지", "망고"); +``` + +### .unshift() + +배열의 시작 부분에 하나 이상의 요소를 추가하고, 배열의 새로운 길이를 반환합니다. +배열 원본이 변경됩니다. +push의 반대 개념이라고 생각하면 된다. + +```js +const f1 = ["사과", "바나나", "체리"]; +console.log(f1.unshift("두리안")); +console.log(f1.length); +console.log(f1); + +const numbers = [17, 20, 199, 60, 31]; +console.log(numbers.unshift(9, 10, 11)); +console.log(numbers.length); +console.log(numbers); +``` + +### 배열 메소드 인덱스 + +배열 메소드의 콜백은 항상 현재 반복의 인덱스를 얻을 수 있다. + +```js +const numbers = [17, 20, 199, 60, 31]; +numbers.every((num, idx) => { + console.log(num, idx); + return true; +}); +numbers.filter((num, idx) => { + console.log(num, idx); + return true; +}); +numbers.reduce((acc, cur, idx) => { + console.log(acc, cur, idx); + return acc + cur; +}, 0); +``` + +### Array.isArray() + +배열 데이터인지 확인합니다. + +```js +const f = ["사과", "바나나", "체리"]; +// 유사배열 +const arrayLikeFruits = { + 0: "사과", + 1: "바나나", + 2: "체리", + length: 3, +}; + +console.log(Array.isArray(f)); +console.log(Array.isArray(arrayLikeFruits)); +``` + +### Array.from() + +유사 배열(Array-Like)을 실제 배열로 반환합니다. + +```js +const fruits = ["Apple", "Banana", "Cherry"]; +// 유사배열 +const arrayLikeFruits = { + 0: "Apple", + 1: "Banana", + 2: "Cherry", + length: 3, +}; +console.log(fruits); +console.log(arrayLikeFruits); + +console.log(fruits[1]); +console.log(arrayLikeFruits[1]); + +console.log(fruits.length); +console.log(arrayLikeFruits.length); + +console.log(Array.isArray(fruits)); +console.log(Array.isArray(arrayLikeFruits)); + +console.log(fruits.map((fruit) => fruit.toUpperCase())); +console.log(fruits.frm(arrayLikeFruits).map((fruit) => fruit.toUpperCase())); +``` + +## Object + +Object(객체) 표준 내장 객체(Built-in Object) + +### Object.assign(대상, 출처1, 출처2, ...) + +하나 이상의 '출처 객체(Source)'로부터 '대상 객체(Target)'로 속성을 복사하고 대상 객체를 반환합니다. + +```js +const target = { a: 1, b: 2 }; +const source1 = { b: 3, c: 4 } +const source2 = { c: 5, d: 6 }; +// 원본 객체 변함 +// const result = Object.assign(target, source1, source2); +// console.log(target); +// console.log(result); + +// 원본 객체 변하지 않음 +const result = Object.assign({}, target, source1, source2); +console.log(target); +console.log(result); + +const userA = { +  name: "200원" +  age: 200, +}; +const userB = { +  age: 22, +  email: "gyejeongjin@gmail.com", +  isValid: true, +}; +// const thw = Object.assign(userA, userB); +const thw = Object.assign({}, userA, userB); + +console.log(thw); +console.log(userA); +``` + +### Object.keys() + +객체의 모든 키를 배열로 반환합니다. +순서는 보장하지 않습니다. + +```js +const user = { + name: "200원", + age: 200, + email: "200woni@naver.com", + isValid: true, +}; +const keys = Object.keys(user); +console.log(keys); + +keys.forEach((key) => { + const el = document.createElement("div"); + el.innerHTML = `${key}: ${user[key]}`; + document.body.append(el); +}); +``` + +### Object.values() + +객체의 모든 값을 배열로 반환합니다. +순서는 보장하지 않습니다. + +```js +const user = { + name: "200원", + age: 200, + email: "200woni@naver.com", + isValid: true, +}; +const values = Object.values(user); +console.log(values); +``` + +## JSON + +JSON(JavaScript Object Notation)은 데이터 전달을 위한 표준 데이터 포맷입니다. +문자, 숫자, 불린, Null, 객체, 배열만 사용 +문자는 큰 따옴표만 사용 +후행 쉼표 사용 불가 (마지막 쉼표) +'.json' 확장자의 파일 사용 가능 + +```json +{ + "name": "200원", + "age": 200, + "email": "200woni@naver.com", + "isValid": true +} +``` + +**JSON.stringify()** + +```js +const user = { + name: "200원", + age: 200, + email: "200woni@naver.com", + isValid: true, // 후행 쉼표 +}; +console.log(JSON.stringify(user)); +``` + +자바스크립트 데이터를 JSON 문자로 변환합니다. +**JSON.parse()** + +```js +const json = JSON.stringify(user); +console.log(json); +console.log(JSON.parse(json)); +``` + +JSON문자를 자바스크립트 데이터로 변환합니다. diff --git "a/hyunmin/ZeroBase/\354\236\205\353\254\270\354\236\220 \354\236\220\353\260\224\354\212\244\355\201\254\353\246\275\355\212\270/05. JS-DOM.md" "b/hyunmin/ZeroBase/\354\236\205\353\254\270\354\236\220 \354\236\220\353\260\224\354\212\244\355\201\254\353\246\275\355\212\270/05. JS-DOM.md" new file mode 100644 index 0000000..9aee28a --- /dev/null +++ "b/hyunmin/ZeroBase/\354\236\205\353\254\270\354\236\220 \354\236\220\353\260\224\354\212\244\355\201\254\353\246\275\355\212\270/05. JS-DOM.md" @@ -0,0 +1,414 @@ +> 제로베이스 자바스크립트 기초개념 DOM 부분 정리 +> 축약된 부분이 존재할 수 있습니다. + +# DOM + +DOM(Document Object Model)이란, HTML 문서를 객체로 표현한 것으로, JS에서 HTML을 제어할 수 있게 해줍니다. + +## Node와 Element + +**노드(Node)** + +- 요소 텍스트, 주석 등의 각 구조를 의미합니다. + **요소(Element)** +- 노드의 하위 객체로 요소를 의미합니다. + +```js +const parentEl = document.querySelector(".parent"); + +// 요소의 모든 자식 노드를 확인합니다. +// 요소, 텍스트, 주석 전부 나옴 +console.log(parentEl.childNodes); + +// 요소의 모든 자식 요소를 확입합니다. +// 자식 요소만 나옴 +console.log(parentEl.children); +``` + +## 검색과 탐색 + +### document.querySelector(선택자) + +선택자로 검색한 요소를 하나 반환합니다. +만약 검색 결과가 없으면, 'null'을 반환합니다. + +```js +// 가장 먼저 만나는 요소를 반환한다. +const el = document.querySelector("div"); +console.log(el); + +// 이런식으로 자식 요소를 찾을 수도 있다. +el.querySelector("div"); +``` + +### document.querySelectorAll(선택자) + +선택자로 검색한 모든 요소를 NodeList 객체로 반환합니다. + +```js +const nodeList = document.querySelectorAll("div"); +console.log(nodeList); + +// NodeList는 유사 배열이며, '.forEach()' 메소드는 내장되어 있지만, +// 기타 배열 메소드는 사용할 수 없다. +nodeList.forEach((e, i) => console.log(i + 1, e)); + +// NodeList 객체는 'Array.from()' 메소드를 통해서 배열로 변환할 수 있다. +const nodes = Array.from(nodeList); +const names = nodes.map((e) => e.textContent); +console.log(names); +``` + +### document.getElementById(아이디) + +HTML 'id' 속성(Atrributes) 값으로 검색한 요소를 하나 반환합니다. +만약 검색 결과가 없으면, 'null'을 반환합니다. + +```js +const idEl = document.getElementById("apple"); +console.log(idEl); + +const selEl = document.querySelector("#apple"); +console.log(selEl); +``` + +### 노드.parentElement + +노드의 부모 요소를 반환합니다. + +```js +const selEl = document.querySelector("#apple"); +console.log(selEl); +console.log(selEl.parentElement); +``` + +### Sibling + +**요소.previousElementSibling** + +- 요소의 이전 형제 요소를 반환합니다. + **요소.nextElementSibling** +- 요소의 다음 형제 요소를 반환합니다. + +```js +const selEl = document.querySelector("#apple"); +console.log(selEl.previousElementSibling); +console.log(selEl.nextElementSibling); +``` + +### 자식 요소 + +**요소.children** + +- 요소의 모든 자식 요소를 반환합니다. + **요소.firstElementChild** +- 요소의 첫 번째 자식 요소를 반환합니다. + **요소.lastElementChild** +- 요소의 마지막 자식 요소를 반환합니다. + +```js +const selEl = document.querySelector("div"); +console.log(selEl.children); +console.log(selEl.children[selEl.children.length - 1]); +console.log(selEl.firstChild); +console.log(selEl.lastChild); +``` + +## 생성, 조회, 수정 + +### document.createElement(태그이름) + +HTML 요소를 메모리상에 생성해 반환합니다. + +```js +const divEl = document.createElement("div"); +divEl.textContent = "안녕하세요!"; +divEl.classList.add("hello"); +console.log(divEl); + +const inputEl = document.createElement("input"); +inputEl.value = "입력하세요!"; +console.log(inputEl); + +const buttonEl = document.createElement("button"); +buttonEl.textContent = "버튼입니다!"; +console.log(buttonEl); + +// HTML body에 추가하기 +document.body.append(divEl, inputEl, buttonEl); +``` + +### append + +**요소.prepend(노드1, 노드2, ...)** + +- 하나 이상의 노드를 요소의 첫 번째 자식으로 삽입합니다. + **요소.append(노드1, 노드2, ...)** +- 하나 이상의 노드를 요소의 마지막 자식으로 삽입합니다. + **노드.appendChild(노드1)** +- 하나의 노드를 노드의 마지막 자식으로 삽입하고, 삽입한 노드를 반환합니다. + +```js +const parentEl = document.createElement(".parent"); + +const divEl = document.createElement("div"); +divEl.textContent = "새로운 요소!"; + +const inputEl = document.createElement("input"); + +const res1 = parentEl.prepend(new Comment(" 새로운 주석 ")); +const res2 = parentEl.append(divEl, "새로운 텍스트"); +const res3 = (parentEl.appendChild(inputEl).placeholder = "값을 입력하세요!"); + +console.log(res1, res2); +console.log(res3); +``` + +### 요소.remove + +요소를 제거합니다. + +```js +const el = document.querySelector("#apple"); + +console.log(el); +el.remove(); +``` + +### 노드.contains(노드) + +주어진 노드가 대상 노드를 포함한 후손인지 확인합니다. + +```js +const parentEl = document.querySelector(".parent"); +const appleEl = document.querySelector("#apple"); + +console.log(parentEl.contains(appleEl)); +console.log(document.body.contains(parentEl)); +console.log(document.body.contains(appleEl)); +console.log(document.body.contains(document.body)); +console.log(parentEl.contains(document.body)); +console.log(appleEl.contains(document.body)); +``` + +### 노드.textContent + +노드의 모든 텍스트를 확인(GET)하거나 지정(SET)합니다. + +```js +// GET +const el = document.querySelector("#apple"); +console.log(el.textContent); + +// SET +el.textContent = "오렌지"; +console.log(el.textContent); +``` + +### 요소.innerHTML + +요소의 내부 HTML을 확인(GET)하거나 지정(SET)합니다. + +```js +// GET +const el = document.querySelector("#apple"); +console.log(el.innerHTML); + +// SET +el.innerHTML = `
두리안
`; +console.log(el.innerHTML); +``` + +### 요소.dataset + +요소의 'data-' 속성을 확인(GET)하거나 지정(SET)합니다. + +```js +const el = document.querySelector("#apple"); +const str = "Hello World"; +const num = 123; +const obj = { + a: 1, + b: 2, +}; + +console.log(el.dataset); + +el.dataset.helloWorld = str; +el.dataset.number = num; +// 객체 같은 경우는 JSON문자 형식으로 변환해야된다. +el.dataset.object = JSON.stringify(obj); + +console.log(el.dataset); + +console.log(el.dataset.helloWorld); +console.log(el.dataset.number); +console.log(JSON.parse(el.dataset.number)); +console.log(el.dataset.object); +console.log(JSON.parse(el.dataset.object)); +``` + +### 요소.classList + +요소의 'class' 속성을 제어합니다. + +**요소.classList.add()** + +- 값을 추가 + **요소.classList.remove()** +- 값을 제거 + **요소.classList.toggle()** +- 값을 토 + **요소.classList.contains()** +- 값을 확인 + +```js +const el = document.querySelector("#apple"); + +el.classList.add("active"); +console.log(el.classList.contains("active")); + +el.classList.remove("active"); +console.log(el.classList.contains("active")); + +el.addEventListener("click", () => { + el.classList.toggle("active"); + console.log(el.classList.contains("active")); +}); +``` + +### 요소.style + +요소의 'style' 속성을 확인(GET)하거나 지정(SET)합니다. + +```js +const el = document.querySelector("#apple"); + +// 개별 지정 할 수 있다. +el.style.width = "100px"; +el.style.fontSize = "20px"; +el.style.backgroundColor = "green"; +el.style.position = "absolute"; + +// 한 번에 지정할 수 있습니다. +Object.assign(el.style, { + width: "100px", + fontSize: "20px", + backgroundColor: "green", + position: "absolute", +}); + +console.log(el.style); +console.log(el.style.width); +console.log(el.style.fontSize); +console.log(el.style.backgroundColor); +console.log(el.style.position); +``` + +### Attribute(속성) + +참고로 HTML의 속성은 Attribute이고, CSS와 JS의 속성은 Property라고 한다. +**요소.getAttribute(속성)** + +- 요소의 속성을 확인합니다. + **요소.setAttribute(속성, 값)** +- 요소에 속성과 값을 지정합니다. + **요소.hasAttribute(속성)** +- 요소에 속성이 있는지 확인합니다. + **요소.removeAttribute(속성)** +- 요소에 속성을 제거합니다. + +```js +const el = document.querySelector("#apple"); + +console.log(el.getAttribute("class")); +console.log(el.getAttribute("title")); + +// 덮어쓰여진다. +// el.setAttribute("class", "hello-world") +el.setAttribute("title", "Hello-World"); + +console.log(el.hasAttribute("class")); +console.log(el.hasAttribute("title")); +console.log(el.hasAttribute("value")); + +el.removeAttribute("class"); +el.removeAttribute("title"); +``` + +### 크기와 좌표 + +**window.innerWidth** + +- 화면(Viewport)의 너비를 얻습니다. + **window.innerHeight** +- 화면(Viewport)의 높이를 얻습니다. + **window.scrollX** +- 화면에서 스크롤된 x축의 위치를 얻습니다. + **window.scrollY** +- 화면에서 스크롤된 y축의 위치를 얻습니다. + +```js +console.log(window.innerWidth); +console.log(window.innerHeight); + +console.log(window.scrollX); +console.log(window.scrollY); + +window.addEventListener("scroll", () => { + console.log(window.scrollY); +}); +``` + +### scrollTo() + +**window.scrollTo()** 와 **요소.scrollTo()** 가 있음 +지정된 좌표로 대상을 스크롤합니다. + +대상.scrollTo(X좌표, Y좌표) +대상.scrollTo({ +left: X좌표, +top: Y좌표, +behavior: "smooth" +}) + +```js +setTimeout(() => { + // window.scrollTo(0, 200); + window.scrollTo({ + left: 100, + top: 200, + behavior: "smooth", + }); +}, 2000); +``` + +### Width | Height + +**요소.offsetWidth** + +- 테두리 선을 포함한 요소의 너비를 얻습니다. + **요소.offsetHeight** +- 테두리 선을 포함한 요소의 높이를 얻습니다. + **요소. clientWidth** +- 테두리 선을 제외한 요소의 너비를 얻습니다. + **요소, clientHeight** +- 테두리 선을 제외한 요소의 높이를 얻습니다. + **요소. scrollWidth** +- 테두리 선을 제외한 요소의 스크롤 영역 너비를 얻습니다. + **요소. scrollHeight** +- 테두리 선을 제외한 요소의 스크롤 영역 높이를 얻습니다. + +```js +const parentEl = document.querySelector(".parent"); +const appleEl = document.querySelector(".apple"); + +console.log(parentEl.clientWidth, parentEl.clientHeight); +console.log(appleEl.clientWidth, appleEl.clientHeight); + +console.log(parentEl.offsetWidth, parentEl.offsetHeight); +console.log(appleEl.offsetWidth, appleEl.offsetHeights); + +console.log(parentEl.scrollWidth, parentEl.scrollHeight); +console.log(appleEl.scrollWidth, appleEl.scrollHeight); +``` diff --git "a/hyunmin/ZeroBase/\354\236\205\353\254\270\354\236\220 \354\236\220\353\260\224\354\212\244\355\201\254\353\246\275\355\212\270/06. JS-\354\235\264\353\262\244\355\212\270.md" "b/hyunmin/ZeroBase/\354\236\205\353\254\270\354\236\220 \354\236\220\353\260\224\354\212\244\355\201\254\353\246\275\355\212\270/06. JS-\354\235\264\353\262\244\355\212\270.md" new file mode 100644 index 0000000..8d9e76a --- /dev/null +++ "b/hyunmin/ZeroBase/\354\236\205\353\254\270\354\236\220 \354\236\220\353\260\224\354\212\244\355\201\254\353\246\275\355\212\270/06. JS-\354\235\264\353\262\244\355\212\270.md" @@ -0,0 +1,289 @@ +> 제로베이스 자바스크립트 기초개념 이벤트(Event) 부분 정리 +> 축약된 부분이 존재할 수 있습니다. + +# 이벤트 + +화면에서 발생할 수 있는 다양한 상황 + +## 이벤트 추가 및 제거 + +### 대상.addEventListener(이벤트 종류, 핸들러) + +대상에서 청취(Listen)할 이벤트 종류와 이벤트가 발생하였을 때 호출할 콜백(Handler)을 등록한다. + +```js +const parentEl = document.querySelector(".parent"); +const childEl = document.querySelector(".child"); + +parentEl.addEventListener("click", () => { + console.log("부모"); +}); + +childeEl.addEventListener("click", () => { + console.log("자식"); +}); + +// 이벤트 버블링 발생 +``` + +### 대상.removeEventListener(이벤트 종류, 핸들러) + +대상에게 등록했던 이벤트 핸들러를 제거합니다. + +```js +const parentEl = document.querySelector(".parent"); +const childEl = document.querySelector(".child"); + +// 삭제하기 위해서는 기명함수가 필요하다. +const handler = () => { + console.log("부모"); +}; + +parentEl.addEventListener("click", handler); +childEl.addEventListener("click", () => { + parentEl.removeEventListener("click", handler); +}); +``` + +## 이벤트 객체 + +.addEventListener() 핸들러의 첫 매개변수로, 발생한 이벤트의 정보를 가진 객체를 전달합니다. + +```js +const parentEl = document.querySelector(".parent"); + +parentEl.addEventListener("click", (event) => { + console.log(parentEl); + console.log(event.target); +}); + +const inputEl = document.querySelector("input"); +inputEl.addEventListener("keydown", (event) => { + console.log(event.key); + console.log(inputEl.value); + console.log(event.target.value); +}); +``` + +## 이벤트 기본 동작 방지 + +**event.preventDefault()** + +- 이벤트의 기본 동작을 방지합니다. + 이벤트의 기본 동작이란 a태그에서 href에 적힌 링크로 이동하는 것이 기본 동작이라고 볼 수 있다. + +```js +// 태그에서 페이지 이동 방지! +const anchorEl = document.querySelector("a"); +anchorEl.addEventListener("click", (e) => { + e.preventDefault(); +}); + +// 마우스의 휠의 스크롤 동작 방지! +const parentEl = document.querySelector(".parent"); +parentEl.addEventListener("wheel", (e) => { + e.preventDefault(); +}); +``` + +## 이벤트 버블링 + +```js +const parentEl = document.querySelector(".parent"); +const childEl = document.querySelector(".child"); +const anchorEl = document.querySelector("a"); + +window.addEventListener("click", () => { + console.log("윈도우"); +}); +document.documentElement.addEventListener("click", () => { + console.log("HTML"); +}); +document.body.addEventListener("click", () => { + console.log("body"); +}); +parentEl.addEventListener("click", (e) => { + console.log("parent"); // e.stopPropagation(); // 버블링 정지! +}); +childEl.addEventListener("click", () => { + console.log("child"); +}); +anchorEl.addEventListener("click", () => { + console.log("a"); +}); +``` + +이러한 코드가 있다고 하였을 때, a 요소를 클릭했다고 해보자. +그러면 a 요소의 상위 요소들의 같은 이벤트들은 전부 발생할 것이다. + +이런식으로 하위 요소의 이벤트를 발생하였을 때, 같은 이벤트의 상위 요소의 이벤트도 발생되는 현상을 이벤트 버블링이라고 한다. + +이를 멈추게 하기 위해서는 **event.stopPropagation()** 을 사용 해야한다. + +## 이벤트 캡처링 + +```js +const parentEl = document.querySelector(".parent"); +const childEl = document.querySelector(".child"); +const anchorEl = document.querySelector("a"); + +window.addEventListener("click", () => { + console.log("윈도우"); +}); +document.documentElement.addEventListener("click", () => { + console.log("HTML"); +}); +document.body.addEventListener("click", () => { + console.log("body"); +}); +parentEl.addEventListener("click", (e) => { + console.log("parent"); // e.stopPropagation(); // 버블링 정지! +}); +childEl.addEventListener("click", () => { + console.log("child"); +}); +anchorEl.addEventListener("click", (e) => { + e.preventDefault(); + console.log("a"); +}); +``` + +이러한 코드가 있다고 해보자. + +a > c > p > b > h > w 순서대로 이벤트 버블링이 발생하여 차례차례 이벤트가 발생할 것이다. +그런데 여기서 a보다 body요소가 먼저 이벤트가 발생되면 좋겠다라고 생각한다면, 우리가 아직 몰랐던 addEventListener의 3번째 인수를 사용할 수 있다. + +```js +document.body.addEventListener( + "click", + () => { + console.log("body"); + }, + { capture: true }, +); +``` + +이런식으로 객체의 형태로 capture: true를 해준다면 a요소를 클릭하고 이벤트 버블링을 확인한 후에 capture가 되어있는 요소의 이벤트를 가장 먼저 발생시킨다. +또한 capture는 capture되어는 요소 중에 더 상위요소를 먼저 발생시킨다. + +## 한글 입력 이벤트 중복 + +브라우저 입력기(IME)의 CJK(중국어, 일본어, 한국어) 문자 구성 중에는 이벤트 핸들러가 2번 실행될 수 있습니다. + +```js +const inputEl = document.querySelector("input"); +inputEl.addEventListener("keydown", (e) => { + // e.isComposing - 한글 입력 중인지 여부를 확인합니다. + // if (e.isComposing) return; + if (e.key === "Enter") { + const h1El = document.createElement("h1"); + h1El.textContent = inputEl.value; + document.body.append(h1El); + } +}); +``` + +그래서 이러한 코드가 실행되었을 때, h1이 두 개가 만들어질 수도 있다. +그럴 떄, e.isComposing을 사용해서 CJK 문자 구성 중에서는 return 시키면 해결이 된다. + +## 마우스와 포인터 이벤트 + +> 포인터 이벤트는 마우스 이벤트의 하위 이벤트이다. (사실상 둘이 같다고 봐야됨) + +```js +const parentEl = document.querySelector(".parent"); +const childEls = document.querySelectorAll(".child"); + +childEls.forEach((childEl) => { + // click - 클릭했을 때, + // dblclick - 더블 클릭했을 때 + childEl.addEventListener("click", () => { + childEl.classList.toggle("active"); + }); // contextmenu - 우클릭 했을 때 + + childEl.addEventListener("contextmenu", (e) => { + e.preventDefault(); + console.log(childEl.textContent); + }); +}); + +// mousedown - 버튼을 누를 때 +// mouseup - 버튼을 땔 때 +// mouseenter - 포인터가 요소로 들어갈 때 +// mouseleave - 포인터가 요소에서 나올 때 +parentEl.addEventListener("mousedown", () => { + parentEl.classList.add("active"); +}); +parentEl.addEventListener("mouseup", () => { + parentEl.classList.remove("active"); +}); + +// mousemove - 포인터가 움직일 때 +parentEl.addEventListener("mousemove", (e) => { + // console.log(e.x, e.y); +}); + +parentEl.addEventListener("wheel", (e) => { + // console.log("Parent Wheel"); +}); +``` + +## 키보드 이벤트 + +```js +const inputEl = document.querySelector("input"); + +// keydown - 키를 누를 때 +// keyup - 키를 땔 때 +inputEl.addEventListener("keydown", (e) => { + console.log(e.key); + if (e.key === "Enter") { + console.log("엔터"); + } + if (e.key === "Escape") { + console.log("ESC"); + } + if (e.key === " ") { + console.log("스페이스"); + } +}); +``` + +keypress같은 경우에는 Deprecated되어서 더 이상 사용하지 않는다. + +## 양식과 포커스이벤트 + +양식(form)에서 사용하는 이벤트에 대해 알아보자. + +```js +const formEl = document.querySelector("#login"); +const inputEls = document.querySelectorAll("input"); + +inputEls.forEach((el) => { + // focus(focusin) - 요소가 포커스를 얻었을 때 + el.addEventListener("focus", () => { + formEl.classList.add("active"); + }); // blur(focusout) - 요소가 포커스를 잃었을 때 + el.addEventListener("blur", () => { + formEl.classList.remove("active"); + }); // input - 값이 변경되었을 때 // change - 상태가 변경되었을 때 + el.addEventListener("input", (e) => { + console.log(e.target.value); + }); +}); + +// submit - 제출 버튼을 선택했을 때 +formEl.addEventListener("submit", (e) => { + e.preventDefault(); + const data = { + id: e.target[0].value, + pw: e.target[1].value, + }; // fetch(https://....) + console.log("서버로 제출했습니다."); +}); + +// reset - 리셋 버튼을 선택했을 때 +formEl.addEventListener("reset", () => { + console.log("모든 값이 초기화되었습니다."); +}); +``` diff --git "a/hyunmin/ZeroBase/\354\236\205\353\254\270\354\236\220 \354\236\220\353\260\224\354\212\244\355\201\254\353\246\275\355\212\270/07. JS-\354\266\224\352\260\200\355\225\231\354\212\265.md" "b/hyunmin/ZeroBase/\354\236\205\353\254\270\354\236\220 \354\236\220\353\260\224\354\212\244\355\201\254\353\246\275\355\212\270/07. JS-\354\266\224\352\260\200\355\225\231\354\212\265.md" new file mode 100644 index 0000000..d7a0d7b --- /dev/null +++ "b/hyunmin/ZeroBase/\354\236\205\353\254\270\354\236\220 \354\236\220\353\260\224\354\212\244\355\201\254\353\246\275\355\212\270/07. JS-\354\266\224\352\260\200\355\225\231\354\212\265.md" @@ -0,0 +1,342 @@ +> 제로베이스 자바스크립트 기초개념 추가학습 부분 정리 +> 축약된 부분이 존재할 수 있습니다. + +# 추가학습 + +사이사이 넣기 애매했던거 배우는 시간 + +## 구조 분해 할당 + +배열이나 객체의 구조에 맞게 바로 개별 변수에 값을 할당하는 방법으로, +필요한 값만 추출하여 변수에 할당할 수 있습니다. + +### 배열 구조 분해 할당 + +```js +const number = [1, 2, 3]; +// const a = number[0]; +// const b = number[1]; +// const c = number[2]; +const [a, b, c] = numbers; + +console.log(a, b, c); +``` + +기본적으로 이렇게 동작이되고, 다양한 패턴들이 존재한다고 한다. +**선언과 분리** + +```js +const number = [1, 2, 3]; +let a; +let b; +let c; +if (number.length) { + [a, b, c] = numbers; +} + +console.log(a, b, c); +``` + +선언과 분리를 하는 이유는 변수에는 scope라는 유효범위가 있는데 if문같은데 넣어서 선언을 해버린다면 밖에서는 사용할 수 없는 상황이 생길 수도 있기때문이다. +**기본값** + +```js +const number = [, , 3]; +const [a = 0, b, c] = number; + +console.log(a, b, c); +``` + +이런식으로 기본값을 지정해줄 수 있다. +**반환 값 무시** + +```js +const number = [1, 2, 3]; +const [, , c] = number; + +console.log(a, b, c); +``` + +이렇게 원하는 값만 빼먹을 수 있다. +**나머지 할당** + +```js +const number = [1, 2, 3]; +const [a, ...rest] = number; + +console.log(a, b, c); +``` + +### 객체 구조 분해 할당 + +```js +const user = { + name: "200won", + age: 200, + isValid: true, +}; +// const name = user.name; +// const age = user.age; +// const isValid = user.isValid; +const { name, age, isValid } = user; + +console.log(name, age, isValid); +``` + +객체 데이터는 순서가 없기때문에, 순서없이 적어도 사용가능하다.\ +**선언과 분리** + +```js +const user = { + name: "200won", + age: 200, + isValid: true, +}; +let name; +let age; +let isValid; +if (user) { + ({ name, age, isValid } = user); +} + +console.log(name, age, isValid); +``` + +**기본값** + +```js +const user = { + name: "200won", + age: 200, + isValid: true, +}; +const { isValid = false } = user; + +console.log(isValid); +``` + +**변수명 변경** + +```js +const user = { + name: "200won", + age: 200, + isValid: true, +}; +const { name: a, age: b, isValid: c } = user; + +console.log(a, b, c); +``` + +이런식으로 객체를 받고 이름을 변경해서 변수를 생성할 수 있다. +**기본값 + 변수명 변경** + +```js +const user = { + name: "200won", + age: 200, + isValid: true, +}; +const { name: a, age: b, isValid: c = false } = user; + +console.log(a, b, c); +``` + +**나머지 할당** + +```js +const user = { + name: "200won", + age: 200, + isValid: true, +}; +const { name, ...rest } = user; + +console.log(name, rest); +``` + +배열과 동일하게 나머지 할당도 사용이 가능하다. + +## 선택적 체이닝 + +**?.** + +- 대괄호 혹은 점 표기법의 대상이 null 혹은 undefined인 경우, 에러 대신 undefined를 반환합니다. + 우리가 변수를 활용하다보면 의도치 않게 null.abc라든가 이렇게 사용될 수 있을 때가 있다. 그럴 때 선택적 체이닝을 사용해서 에러없이 아래 코드를 실행시킬 수 있다! + +```js +console.log(null?.abc); +console.log(undefined?.abc); + +const el = document.querySelector("h1"); +console.log(el?.textContent); + +// const numbers = [1, 2, 3]; +const number = null; +// 대괄호에서도 쓸 수 있다 ㄷㄷ +console.log(numbers?.[0]); + +const user = { + name: "200원", + age: 22, +}; +// const user = null; +console.log(user?.name); +``` + +함수에도 `user.func?.()`처럼 사용할 수 있다. + +## 모듈 + +자바스크립트 코드를 파일로 구분해서 데이터를 내보내거나 가지고 와서 사용할 수 있는 개념이다. + +```js +function add(a, b) { + return a + b; +} +function sub(a, b) { + return a - b; +} +function getUserBirthYear(user) { + const year = new Date().getFullYear; + return year - user.age; +} +const fruits = ["사과", "바나나", "체리"]; +const addFruits = (fruit) => { + fruits.push(fruit); +}; + +console.log(add(2, 7)); +console.log(sub(2, 7)); + +const thw = { + name: "200원", + age: 200, +}; +const hhm = { + name: "황현민", + age: 19, +}; +console.log(getUserBirthYear(thw)); +console.log(getUserBirthYear(hhm)); + +addFruits("오렌지"); +addFruits("망고"); +console.log(fruits); +``` + +위에 있는 함수들을 활용하고 있다. +그럼 이것을 모듈개념으로 정리해보자. + +일단 계산하는 함수를 모아두는 calculator.js 파일을 만들어주자. + +```js +function add(a, b) { + return a + b; +} +function sub(a, b) { + return a - b; +} +``` + +그럼 이런식으로 작성하면 될까? +아니다 이제 import와 export라는 개념을 사용해줘야 한다. + +```js +// calculaotr.js +export function add(a, b) { + return a + b; +} +export function sub(a, b) { + return a - b; +} +``` + +```js +// main.js + +// 추가해주기 +import { add, sub } from "./calculator.js"; +``` + +그리고 또한 HTML에서 불러오는 js파일을 module와 시켜줘야 한다. + +```html + +``` + +이런식으로 모듈화를 시켜준다면, 작동이 잘 되는것을 알 수 있다. + +그럼 따른 함수들도 모듈화를 시켜보자. + +```js +import { add, sub } from "./calculator.js"; +import { getUserBirthYear } from "./user.js"; +import { fruits, addFruits } from "./fruits.js"; + +console.log(add(2, 7)); +console.log(sub(2, 7)); + +const thw = { + name: "200원", + age: 200, +}; +const hhm = { + name: "황현민", + age: 19, +}; +console.log(getUserBirthYear(thw)); +console.log(getUserBirthYear(hhm)); + +addFruits("오렌지"); +addFruits("망고"); +console.log(fruits); +``` + +이런식으로 main.js에 있던 함수를 구분별로 모듈화를 시켰고, import로 main.js에 불러와서 사용할 수 있다. + +이런식으로 하면 좀 더 간결하게 관리할 수 있어진다. +또한 기능들을 폴더에 넣어서 관리할 수도 있다. + +### import & export 사용 패턴 + +import & export를 하나라도 가지고 있으면 module이다. +**import(가져오기)** + +- JS 파일의 최상단에 위치해야 합니다. + +```js +import {} from "./module.js"; +``` + +**export(내보내기)** + +```js +// 기본 내보내기(Default export) +// - 이름이 필요치 않습니다. +// - 모듈에서 1번만 사용할 수 있습니다. +// export default 데이터; +export default [1, 2, 3]; + +// 이름 내보내기(Named export) +// - 이름이 필수입니다. +// - 모듈에서 여러번 사용할 수 있습니다. +// export const 이름1 = 데이터1; +// export const 이름2 = 데이터2; +// export function 이름3() {} +// export const a = 1; +// export const b = 3; + +const a = 1; +const b = 3; + +// 모아서 내보내기 (객체 아님) +export { a as a1, b }; +``` + +```js +// 기본 내보내기는 이름을 import에서 지정함 +// 이름 내보내기는 import에서 이름대로 가져오고 이름을 바꾸고 싶다면 as 사용 +// 와일드카드(*)을 사용해서 다 가져올 수도 있음 +import abc, { a as b } from "./module.js"; +``` diff --git "a/hyunmin/ZeroBase/\354\236\205\353\254\270\354\236\220 \354\236\220\353\260\224\354\212\244\355\201\254\353\246\275\355\212\270/08. JS-\355\201\264\353\236\230\354\212\244.md" "b/hyunmin/ZeroBase/\354\236\205\353\254\270\354\236\220 \354\236\220\353\260\224\354\212\244\355\201\254\353\246\275\355\212\270/08. JS-\355\201\264\353\236\230\354\212\244.md" new file mode 100644 index 0000000..a29c586 --- /dev/null +++ "b/hyunmin/ZeroBase/\354\236\205\353\254\270\354\236\220 \354\236\220\353\260\224\354\212\244\355\201\254\353\246\275\355\212\270/08. JS-\355\201\264\353\236\230\354\212\244.md" @@ -0,0 +1,272 @@ +> 제로베이스 자바스크립트 기초개념 클래스 부분 정리 +> 축약된 부분이 존재할 수 있습니다. + +# 클래스 + +클래스라는 것에 대해서 배워보자 + +## 개요 + +```js +// 객체 리터럴 +const thw = { + name: "200원", + age: 200, + getBirthYear() { + const year = new Date().getFullYear(); + return year - this.age; + }, +}; + +const hhm = { + name: "황현민", + age: 19, + getBirthYear() { + const year = new Date().getFullYear(); + return year - this.age; + }, +}; + +console.log(thw.getBirthYear()); +console.log(hhm.getBirthYear()); +console.log(thw.getBirthYear === hhm.getBirthYear); // false +``` + +이렇게 객체 두 개의 동일한 함수를 놓고 사용하게 되면 두 개의 함수가 같지 않다고 판단하여 메모리에 두 개가 할당이 된다. 하지만 내용은 같기에 비효율적이라고 볼 수 있다. + +```js +const thw = { + name: "200원", + age: 200, + getBirthYear() { + const year = new Date().getFullYear(); + return year - this.age; + }, +}; + +const hhm = { + name: "황현민", + age: 19, +}; + +console.log(thw.getBirthYear()); +// 함수의 this.age의 this가 hhm을 가리키게 됨 +console.log(thw.getBirthYear.call(hhm)); +// console.log(thw.getBirthYear === hhm.getBirthYear); // false +``` + +그럴 때, 이런식으로 사용하게 되면 메모리에 함수는 하나만 존재하게 되니 좀 더 효율적이라고 볼 수 있다. +하지만 이 부분도 계속 thw객체에서 함수를 불러와야하는 행위를 해야하기에 이때 클래스를 활용해 볼 수 있다. + +### 프로토타입 + +```js +// 프로토타입 +function User(name, age) { + this.name = name; + this.age = age; +} +User.prototype.getBirthYear = function () { + const year = new Date().getFullYear(); + return year - this.age; +}; + +const thw = new User("thw", 200); +const hhm = new User("hhm", 19); + +console.log(thw); +console.log(hhm); +console.log(thw.getBirthYear()); +console.log(hhm.getBirthYear()); +console.log(thw.getBirthYear() === hhm.getBirthYear()); +``` + +이런식으로 프로토타입을 이용해서 기존의 방법을 개선할 수 있다. +그럼에도 단점이 존재하는데, User부분이 function으로 되어있어서 함수인지 클래스인지 헷갈릴수 있다. +그래서 이것을 자바스크립트의 최신문법으로 class로 바꿀 수 있다. + +```js +// 프로토타입 +class User { + constructor(name, age) { + this.name = name; + this.age = age; + } + getBirthYear() { + const year = new Date().getFullYear(); + return year - this.age; + } +} + +const thw = new User("thw", 200); +const hhm = new User("hhm", 19); + +console.log(thw); +console.log(hhm); +console.log(thw.getBirthYear()); +console.log(hhm.getBirthYear()); +console.log(thw.getBirthYear() === hhm.getBirthYear()); +``` + +이런식으로 prototype을 class로 바꿔서 사용할 수도 있다. + +## Getter & Setter + +값을 얻거나 지정할 떄 사용하는 함수 + +```js +// class +class User { + constructor(first, last) { + this.first = first; + this.last = last; + this.fullName = `${first} ${last}`; + } +} + +const thw = new User("200", "원"); + +// Get +console.log(thw.first); +//Set +thw.fullName = "황 현민"; + +console.log(thw); +``` + +이런식으로 하게되면 fullName을 바꾸게 될 때, first와 last는 안바껴서 불편할 수 있다. 또 다른 구조라면 더 불편할 수도 있다. +이럴 때 클래스의 Getter와 Setter라는 개념이 사용된다. + +```js +// class +class User { + constructor(first, last) { + this.first = first; + this.last = last; + } + get fullName() { + return `${this.firstName} ${this.lastName}`; + } + set fullName(value) { + const names = value.split(" "); + this.first = names[0]; + this.last = names[1]; + } +} + +const thw = new User("200", "원"); + +// Get +console.log(thw.fullName); +//Set +thw.fullName = "황 현민"; + +console.log(thw); +``` + +이런식으로 Getter와 Setter를 사용해서 함수를 속성처럼 사용할 수 있다. + +## 정적 메소드 + +정적 메소드(static method)는 주로 클래스의 유틸리티(보조) 함수를 만들 때 사용됩니다. +인스턴스와는 연결되지 않으며 , 클래스 자체에서 호출해야 합니다. + +```js +// 첫 번째 방식이 클래스의 생성자를 이용해서 인스턴스를 만드는 방식이고 +// 이 생성자 방식을 좀 더 쉽게 만든 것이 기호를 사용해서 인스턴스를 만드는 두 번째의 리터럴 방식이다. +const fruits = new Array("Apple", "Banana", "Cherry"); +// const fruits = ["Apple", "Banana", "Cherry"]; + +// fruits.includes("Apple"); +// fruits.filter(item => item); +// fruits.push("orange"); +// 프로토타입 함수의 그 클래스의 모든 인스턴스에서 사용이 가능하다. +Array.prototype.abc = function () { + console.log(this); + return this.map((item) => item.slice(0, 1).toLowerCase()); +}; + +const newFruits = fruits.abc(); +console.log(newFruits); +console.log(Array.isArray(fruits)); +// fruits.isArray()가 없는 이유를 이제 알겠나? +// 모든 데이터 형식의 isArray()를 넣게 되면 낭비가 심하기 때문이다. +// 누가봐도 객체인데 객체.isArray()??를 하는 것이 이상하기 때문이다. +// 이러하여 정적 메소드를 사용해서 보조하는 기능을 만드는 것 같다. +console.log(Array.isArray(newFruits)); + +const user = { name: "200원" }; +console.log(Array.isArray(user)); +// user.isArray() +``` + +위 코드를 보며 prototype 함수와 정적 메소드의 차이를 확인할 수 있다. + +그럼 실제로 정적 메소드를 어떻게 만들 수 있을까? + +```js +class User { + constructor(first, last) { + // this 키워드는 클래스로 생성된 인스턴스라고 생각하면 된다. + this.firstName = first; + this.lastName = last; + } + static isUser(user) { + return user instanceof User; + } +} + +const thw = new User("200", "원"); +const hhm = new user("황", "현민"); +const hhh = { + name: "황황현", + age: 100, +}; + +console.log(User.isUser(thw)); +console.log(User.isUser(hhm)); +console.log(User.isUser(hhh)); +``` + +이런식으로 정적메소드를 만들어서 사용할 수 있다. + +## 상속 + +클래스의 속성과 메소드를 다른 클래스에게 확(Extends)해서 재사용하는 기능을 말합니다. + +```js +class A { + constructor(a) { + this.a = a; + } +} + +class B extends A { + constructor(a, b) { + // super를 사용하게 되면 부모 클래스의 생성자에 접근해서 값을 넣어준다. + super(a); + this.b = b; + } +} + +const a = new A(1); +const b = new B(1, 2); + +// a의 prototype이 Object라는 것은 a도 부모 클래스로 object를 상속받고 있다는 것이다. +console.log(a); +// b의 prototype은 A클래스로 부모 클래스로 A클래스를 상속받고 있다는 것이다. +console.log(b); + +// A클래스에서 만들었기에 A클래스의 인스턴스가 맞음 +console.log(a instanceof A); // true +// B클래스에서 만들었지만 A클래스의 확장 버전이기 때문에 A클래스의 인스턴스라고 볼 수 있음 +console.log(b instanceof A); // true + +// A클래스는 B라는 클래스의 상위 클래스이기 때문에 B라는 클래스의 인스턴라고 볼 수 없다. +console.log(a instanceof B); // false +// B클래스에서 만들었기에 Bs클래스의 인스턴스가 맞음 +console.log(b instanceof B); // true + +console.log(a instanceof Object); // true +console.log(b instanceof Object); // true +``` diff --git "a/hyunmin/ZeroBase/\354\236\205\353\254\270\354\236\220 \354\236\220\353\260\224\354\212\244\355\201\254\353\246\275\355\212\270/09. JS-\353\271\204\353\217\231\352\270\260.md" "b/hyunmin/ZeroBase/\354\236\205\353\254\270\354\236\220 \354\236\220\353\260\224\354\212\244\355\201\254\353\246\275\355\212\270/09. JS-\353\271\204\353\217\231\352\270\260.md" new file mode 100644 index 0000000..1b44e70 --- /dev/null +++ "b/hyunmin/ZeroBase/\354\236\205\353\254\270\354\236\220 \354\236\220\353\260\224\354\212\244\355\201\254\353\246\275\355\212\270/09. JS-\353\271\204\353\217\231\352\270\260.md" @@ -0,0 +1,600 @@ +> 제로베이스 자바스크립트 기초개념 비동기 부분 정리 +> 축약된 부분이 존재할 수 있습니다. + +# 비동기 + +## 개요 + +### 동기 + +코드는 작성된 순서대로 실행되며, 하나의 작업이 끝나기 전에는 다음 작업이 시작되지 않습니다. + +```js +console.log(1); +console.log(2); +alert("확인"); +console.log(3); +console.time("Loop!"); +for (let i = 0; i < 10000000; i++) {} +console.timeEnd("Loop!"); +console.log(4); +``` + +이런식으로 전 코드가 실행되기전까지는 뒤에 코드가 실행되지 않는다. + +### 비동기 + +코드는 작성된 순서대로 실행되지만, 특정 작업이 끝나기 전에 다음 작업이 시작될 수 있습니다. + +```js +console.log(1); +console.log(2); +console.log(3); +console.time("Loop!"); +// 비동기 함수 +setTimeout(() => { + for (let i = 0; i < 10000000; i++) {} + console.timeEnd("Loop!"); +}, 0); +console.log(4); +``` + +```js +console.log(1); +const h1El = document.querySelector("h1"); +h1El.addEventListener("click", () => { + console.log("클릭"); +}); +console.log(2); +``` + +```js +console.log(1); +fetch("https://api....") + .then((res) => res.json()) + .then((data) => console.log(data)); +console.log(2); +``` + +## 콜백과 콜백 지옥 + +```js +function timer() { + setTimeout(() => { + console.log(1); + }, 2000); +} + +timer(); +console.log(2); +``` + +이렇게 존재할 때 숫자1을 2보다 먼저 출력시키고 싶다고 한다면 어떻게 해야할까? +물론 setTimeout안에 console.log(2)를 넣을수도 있겠지만, 만약 모듈화가 되어있어서 건드릴 수 없다면? 그럴 때 콜백을 사용하면 된다. + +```js +function timer(callback) { + setTimeout(() => { + console.log(1); + callback(); + }, 2000); +} + +timer(() => { + console.log(2); +}); +``` + +이 콜백 패턴의 핵심은 우리가 비동기 함수를 호출할 때, 콜백 함수를 전달해줘서 콜백 함수가 정확히 어디서 실행될지 지정해주는 것이 핵심이다. + +하지만, 단점이 존재하는데 바로 콜백 지옥이다. + +```js +function renderImage(callback) { + const imgEl = document.createElement("img"); + imgEl.src = "https://picsum.photos/3000/2000"; + imgEl.addEventListener("load", () => { + document.body.append(imgEl); + callback(); + }); +} +renderImage(() => { + console.log("Done 1"); +}); +renderImage(() => { + console.log("Done 2"); +}); +renderImage(() => { + console.log("Done 3"); +}); +renderImage(() => { + console.log("Done 4"); +}); +``` + +우리가 코드를 이런식으로 작성하게 된다면, Done의 순서가 없이 출력이 될 것이다. +왜냐하면, 첫 번째가 가장 먼저 도착할 것이라는 보장이 없기 때문이다. +인터넷 속도에 따라서 로드되는 속도가 서로 다르기 때문이다. +즉, 출발은 순서대로 해도 도착은 순서대로 된다는 보장이 없다. + +그래서 이 문제점을 해결하기 위해 동기적으로 코드를 바꾸면.. + +```js +function renderImage(callback) { + const imgEl = document.createElement("img"); + imgEl.src = "https://picsum.photos/3000/2000"; + imgEl.addEventListener("load", () => { + document.body.append(imgEl); + callback(); + }); +} +renderImage(() => { + console.log("Done 1"); + renderImage(() => { + console.log("Done 2"); + renderImage(() => { + console.log("Done 3"); + renderImage(() => { + console.log("Done 4"); + }); + }); + }); +}); +``` + +그럼 이런식의 코드가 만들어지는데 마치 콜백들이 개미지옥같이 들여쓰기가 계속 되는 모습을 콜백 지옥이라고 한다. + +이것을 개선하기 위해서는 promise라는 클래스를 사용할 수 있다. +promise는 다음시간에 배울 것이기에 일단 수정부터 해보겠다. + +```js +function renderImage() +  return new Promise((resolve) => { +    const imgEl = document.createElement("img"); +    imgEl.src = "https://picsum.photos/3000/2000"; +    imgEl.addEventListener("load", () => { +      document.body.append(imgEl); +      resolve(); +    }); +  }); +} +renderImage() +  .then(() => { +    console.log("Done 1"); +    return renderImage(); +  }) +  .then(() => { +    console.log("Done 2"); +    return renderImage(); +  }) +  .then(() => { +    console.log("Done 3"); +    return renderImage(); +  }) +  .then(() => { +    console.log("Done 4"); +  }); +``` + +이런식으로 promise를 사용하게 된다면, 콜백 지옥에서 벗어나 좀 더 가독성 좋게 코드를 짤 수 있다. + +## Promise + +비동기 작업의 완료나 실패 지점을 지정하고 그 결과를 반환할 수 있습니다.` + +```js +function timer(cb) { + setTimeout(() => { + console.log(1); + cb("Done!"); + }); +} + +timer((msg) => { + console.log(msg); + console.log(2); +}); +``` + +이 코드를 promise를 사용한 코드로 바꿔본다면 + +````js +function timer() { +  return new Promise((resolve, reject) => { +    if (error) { +      // reject 호출시 resolve 호출 안됨 +      reject(); +    } +    setTimeout(() => { +      console.log(1); +      // resolve 호출시 reject 호출 안됨 +      resolve("Done!"); +    }); +  }); +} + +timer() +  .then((msg) => { +    console.log(msg); +    console.log(2); +    return timer(); +  }) +  .then((msg) => { +    console.log(msg); +    console.log(2); +    return timer(); +  }) +  .then((msg) => { +    console.log(msg); +    console.log(2); +  }); +  ``` +resolve와 reject는 반대되는 개념이라고 생각하면 된다. +reject는 추후에 다시 다루게 될 것이다. +```js +function loadImage(src) { +  return new Promise((resolve) => { +    const imgEl = document.createElement("img"); +    imgEl.src = src; +    imgEl.addEventListener("load", () => { +      resolve(imgEl); +    }); +  }); +} +loadImage("https://picsum.photos/3000/2000").then((imgEl) => { +  document.body.append(imgEl); +  console.log("Done"); +}); +loadImage("https://picsum.photos/100/200").then((imgEl) => { +  console.log(imgEl); +}) +```` + +그리고 위에 사진을 불러오는 코드를 좀 더 promise를 사용해서 활용성있게 만들어줄 수도 있습니다. +promise라는게 약속을 나타내는 이름이다 보니 콜백 함수가 어떤 시점에 실행 된다는 것을 약속한다는 의미를 지니고 있고, 그때 약속이 이행되었으면 then메소드를 호출하겠다는 것이다. + +다음에는 async와 await에 대해서 배워볼 것이다. +then보다 최신 기술이기에 배워두면 좋다. + +## Async & Await + +```js +const h1El = document.querySelector("h1"); +const ulEl = document.createElement("ul"); +document.body.append(ulEl); + +h1El.addEventListener("click", () => { + ulEl.textContent = "Loading..."; // promise instance가 반환이 된다. + fetch("https://api.heropy.dev/v0/users") + .then((res) => res.json()) + .then((data) => { + const { users } = data; + const liEls = users.map((user) => { + const liEl = document.createElement("li"); + liEl.textContent = user.name; + const imgEl = document.createElement("img"); + imgEl.src = user.photo?.url || "https://heropy.dev/favicon.png"; + liEl.prepend(imgEl); + return liEl; + }); + ulEl.textContent = ""; + ulEl.append(...liEls); + }); +}); +``` + +일단 이런식으로 기본 코드가 있을 때, + +```js +const h1El = document.querySelector("h1"); +const ulEl = document.createElement("ul"); +document.body.append(ulEl); + +h1El.addEventListener("click", async () => { + ulEl.textContent = "Loading..."; // promise instance가 반환이 된다. // await로 데이터를 가져오는 것을 기다림 (promise instance에만 사용가능) + const res = await fetch("https://api.heropy.dev/v0/users"); // 데이터 분석이 끝나는 것을 기다림 + const data = await res.json(); // promise instance 반환함 + const { users } = data; + const liEls = users.map((user) => { + const liEl = document.createElement("li"); + liEl.textContent = user.name; + const imgEl = document.createElement("img"); + imgEl.src = user.photo?.url || "https://heropy.dev/favicon.png"; + liEl.prepend(imgEl); + return liEl; + }); + ulEl.textContent = ""; + ulEl.append(...liEls); +}); +``` + +async와 await을 사용해서 위 코드처럼도 만들어줄 수 있다. +await은 무조건 promise instance에만 사용할 수 있고 await을 사용하는 가장 가까운 함수에 async가 있어야 한다. + +## 예외 처리 + +`fetch("https://api.heropy.dev/v0/users");` 만약 이런식으로 서버로 데이터를 요청하는 코드를 작성하였을 때, 우리가 글자를 잘 못 작성하거나 서버가 고장나있는 상태라면 오류가 나게될 것이다. 이러한 예외 상황들이 있을 때, 처리하는 코드를 만드는 것도 중요하다고 할 수 있다. + +### try~catch + +이 때, 우리는 try~catch를 사용해서 예외 처리를 해줄 수 있다. + +```js +const h1El = document.querySelector("h1"); +const ulEl = document.createElement("ul"); +document.body.append(ulEl); + +h1El.addEventListener("click", async () => { + ulEl.textContent = "Loading..."; // promise instance가 반환이 된다. + try { + const res = await fetch("https://api.heropy.dev/v0/users"); + const data = await res.json(); + const { users } = data; + const liEls = users.map((user) => { + const liEl = document.createElement("li"); + liEl.textContent = user.name; + const imgEl = document.createElement("img"); + imgEl.src = user.photo?.url || "https://heropy.dev/favicon.png"; + liEl.prepend(imgEl); + return liEl; + }); + ulEl.textContent = ""; + ulEl.append(...liEls); + } catch (error) { + console.log(error); + } +}); +``` + +이런식으로 try에서 시도를 해보다가 에러가 발생하면 catch부분에서 에러를 받아서 코드를 실행할 수 있다. + +또한, try나 catch에 둘 다 넣어야 하는 코드가 있다면 finally 구문에 넣어주면 된다. + +```js +try { + // 시도 해보다가 에러나면 그 아래부터 실행안하고 catch로 넘어감 +} catch (error) { + // 에러나면 실행됨 +} finally { + // 에러가 나든 안나든 실행됨 +} +``` + +그리고 여기서 reject를 말해볼 건데 +만약, img를 불러오다가 error가 나게 되면 resolve밖에 없는 promise는 에러를 띄우지 않고 계속 약속을 이행하겠다라고만 하기에 우리는 reject라는 것들 추가해 addListener로 error라는 이벤트를 추가해서 reject를 실행시켜줄 수 있다. +그리고 reject에서 new Error라는 객체를 넘겨서 catch의 error에서 받을 수 있다. + +```js +reject(new Error("이미지를 로드할 수 없어..")); +``` + +그러면 이행과 거부. 예외 처리 부분을 한 번 정리해보자 + +```js +// 이행과 거부, 예외 처리 + +//매개 변수 +// resolve - 약속을 이행하는 함수(정상 처리) +// reject - 약속을 거부하는 함수(에러 상황) + +// 용어 정리 +// Pending - 약속이 이행되거나 거부되기 전 상태 +// Fulfilled - 약속이 이행된 상태 +// Rejected - 약속이 거부된 상태 +function loadImage(src) { + // Pending... + return new Promise((resolve, reject) => { + if (!src) { + reject(new Error("이미지 경로가 필요해요")); // Rejected + } + const imgEl = document.createElement("img"); + imgEl.src = src; + imgEl.addEventListener("load", () => { + resolve(imgEl); // Fulfilled + }); + imgEl.addEventListener("error", () => { + reject(new Error("이미지를 불러올 수 없어요")); // Rejected + }); + }); +} + +// .then() /.catch() /.finally() +// - 약속이 이행되었을 때 호출(then)하거나, +// - 약속이 거부되있을 때 호출(catch)하거나, +// - 이행 및 거부와 상관없이 항상 호출(finally)하는 메소드를 제공할 수 있습니다. +loadImage("https://picsum.photo/300") + .then((imgEl) => { + document.body.append(imgEl); + }) + .catch((error) => { + console.log(error.message); + }) + .finally(() => { + console.log("Done"); + }); + +// try / catch / finally +// - 에러(예외)가 발생할 수 있는 코드의 실행을 시도(try)하고, +// - 에러가 발생하면 시도를 종료해 에러를 잡아내며(catch), +// - 에러 여부와 상관없이 항상 실행(finally)하는 코드를 정의할 수 있습니다. +(async () => { + try { + const imgEl = await loadImage("https://picsum.photo/300"); + document.body.append(imgEl); + } catch (error) { + console.log(error.message); + } finally { + console.log("Done!"); + } +})(); +``` + +이렇게 이행과 거부 부분을 정리할 수 있을 것 같다. + +# 네트워크 통신을 위한 fetch 함수 + +우리가 앞에서 사용했던 fetch함수에 대해서 자세히 알아보고 네트워크 통신을 하기 위해서 알아야 하는 것들을 공부해보자. + +```js +fetch("https://api.heropy.dev/v0/users") + .then((res) => res.json()) + .then((data) => console.log(data)); +``` + +우리가 fetch로 서버의 주소를 입력하게 되면 서버에 데이터를 가지러 가게 된다. +그 시작지점이 client이고 도착 부분이 서버이다. 그리고 방향에 따라서 요청(Request)/응답(Response)이라고 부른다. + +그래서 우리가 웹 브라우저에서 서버로 요청을 하는 것이기에 웹사이트가 클라이언트 인 것이다. + +그럼 이제 요청과 응답에 필요한 것들을 알아보자. + +### 요청(Request) + +- url: 요청 서버 주 +- method: 요청 종류(GET, POST, PUT, DELETE 등) +- headers: 요청 메타 정보 (클라이어트의 환경 정보) +- body: 요청 데이터(POST, PUT) + 우리는 fetch를 할 때 url만 넣었는데 나머지 정보들을 어떻게 넣을까? + 바로 두 번째 인수인 객체에 넣어주면 된다. + +```js +fetch("https://api.heropy.dev/v0/users", { + method: "GET", + headers: {}, + body: undefined, +}) + .then((res) => res.json()) + .then((data) => console.log(data)); +``` + +우리가 원래 주소만 넣어주었던 이유는 나머지 정보를 생략해도 자동으로 값을 넣어주기 때문에 주소만 적어준 것이다. + +### 응답(Response) + +- status: 응답 상태 코드(200, 400, 500 등) +- headers: 응답 메타 정보 +- body: 응답 데이 +- ok: 정상적인 처리 여부 + 이런식의 정보가 들어있다. + +### CRUD + +CRUD는 create, read, update, delete를 합친 단어이다. + +- Create: POST - 데이터 생성 +- Read: GET - 데이터 조회 +- Update: PUT(PATCH) - 데이터 수정 +- Delete: DELETE - 데이터 삭제 + 우리가 요청을 할 때, method로 넘길 수 있는 것들이다. + +### URL 구조 + +https://www.heropy.dev/p/QOWqjv?key=value&a=12&b=34#h1-title + +- https: 통신 규약(Protocol) +- www.heropy.dev: 도메인 +- p/QOWqjv: 경로(Path) +- key=value&a=12&b=34: 쿼리(Query) +- \#h1-title: 해시(hash) + +### HTTP 상태 코드 + +- 1xx: 처리 +- 2xx: 성공 +- 3xx: 리다이랙트 +- 4xx: 클라이언트 오류 +- 5xx: 서버 오류 + +- 200: 정상적으로 처리됨 +- 400: 잘못된 요청 +- 401: 인증 정보가 부족 +- 403: 권한이 없음 +- 404: 찾을 수 없음 +- 500: 서버 오류 + +### fetch 함수 + +- fetch(url, options) +- options.method: 요청 종류(GET, POST, PUT, DELETE 등) +- options.headers: 요청 메타 정보 +- options.body: 요청 데이터 + +```js +// fetch(url, options) +// options.method: 요청 종류(GET, POST, PUT, DELETE 등) +// options.headers: 요청 메타 정보 +// options.body: 요청 데이터 +fetch("https://api.heropy.dev/v0/users", { + method: "POST", + headers: { + "Context-Type": "application/json", + }, + body: JSON.stringify({ + name: "황현민", + age: 19, + }), +}) + .then((res) => res.json()) + .then((data) => console.log(data)); +``` + +이런식으로 사용할 수 있다. + +# 반복문에서 비동기 처리 + +우리가 반복문에서 비동기 처리할 떄 주의 해야되는 점을 알아보자. + +```js +async function getMovies(movieName) { + const res = await fetch( + `https://omdbapi.com/?apikey=7035c60c&s=${movieName}`, + ); + return await res.json(); +} + +const titles = ["frozen", "avengers", "avatar"]; + +// 배열 메소드 반복 +titles.forEach(async (title, index) => { + const movies = await getMovies(title); + console.log(`${index + 1} ${title}`, movies); +}); +``` + +이런식의 코드가 존재한다고 하였을 때, 우리는 영화가 순서대로 출력된다고 생각할 수 있다. 하지만 forEach를 돌리면서 영화의 순서가 뒤죽박죽으로 출력이된다. + +우리가 배열데이터의 사용하는 콜백을 활용하는 메소드들 map, filter, reduce 등..의 메소드들의 콜백 함수는 배열 아이템의 순서대로 독립적으로 호출되는 개념이기에 하나의 콜백함수가 완료되는 것을 기다리지 않고, 바로 다음 콜백을 실행한다. + +즉, 호출은 순서대로 되지만 await로 기다리는 행위는 독립적으로 진행되어 먼저 응답을 받는 쪽이 출력이 되는 것이다. (이전 콜백의 기다림을 다음 콜백이 기다리지 않는다.) +우리가 순서대로 데이터를 받아와야 하는 상황이라면 배열의 콜백을 활용하는 메소드는 사용하면 안된다. + +순서를 보장받고 싶다면 간단하게 for반복문을 사용하면 된다. + +```js +async function getMovies(movieName) { + const res = await fetch( + `https://omdbapi.com/?apikey=7035c60c&s=${movieName}`, + ); + return await res.json(); +} + +const titles = ["frozen", "avengers", "avatar"]; + +// 배열 메소드 반복 +// titles.forEach(async (title, index) => { +//  const movies = await getMovies(title); +//  console.log(`${index + 1} ${title}`, movies); +// }); + +// for 반복문 +(async () => { + let index = 1; + for (const title of titles) { + const movies = await getMovies(title); + console.log(`${index + 1} ${title}`, movies); + index += 1; + } +})(); +``` + +이런식으로 for문을 사용하게 된다면 await키워드를 사용해서 기다리는 것이 가능하다. diff --git "a/hyunmin/ZeroBase/\354\236\205\353\254\270\354\236\220 \354\236\220\353\260\224\354\212\244\355\201\254\353\246\275\355\212\270/10. JS-\354\213\254\355\231\224\355\225\231\354\212\265.md" "b/hyunmin/ZeroBase/\354\236\205\353\254\270\354\236\220 \354\236\220\353\260\224\354\212\244\355\201\254\353\246\275\355\212\270/10. JS-\354\213\254\355\231\224\355\225\231\354\212\265.md" new file mode 100644 index 0000000..6d34adc --- /dev/null +++ "b/hyunmin/ZeroBase/\354\236\205\353\254\270\354\236\220 \354\236\220\353\260\224\354\212\244\355\201\254\353\246\275\355\212\270/10. JS-\354\213\254\355\231\224\355\225\231\354\212\265.md" @@ -0,0 +1,571 @@ +> 제로베이스 자바스크립트 기초개념 심화학습 부분 정리 +> 축약된 부분이 존재할 수 있습니다. + +# 심화학습 + +심화적인 부분 학습 + +## 함수 재귀 + +함수 재귀(Recursion)란, 함수가 자기 자신을 호출하는 것을 말합니다. + +```js +let i = 0; +function a() { + console.log(i, "A"); + + i += 1; + if (i < 4) { + a(); + } +} +a(); +``` + +이런식으로 함수안에서 자신을 부르는 것을 함수 재귀라고 한다. + +그럼 유용하게 어떻게 사용될 수 있을까? + +```js +const neo = { name: "Neo" }; +const evan = { name: "Evan", parent: neo }; +const lewis = { name: "Lewis", parent: evan }; +const amy = { name: "Amy", parent: lewis }; + +const getRootUser = (user) => { + if (user.parent) { + return getRootUser(user.parent); + } + return user; +}; + +console.log(getRootUser(amy)); +``` + +사용하는 예시를 보면 이런식으로 유용하게 사용할 수도 있다. + +## 함수 this + +일반 함수와 화살표 함수에 따라 다르게 정의됩니다. <- 가장 중요한 부분 +일반 함수는 호출 위치에서 this가 정의됩니다. +화살표 함수는 선언 위치(렉시컬 스코프)에서 this가 정의됩니다. + +```js +function User() { + this.name = "User"; + return { + name: "Neo", + age: 80, // getInfo() { //  return `${this.name}는 ${this.age}입니다.`; // }, + getInfo: () => { + return `${this.name}는 ${this.age}입니다.`; + }, + }; +} + +const u = new User(); +console.log(u.name); +console.log(u.age); +console.log(u.getInfo()); + +const evan = { + name: "Evan", + age: 25, +}; +console.log(u.getInfo.call(evan)); +``` + +일반 함수는 자신이 호출되는 범위인 return 안에 객체의 name과 age를 출력시키고 +화살표 함수는 호출된 함수를 감싸는 가장 가까운 함수가 this를 정의하는 영역이 되어 name은 User가 뜨고 age는 undefined가 뜬다. + +그래서 this키워드를 사용할 때, 일반 함수를 사용할 것인지 화살표 함수를 사용할 것인지 정하고 코드를 작성해야 한다. + +## call, bind, apply + +```js +const neo = { + name: "Neo", +}; +const amy = { + name: "Amy", + getInfo(age, city) { + return `${this.name}는 ${age}세이고, ${city}에 삽니다.`; + }, +}; +console.log(amy.getInfo(22, "서울")); + +// .call(this, 인수1, 인수2, ...) +// 대상 함수를 주어진 객체(this)의 메소드로 실행합니다. +console.log(amy.getInfo.call(neo, 1, "부산")); + +// .apply(this, [인수1, 인수2, ...]) +// 대상 함수를 주어진 객체(this)의 메소드로 실행합니다. +console.log(amy.getInfo.apply(neo, [85, "서울"])); + +// .bind(this) +// 대상 함수를 주어진 객체(this)의 메소드로 실행할 수 있는 새로운 함수를 반환합니다. +const neoGetInfo = amy.getInfo.bind(neo); +setTimeout(() => { + console.log(neoGetInfo(85, "서울")); +}, 1000); +``` + +3가지 모두 함수가 없는 객체에게 함수를 빌려주는 느낌은 비슷하다. + +## Throttle & Debounce + +Throttle & Debounce같은 경우는 직접 구현하기가 쉽지 않기에 외부 라이브러리를 사용해 볼 것이다. +https://lodash.com/ +CDN copies에 들어가서 + +```html + +``` + +이러한 코드를 복사해주고 html에 붙여넣기 해주자. (main.js위에) + +```js +// Throttle +// - 정해진 시간 간격으로 함수를 실행하도록 제한합니다. +window.addEventListener( + "scroll", + _.throttle(function () { + console.log("Scroll!"); + }, 400), +); + +// Debounce +// - 정해진 시간 동안 함수가 실행되지 않으면, 함수를 실행합니다.(마지막에 한 번만 실행) +async function getMovies(movieName) { + const res = await fetch( + `https://omdbapi.com/?apikey=7035c60c&s=${movieName}`, + ); + return await res.json; +} +const inputEl = document.querySelector("input"); +inputEl.addEventListener( + "input", + _.debounce(function () { + console.log(getMovies(inputEl.value)); + }, 400), +); +``` + +둘 다 함수의 실행 횟수를 제한하는 느낌이다. (최적화!) + +## 불변성과 가변성 + +**불변성** + +- 생성된 데이터가 메모리에서 변경되지 않는 것을 의미합니다. + **가변성** +- 생성된 데이터가 메모리에서 변경될 수 있음을 의미합니다. + +원시형(문자, 숫자, 불린, null, undefined)은 불변성 +참조형(배열, 객체, 함수)는 가변성을 가지고 있습니다. + +```js +const a = 1; +let b = a; +b = 2; +console.log(a); +console.log(b); + +const c = { x: 1, y: 2 }; +const d = c; +d.x = 99; +console.log(c); +console.log(d); + +const e = [1, 2, 3]; +const f = e; +f[0] = 99; +console.log(e); +console.log(f); +``` + +![[Pasted image 20240619201350.png]] +이런식으로 작동이 되는 것을 알고 있으면 나중에 에러를 줄일 수 있다. +(코드를 순서대로 보며 사진처럼 생각해보자) + +이것으로 알 수 있는 것은 참조형같은 경우는 우리가 원치않게 변하게 만드는 일이 생길 수 있기때문에 복사를 해서 사용을 해줘야 한다. + +근데 여기서 복사는 두 가지로 나뉘게 된다. +그래서 다음시간에는 얕은 복사와 깊은 복사에 대해서 공부해 볼 것이다. + +## 얕은 복사 & 깊은 복사 + +**얕은 복사(Shallow copy)** + +- 참조형의 최상위 레벨만 복사하는 것을 말합니다. + **깊은 복사(Deep copy)** +- 참조형의 모든 레벨을 복사하는 것을 말합니다. + +```js +const a = [ + { x: 1, y: [1, 2] }, + { x: 2, y: [3, 4] }, + { x: 3, y: [5, 6] }, +]; +``` + +여기 여러 레벨을 가지고 있는 배열이 있다. + +여기서 얕은 복사는 최상위인 배열만을 복사하는 것이고, 깊은 복사는 저 배열의 모든 레벨을 복사하는 것이다. (배열, 배열안 객체, 배열안 객체안 배열도 복사) + +```js +const a = [ + { x: 1, y: [1, 2] }, + { x: 2, y: [3, 4] }, + { x: 3, y: [5, 6] }, +]; + +const c = { x: 1, y: 2 }; +// const d = c; +// 얕은 복사 2개 +const d = Object.assign({}, c); +// const d = { ...c }; +d.x = 99; +console.log(c); +console.log(d); + +const e = [1, 2, 3]; +// const f = e; +// 얕은 복사 2개 +// const f = e.slice() // 똑같은 얕은 복사가 되지만 의미가 자른다는 의미여서 않좋음 +const f = [...e]; +console.log(e); +console.log(f); + +const g = [ + { x: 1, y: 2 }, + { x: 3, y: 4 }, +]; +// 깊은 복사 +const h = _.cloneDeep(g); +h[0].x = 99; +console.log(g); +console.log(h); +``` + +얕은 복사는 간단하게 할 수 있지만, 깊은 복사같은 경우는 구현하기가 어렵기에 lodash의 함수를 사용해서 깊은 복사를 해주었다. + +깊은 복사는 레벨이 얼마나 존재하는지 모르기에 성능상 불안정적인 부분이 있어서 얕은 복사로 최대한 해결해보고 안된다고 하면 깊은 복사를 사용하는게 맞다. + +즉, 그냥 상황에 맞게 얕은 복사와 깊은 복사를 사용하면 된다. + +## 렉시컬 스코프 + +우리가 화살표 함수에서 this키워드를 사용할 때 렉시컬 스코프에서 정의가 된다고 배웠다. + +그래서 이번시간에 렉시컬 스코프에 대해서 좀 더 자세히 알아볼 것이다. + +**렉시컬 스코프(Lexical Scope)** + +- 정적 스코프(Static Scope)라고도 합니다. +- 함수를 선언한 위치에서 유효하게 접근 가능한 유효 범위를 말합니다. + +```js +const a = { + fnA() { + console.log("fnA", this); + const b = { + fnB() { + console.log("fnB", this); + const c = { + fnC() { + console.log("fnC", this); + console.log("a", a); + console.log("b", b); + console.log("c", c); + console.log("x", x); + }, + }; + return c; + }, + }; + return b; + }, + fnX() { + console.log("fnX", this); + const x = { + fnY() { + console.log("fnY", this); + console.log("a", a); + console.log("b", b); + console.log("x", x); + }, + }; + }, +}; +a.fnA().fnB().fnC(); +a.fnY(); +``` + +이 코드를 보면 렉시컬 스코프에 대해서 이해할 수 있다. + +fnC를 기준으로 설명을 해주게 된다면, fnC는 fnB에 속하고 fnB는 fnA에 속하기에 fnC입장에서 보면 fnA와 fnB의 범위가 유효한 범위이다. 하지만 fnC입장에서 fnX의 포함된 부분이 아니기에 fnC에서 fnX에 접근을 할 수 없다. + +이렇게 fnC의 상위의 범위를 렉시컬 스코프(정적 스코프)라고 부른다. 왜 정적 스코프라고도 부르냐면 fnC를 만드는 단계에서 정적으로 유효 범위가 정해지기 때문이다. + +물론 위 코드는 일반 함수이기에 호출된 위치에서 this키워드가 정의되지만 화살표함수라면 가장 먼저 만난 일반 함수의 this를 사용하게 될 것이다. + +## 클로저 + +클로저(Closure)는 함수가 선언될 때의 렉시컬 스코프를 기억하고 있다가, 함수가 호출될 때 그 스코프에 접근할 수 있는 개념(특성)을 말합니다. + +```js +let count1 = 0; +function c1() { + return (count1 += 1); +} +console.log(c1()); +console.log(c1()); +console.log(c1()); + +let count2 = 77; +function c2() { + return (count2 += 1); +} +console.log(c2()); +console.log(c2()); +console.log(c2()); + +//////////////////////////////////////////////// + +function createCount(count) { + return function () { + return (count += 1); + }; +} +const c3 = createCount(0); +console.log(c3()); +console.log(c3()); +console.log(c3()); +const c4 = createCount(77); +console.log(c4()); +console.log(c4()); +console.log(c4()); +``` + +구분선 부분의 위쪽을 보게 되면 변수와 함수를 같이 만들어야지 함수를 사용해서 변수의 값을 늘릴 수 있다. + +이 부분을 축소하기 위해서 우리는 클로져라는 개념을 사용할 수 있다. +구분선 아래 부분을 보면 c3이 함수를 반환받는데 c3라는 함수는 count라는 변수가 정의되어 있지 않다. 하지만 함수에서 반환을 할 때 count라는 함수를 사용하고 있기 때문에, c3함수가 호출이 될 때, 그 함수가 만들어질 때의 렉시컬 스코프를 가지고 있어서 count라는 변수에 접근할 수 있는 개념(특징)이다. + +**클로저의 사용예시** + +```js +const h1El = document.querySelector("h1"); + +let h1IsRed = false; +h1El.addEventListener("click", () => { + h1IsRed = !h1IsRed; + h1El.style.color = h1IsRed ? "red" : "black"; +}); +``` + +이런식으로 변수와 함수를 분리해서 사용하는 경우가 있는데 이러한 상황이 계속된다면 변수를 계속 만들어야 하는 불편함이 있을 것이다. + +이것을 클로저라는 특성을 활용하여 현재 코드를 더 효율적으로 만들어 관리할 수 있다. + +```js +const h1El = document.querySelector("h1"); + +const createToggleHandler = () => { + let isRed = false; + return (event) => { + isRed = !isRed; + event.target.style.color = isRed ? "red" : "black"; + }; +}; + +h1El.addEventListener("click", createToggleHandler()); +``` + +이런식으로 코드를 작성하게 되면, 재사용도 가능하고 효율성도 좋아지기에 클로저를 사용할 수 있는 상황에서 사용하면 좋다. + +## 가비지 컬렉션과 메모리 누수 + +**가비지 컬렉션** + +- 더 이상 사용되지 않는 메모리를 해제하는 프로세스로 자바스크립트 엔진이 자동으로 처리합니다. + **메모리 누수** +- 더 이상 필요치 않은 데이터가 해제되지 못해 메모리에 계속 차지되는 것을 말합니다. + +가비지 컬렉션을 효율적으로 동작하기 위한 주의해야 할 점을 알아볼 것이다. + +### 메모리 누수가 되는 다양한 상황들 + +**불필요한 데이터 참조를 피하세요!** + +```js +// 불필요한 데이터 참조를 피하세요! +const user = { + name: "Neo", + age: 85, + emails: ["abc@gmail.com", "xyz@naver.com"], +}; +const removedEmail = user.emails.splice(1, 1); +console.log(removedEmail); +console.log(user.emails); +``` + +이런식으로 splice로 잘라서 확인하기 위해 변수 담아둔다면 가비지 컬렉션이 메모리를 순회하면서 저 부분을 찾아도 removedEmail이 참조를 하고 있기에 지울 수가 없어진다. + +그렇기에 확인을 하고나면 변수를 지워줘야 한다. + +**불필요한 전역 변수 사용을 피하세요!** + +```js +// 불필요한 전역 변수 사용을 피하세요! +window.hello = "Hello world!"; +window.thw = { name: "200won", age: 85 }; +``` + +우리가 어디에서나 접근할 수 있는 객체를 전역 객체라고 부른다. +window도 전역 객체이다. 이렇게 전역 객체에서 어떤 속성에 데이터를 할당하게 되면 우리가 직접 제거를 하지 않는 이상 데이터를 제거하는 상황을 만들기가 어렵다. 그렇기에 전체영역에서 사용할 수 있는 변수에게 데이터를 만드는 행위를 주의해야 한다. + +**제거된 요소가 참조되지 않도록 주의하세요!** + +```js +const h1El = document.querySelector("h1"); +window.addEventListener("click", () => { + console.log(h1El); + h1El.remove(); +}); +``` + +이렇게 되면 우리는 querySelector를 활용해서 h1El에 할당한 것이기에 화면상에서는 제거 되었지만, 저 변수가 사라지지 않는다면 메모리상에 계속 존재해 사용할 수 있게 된다. + +```js +window.addEventListener("click", () => { + const h1El = document.querySelector("h1"); + if (h1El) { + console.log(h1El); + h1El.remove(); + } +}); +``` + +이런식으로 코드를 작성하게 되면, 완전히 가비지 컬렉션을 사용해서 제거할 수 있다. + +**불필요한 타이머를 해제하세요!** + +```js +// 불필요한 타이머를 해제하세요! +let a = 0; +setInterval(() => { + a += 1; +}, 100); +setTimeout(() => { + console.log(a); // 10 +}, 1000); +``` + +이 부분의 코드는 메모리가 계속 낭비되고 있는 중이다. 왜냐하면 setInterval을 사용하면 setTimeout으로 값을 확인하고 나서도 계속 값이 늘어나기 때문이다. + +그러기에 타이머를 해제하는 코드가 필요해진다. + +```js +// 불필요한 타이머를 해제하세요! +let a = 0; +const intervalId = setInterval(() => { + a += 1; +}, 100); +setTimeout(() => { + console.log(a); // 10 + clearInterval(intervalId); +}, 1000); +``` + +이런식으로 clearInterval이라는 함수를 사용해서 interval을 멈춰줄 수 있다. + +**불필요한 클로저를 제거하세요!** + +```js +// 불필요한 클로저를 제거하세요! +const getFn = (x) => { + return (name) => { + x += 1; + console.log(x); + return `Hello ${name}~`; + }; +}; +const fn = getFn(0); +console.log(fn("Neo")); +console.log(fn("Lewis")); +fn("Evan"); +fn("Amy"); +``` + +getFn을 호출하여 fn이 함수를 리턴받게 되었는데 그 함수에서 x가 사용되기에 렉시컬 스코프에서 x를 가져와서 사용하게 된다. 하지만 x는 출력하는 행위 외에는 아무 행동도 하고 있다. 이렇게 클로저가 발생되어 의미없는 변수를 참조해서 메모리가 사용되는 현상을 피해야한다. + +--- + +지금까지 우리가 본 예시들 전부 불필요한 것들을 사용하지 않게 만드는 것이였다. +그래서 우리가 코드를 작성할 떄는 꼭 필요한 내용만 넣어줘야 한다. 개발이 끝나게 되면 console.log같은 부분은 다 지워줘서 메모리를 불필요하게 차지하는 데이터를 사용하지 않게 만들 수 있다. + +## 콜 스택과 이벤트 루프 + +자바스크립트는 저수준의 오래 걸릴 수 있는 일(Timer, Network 등)은 Web API에게 위임하고, 고수준의 작업은 자바스크립트 엔진(싱글 스레드)에서 처리하는 방식으로 빠른 속도와 확장성을 유지합니다. + +```js +setTimeout(() => { + console.log(1); +}, 0); +window.addEventListener("load", () => { + console.log(2); +}); +fetch("/").then(() => console.log(3)); +for (let i = 0; i < 1000; i++) { + console.log(4); +} +``` + +그래서 이런 코드를 실행하였을 때, 작성한 순서대로 실행되는 것이 아니라 다른게 실행 될 수도 있는 현상이 발생한다. + +그럼 이제 콜 스택과 이벤트 루프에 대해서 알아보기 전에 두 가지 용어만 정리하고 가보자 + +**FIFO(FIrst In First Out)** + +- 선입선출, 먼저 들어온 데이터가 먼저 나감 + **LIFO(Last In First Out)** +- 후입선출, 마지막에 들어온 데이터가 먼저 나감 + +![[Pasted image 20240620165903.png]] +자바스크립트 엔진입장에서는 Heap이라는 영역으로 메모리가 관리가 되고, Call Stack 즉 호출된 함수들의 내역이 쌓이는 곳이다. + +이 부분의 JS Runtime이 자바스크립트 엔진이 동작하는 부분이다. + +그리고 자바스크립트는 저수준의 오래걸리는 일들은 엔진에서 하지않고 브라우저에서 동작한다고 하였는데 +![[Pasted image 20240620170202.png]] +바로 그것이 Web API라는 것이다. 이렇게 오래걸릴 수 있는 일들이 끝나게 되면 콜 스택으로 가는 것이 아니라. Queue라는 영역으로 가서 하나씩 쌓이게 된다. +![[Pasted image 20240620170324.png]] +Queue영역에서는 Call Stack의 함수가 전부 호출되서 비워지면 Event Loop라는 것을 통해서 Queue에 있는 순서대로 Call Stack으로 올라가 처리가 될 수 있다. + +```js +function a() { + console.log("A"); + function b() { + setTimeout(() => { + console.log("B1"); + console.log("B2"); + }, 0); + } + b(); +} +function c() { + console.log("C"); +} +function first() { + a(); + c(); +} +function second() { + c(); +} +first(); +second(); +``` + +코드와 위 사진을 보며 어떤식으로 동작되는지 생각해보자!