lecture1

// 변수, 호이스팅, TDZ

// var는 한번 선언된 변수를 다시 선언 가능
var name = 'Mike';
console.log(name); // Mike

var name = 'Jane'
console.log(name); // Jane

// 같은 상황에서 let은 에러
let name = 'Mike';
console.log(name); // Mike

let name = 'Jane' // error!
console.log(name); 

// var는 선언하기 전에 사용 가능
console.log(name); // undefined
var name = 'Mike';

// 왜? var는 이렇게 동작하기 때문 
/* var로 선언한 모든 변수는 코드가 실제로 이동하지는 않지만
최상위로 끌어올려진 것처럼 동작 = 호이스팅
console이 undefined를 찍는 이유는 선언은 hoisting 되지만
할당은 호이스팅되지 않기 때문 */
var name;
console.log(name); // undefined
name = 'Mike'; // 할당

// 같은 상황에서 let은 에러
/* let과 const는 호이스팅되지 않는 걸까? NO
호이스팅: 스코프 내부 어디서든 변수 선언은 최상위에 선언된 것처럼 행동
근데 왜 에러를 내는 걸까? -> Temporal Dead Zone (TDZ)때문
TDZ 영역에 있는 변수들은 사용할 수 없음
let과 const는 TDZ의 영향을 받음
할당을 하기 전에는 사용할 수가 없다.
-> 코드를 예측가능하게 하고 잠재적 버그를 줄임
*/
console.log(name); // ReferenceError
var name = 'Mike';

console.log(name) // Temporal Dead Zone
const anme = 'Mike' // 변수 선언 및 할당
console.log(name) // 사용 가능

// 문제가 발생하지 않는 코드
let age = 30;
function showAge() {
    console.log(age);
}
showAge();

// 문제 발생 코드
let age = 30;
function showAge() {
    console.log(age); // TDZ
    let age = 20; // 호이스팅 발생
}
showAge();

/* 변수의 생성과정
1. 선언 단계
2. 초기화 단계
3. 할당 단계

var 
1. 선언 및 초기화 단계
2. 할당 단계
초기화: undefined를 할당해주는 단계

let
1. 선언 단계
2. 초기화 단계
3. 할당 단계
호이스팅 되며 선언 단계가 이루어지지만 
초기화 단계는 실제 코드에 도달했을 때 되기 때문에
레퍼런스 에러 발생

const
1. 선언 + 초기화 + 할당

let과 var는 선언만 해두고 나중에 할당허용
let var는 값을 바꿀 수 있기 때문에 어찌보면 당연
*/

// const
let name;
name = 'Mike';

var age;
age = 30;

const gender; // error! 선언하며 바로 할당안함
gender = 'male'; 

/* 
var는 함수 스코프
함수 내에서 선언된 변수만 그 지역변수

let, const는 블록 스코프(함수, if, for, while, try-catch 등)
블록 스코프는 모든 코드 블럭 내에서 선언된 변수는 
코드 블록 내에서만 유효
외부에서는 접근 불가능
즉 코드 블록 내부에서 선언한 변수는 지역 변수
*/

// if문 안에서 var로 선언한 변수는 if문 밖에서도 사용 가능
// let과 const는 중괄호 안에서만 사용 가능 (블록 스코프)
const age = 30;
if (age > 19) {
    var txt = '성인';
}
console.log(txt); // '성인'

// var도 함수 내에서 선언되면 함수 밖에서 사용할 수 없음
// 유일하게 벗어날 수 없는 스코프가 함수
function add(num1, num2) {
    var result = num1 + num2;
}
add(2, 3);
console.log(result);


lecture2

// 생성자 함수

// 객체 리터럴
// 개발을 하다보면 이런 비슷한 변수들을 여러개 만들어야 하는 경우가 발생 ex. 회원, 상품 -> 생성자 함수
let user = {
    name : 'Mike',
    age : 30,
}

// 생성자 함수 첫 글자는 보통 대문자로
// new 연산자를 사용하여 함수 호출
// 생성자 함수는 붕어빵 틀, 와플 틀
// 지금 필요한 재료는 이름과 나이
// 생성되는 객체들은 와플
function User(name, age) {
    this.name = name;
    this.age = age;
}

let user1 = new User('Mike', 30);
let user2 = new User('Jane', 22);
let user3 = new User('Tom', 17);

// new를 붙여 실행하는 순간 이 방식대로 알고리즘이 동작
// 이렇게 객체를 만들면 일일이 객체를 만드는 것보다 훨씬
// 빠르고 일관성있게 객체를 만들 수 있다. 
// 어떤 함수라도 new를 붙여서 실행하면 이 알고리즘이 동작
function User(name, age) {
    this = {} // 실제로 없는 코드 : 빈 객체를 만들어 this에 할당

    this.name = name; // this에 프로퍼티들 추가
    this.age = age;

    return this; // 실제로 없는 코드 : this 반환
}
new 함수명();

// user5.sayName() 했을 때 sayName의 this는 user5
function User(name, age) {
    this.name = name;
    this.age = age;
    this.sayName = function() { // this로 할당된 객체에 sayName 메소드 추가
        console.log(this.name); // user5.sayName을 호출 했을 때 sayName 함수의 this는 user5
    }
}
let user5 = new User('Han', 40);
user5.sayName(); // 'Han'

// 실습
// 생성자 함수: 상품 객체를 생성해보자.
function Item(title, price) {
    // this = {};
    this.title = title;
    this.price = price;
    this.showPrice = function() {
        console.log(`가격은 ${price}원 입니다.`);
    }
    // return this;
}

// 만일 new를 안붙인다면 undefined
// new를 안붙이면 그냥 함수를 실행해주는 것
// 리턴해주는 게 없기 때문에 underined 반환하고 item2로 들어감
const item1 = new Item('인형', 3000);
const item2 = Item('가방', 2000);
const item3 = new Item('지갑', 5000);

console.log(item1, item2, item3);

item3.showPrice();

lecture3

// 객체 메소드, 계산된 프로퍼티
// Object methods, Computed property

// 컴퓨티트 프로퍼티
// 대괄호로 묶어주면 a라는 문자열이 아니라 변수a에 할당된 값이 들어감
// 식 자체를 넣는 것도 가능
let a = 'age';
const user = {
    name: 'Mike',
    [a]: 30,
    [1 + 4] : 5,
    ['안녕' + '하세요'] : 'hello'
}

// 객체에서 사용할 수 있는 몇 가지 메소드
/*
Object.assign()
Object.keys()
Object.values()
Object.entries()
Object.fromEntries()
*/

// Object.assign() : 객체 복제
const user = {
    name : 'Mike',
    age : 30
}
// 이렇게 하면 복제되는 걸까? NO
const cloneUser = user;

//user에는 객체 자체가 들어있는 게 아니라 객체가 저장되어 있는 메모리 주소인 객체에 대한 참조값이 저장되므로
// clonUser를 만들어서 user를 넣으면 객체가 복사되며 들어가는게 아니라 참조값만 복사

// cloneUser의 이름을 바꾸면 user의 이름도 바뀜
// 하나의 객체를 두 변수가 접근하고 있는 것
cloneUser.name = 'Tom'; 

// 따라서 객체를 복제해주기 위해선 Object.assign() 사용
// 빈 객체는 초기값, 두번째 매개변수부터 들어온 값이 초기값에 병합됨
// {} + {name:'Mike', age:30} = {name:'Mike', age:30}
const newUser = Object.assign({}, user);

//이름을 바꿔도 user에는 변함 x
newUser.name = 'Tom';
console.log(user.name); // 'Mike'
newUser != user

// {gender:'Male', name:'Mike', age:30} : 총 3개의 프로퍼티를 가지게 됨
const newUser = Object.assign({gender:'Male'}, user);

// 병합을 하는데 키가 갖다면? 덮어쓰게 됨
// {name:'Mike', age:30}
const newUser = Object.assign({name:'Tom'}, user);

// 두개 이상의 객체도 합칠 수 있음
const user = { name:'Mike'}
const info1 = { age:30}
const info2 = { gender:'Male'}

const newUser = Object.assign({}, info1, info2);

const user = {
    name : 'Mike',
    age : 30,
    gender : 'male',
}

// Object.keys() : 키 배열 반환
Object.keys(user);
["name", "age", "gender"]

// Object.values() : 값 배열 반환
Object.values(user);
["Mike", 30, "male"]

// Object.entries() : 키/값 배열 반환
Object.values(user);
[
    ["name", "Mike"]
    ["age", "30"]
    ["gender", "male"]
]

// Object.fromEntries() : 키/값 배열을 객체로
const arr = 
[
    ["name", "Mike"]
    ["age", "30"]
    ["gender", "male"]
];
Object.fromEntries(arr);
{
    name : 'Mike',
    age : 30,
    gender : 'male',
}

// 실습
let n = "name";
let a = "age";

const user = {
    [n] : 'Mike',
    [a] : 30,
    [1 + 4] : 5,

};

console.log(user);

function makeObj(key, val) {
    return {
        [key] : val
    }
}
const obj = makeObj('나이', 33);

console.log(obj);

lecture4

// 심볼 (Symbol)

// property key : 문자형
const obj = {
    1: '1입니다.',
    false : '거짓'
}
// 숫자형이나 불리언으로 만들어도 프로퍼티 키들을 가져와보면 문자형으로 반환
Object.keys(obj); // ["1", "false"]
// 접근할 때도 obj['1']이나 obj['false']로 접근할 수 있음

// Symbol : 유일한 식별자를 만들 때 사용
const a = Symbol(); // new를 붙이지 않음
const b = Symbol();

// a===b, a==b 둘 다 false
// Symbol형은 유일성 보장

// 심볼을 만들 때 설명을 붙여 줄 수도 있음
// 설명을 붙이면 디버깅할 때 편함
// 문자열을 전달해주면 되는데 이 문자열은 심볼 생성에 어떤 영향도 끼치지 않음
const id = Symbol('id');
const id2 = Symbol('id');
// 설명을 똑같이 해도 id===id2, id==id2 false

// 심볼을 객체의 키로 사용해보자
const id = Symbol('id');
const user = {
    name : 'Mike',
    age : 30,
    [id] : 'myid'
}
console.log(user);
// {name:'Mike', age:30, Symbol(id):"myid"}
console.log(user[id]); // "myid"

// 이런 메소드들은 키가 심볼형인 프로퍼티는 건너뜀
// for in을 써도 건너 뜀
Object.keys(user); // ["name", "age"]
Object.values(user);  // ["Mike", "30"]
Object.entries(user);  // [Array(2), Array(2)]

// 그럼 이 꽁꽁 숨겨진 심볼을 어디에 쓸까?
// 특정 객체의 원본 데이터는 건들이지 않고 속성 추가할 수 있음
// 다른 사람이 만든 객체에 자신만의 속성을 추가해서 덮어쓰면 안됨 
// 그렇다고 길고 이상한 네이밍을 하는 것도 좋지 않음
// 만약 그 원본 객체가 어딘가에서 Object.keys나 for in으로 순회하면서 데이터를 사용할 수도 있음
// 내가 추가한 프로퍼티가 어디서 어떻게 튀어나올지 예측 불가능

// 이런식으로 심볼은 이름이 같더라도 모두 다른 존재
// 그런데 가끔 전역변수처럼 이름이 같으면 같은 객체를 가르켜야할 때가 있는데 이럴 때 사용하는게
/* Symbol.for() : 전역 심볼
하나의 심볼만 보장받을 수 있음
없으면 만들고, 있으면 가져오기 때문
Symbol을 함수는 매번 다른 Symbol을 값을 생성하지만
Symbol을.for 메소드는 하나를 생성한 뒤 키를 통해 같은 Symbol을 공유 
코드 어디에서든 사용 가능*/

const id1 = Symbol.for('id');
const id2 = Symbol.for('id');
id1 === id2; // true

// 이름을 얻고 싶다면
// keyFor에 변수를 넣어주면 생성할 때 적어준 이름을 알려줌
Symbol.keyFor(id1) // "id"

// 전역 심볼이 아닌 경우는 keyFor사용불가능
// 대신 description 사용
const id = Symbol('id입니다.');
id.description; // "id입니다."

// 사실 심볼을 완전히 숨길 수 있는 방법은 없음
// 숨겨진 심볼 키 보는 법
const id = Symbol('id');
const user = {
    name : 'Mike',
    age : 30,
    [id] : 'myid'
}
// 심볼들만 볼 수 있음
Object.getOwnPropertySymbols(user); // [Symbol(id)]
// 심볼형 키를 포함한 모든 키를 보여줌
Reflect.ownKeys(user); // ["name", "age", Symbol(id)]
// 사실 대부분의 라이브러리, 내장함수는 이런 메소드 사용안하니 유일한 프로퍼티를 추가하고 싶을 때 심볼을 사용하자

// 실습
// 다른 개발자가 만들어 놓은 객체
const user = {
    name : 'Mike',
    age : 30,
};

// 내가 작업
user.showName = function() {}; 
// His showName is function() {} 말도 안되는 메세지 출력
const showName = Symbol('show name');
user[showName] = function () {
    console.log(this.name);
}
// Symbol을 만들었기 때문에 for in문에서 걸리지 않음
// name, age만 출력

user[showName]();
// 이렇게 하면 Mike도 잘 나옴
// 내가 추가한 메소드도 잘 동작하고 다른 개발자가 만들어 둔 코드에도 영향을 미치지 않는 선에서 메소드 추가
// 원래 user에 showName이라는 메소드가 있었는지 고민할 필요도 없고 다른 사람이 만들어 둔 프로퍼티를 덮어쓸 일도 없음
// 이게 바로 심볼의 장점!

// 사용자가 접속하면 보는 메세지 
for (let key in user) {
    console.log(`His ${key} is ${user[key]}.`);
}
// His name is Mike
// His age is 30

lecture5

// Number, Math

// 10진수 -> 2진수, 16진수로 바꾸는 법
// toString
let num = 10;
num.toString(); // "10"
num.toString(2); // "1010"

let num2 = 255;
num2.toString(16); // "ff"

// Math
Math.PI // 원주율
Math.ceil(n) // 올림
Math.floor(n) // 버림
Math.round(n) // 반올림

// 소수점 자릿수
let userRate = 30.1234;
// 소수점 둘째자리까지 표현(셋째 자리에서 반올림)
Math.round(userRate * 100) / 100 // 30.12

// toFixed() : 문자열 반환 주의
userRate.toFixed(2) // "30.12"
userRate.toFixed(0) // "30"
userRate.toFixed(6)// "30.123400"

Number(userRate.toFixed(2)) // 30.12

// isNaN() : NaN인지 아닌지 판단
let x = Number('x'); // Nan

// NaN은 자기자신과도 같지 않다고 판단
x == NaN // false
x === NaN // false
NaN == NaN // false

// isNaN()만이 NaN인지 아닌지 판단 가능
isNaN(x) // true
isNaN(3) // false

// parseInt() : 문자열을 숫자로 바꿔줌
let margin = '10px';

// Number와 다른 점은 문자가 혼용되어 있어도 동작
// 읽을 수 있는 부분까지 읽고 숫자로 반환
parseInt(margin); // 10
Number(margin); // NaN

// 그렇기 때문에 숫자로 시작하지 않으면 NaN반환
let redColor = 'f3';
parseInt(redColor); // NaN

// parseInt는 두번째 인수를 받아 진수 지정 가능
let redColor = 'f3';
parseInt(redColor, 16); // 243

parseInt('11', 2) // 3

// parseFloat()은 parseInt와 동일하게 동작하지만 부동소수점 반환
// parseInt는 소수점이하는 무시하고 정수만 반환
let padding = '18.5%';
parseInt(padding); // 18
parseFloat(padding); // 18.5

// Math.random() 0~1사이의 무작위 숫자 생성
// 1~100 사이 임의의 숫자를 뽑고 싶을 때
Math.floor(Math.random() * 100) + 1

// Math.max(), Math.min()
Math.max(1, 4, -1, 5, 10, 9, 5.54); // 10
Math.max(1, 4, -1, 5, 10, 9, 5.54); // -1

// Math.abs() : 절대값
Math.abs(-1) // 1

// Math.pow(n, m) : 제곱
Math.pow(2, 10); // 1024

// Math.sqrt() : 제곱근
Math.sqrt(16) // 4

lecture6

// String 문자열

// html코드 같은 경우 작은 따옴표로 감싸는게 편하고
// 영어는 큰 따옴표로 감싸는게 편함
// 백틱은 ${}를 이용해 변수와 표현식 표현 가능

// 백틱은 여러줄 포함 가능
// 따옴표로 여러줄 쓰려면 \n을 쓰고 한줄에 써야함

// str.length : 문자열 길이
let desc = '안녕하세요.';
desc.length // 6

// 특정 위치에 접근 가능
// 배열과 동일하게 0부터 시작하나 바꿀 수는 없음
desc[2] // '하'

// toUpperCase(), toLowerCase()
let desc = "Hi"
desc.toUpperCase() // "HI"
desc.toLowerCase() // "hi"

// str.indexOf(text) 
// 0부터 세고 찾는 문자열이 없으면 -1 반환
// 포함된 문자가 여러개라면 처음 문자만 반환
let desc = "Hi guys. Nice to meet you."
desc.indexOf('to'); // 14

// 주의 
// Hi가 맨 처음에 있어 0을 반환하므로 조건문이 거짓이 됨
if (desc.indexOf('Hi')) {
    console.log('Hi가 포함된 문장입니다.');
}
// 따라서 -1보다 큰가로 비교하면 됨
if (desc.indexOf('Hi') > -1) {
    console.log('Hi가 포함된 문장입니다.');
}

// str.slice(n, m) n은 시작점
// m은 없으면 문자열 끝까지, 양수면 그 숫자까지(포함x), 음수면 끝에서부터 셈
let desc = "abcdefg";
desc.slice(2); // "cdefg"
desc.slice(0, 5); // "abcde"
desc.slice(2, -2); // "cde"

// str.substring(n, m) : n과 m사이의 문자열 반환
// 슬라이스와 유사하지만 m과 n을 바꿔도 동작
// n부터 m까지보다는 n부터 m사이라고 생각
// 음수는 허용하지 않음 0으로 인식
let desc = "abcdefg";
desc.substring(2, 5); // "cde"
desc.substring(5, 2); // "cde"

// str.substr(n, m) 
// n부터 시작해서 m개를 가져옴
let desc = "abcdefg";
desc.substr(2, 4); // "cdef"
desc.substr(-4, 2); // "de"

// str.trim(n, m) : 앞 뒤 공백 제거
// n부터 시작해서 m개를 가져옴
// 사용자로부터 입력받을 때 자주 사용
let desc = " coding     ";
desc.trim(); // "coding"

// str.repeat(n) : 문자열 n번 반복
let desc = "hello!";
desc.repeat(3); // "hello!hello!hello!"

// 문자열 비교
"a" < "c" // true
"a".codePointAt(0); // 97
String.fromCodePoint(97) // "a"

// 있는지 없는지만 알고 싶을 때 str.includes()
// includes는 문자가 있으면 true
// 없으면 false 반환
str.includes("콜라")

lecture7

// 배열 메소드

/*
push() : 뒤에 삽입
pop() : 뒤에 삭제
unshift() : 앞에 삽입
shift() : 앞에 삭제
*/

// arr.splice(n, m) : 특정 요소 지움
// n번째 요소부터 m개 지워라
let arr = [1, 2, 3, 4, 5];
arr.splice(1, 2);
console.log(arr); // [1, 4, 5]

// arr.splice(n, m, x) : 특정 요소 지우고 추가
let arr = [1, 2, 3, 4, 5];
arr.splice(1, 3, 100, 200); // 2, 3, 4가 지워지고 100과 200이 들어감
console.log(arr); // [1, 100, 200, 5]

// 두번째 인수에 0을 넣으면?
// 아무것도 지우지 않고 중간에 새로운 요소 추가
let arr = ["나는", "철수", "입니다"];
arr.splice(1, 0, "대한민국", "소방관");
// ["나는", "대한민국", "소방관","철수", "입니다"];

// arr.splice() : 삭제된 요소 반환
let arr = [1, 2, 3, 4, 5];
let result = arr.splice(1, 2);
console.log(arr); // [2, 3]

// arr.slice(n, m) : n부터 m까지 반환
let arr = [1, 2, 3, 4, 5];
arr.slice(1, 4); // [2, 3, 4]

// 괄호 안에 아무것도 안넣으면 배열 복사
let arr2 = arr.slice();
console.log(arr2); // [1, 2, 3, 4, 5]

// arr.concat(arr2, arr3 .. ): 합쳐서 새 배열 반환
let arr = [1, 2];
arr.concat([3, 4]); // [1, 2, 3, 4]
arr.concat([3, 4], [5, 6]); // [1, 2, 3, 4, 5, 6]
arr.concat([3, 4], 5, 6); // [1, 2, 3, 4, 5, 6]

// arr.forEach(fn) : 배열 반복
// 함수를 인수로 받음. 함수에는 3가지 매개변수
// 해당 요소, 인덱스, 배열 자체
// 보통 첫 번째와 두 번째만 사용
let users = ['Mike', 'Tom', 'Jane'];
users.forEach((item, index, arr) => {
    // item: Mike, Tom, Jane
    // index: 0, 1, 2
    // arr: users
});
users.forEach((item, index, arr) => {
    console.log(`${index + 1}. ${name}`);
});

// arr.indexOf / arr.lastIndexOf
// 발견하면 해당 요소 인덱스, 없으면 -1 반환
let arr = [1, 2, 3, 4, 5, 1, 2, 3];
arr.indexOf(3); // 2
// 두 번째 인수는 시작 위치 4부터 시작해서 3탐색
arr.indexOf(3, 3) // 7
// 끝에서부터 탐색하고 싶을 때
arr.lastIndexOf(3); // 7

// arr.includes() : 포함하는지 확인
// 굳이 인덱스를 확인할 필요는 없고 포함하는지만 확인하고 싶을 때
let arr = [1, 2, 3];
arr.includes(2); // true
arr.includes(8); // false

// arr.find(fn), arr.findIndex(fn)
// indexOf처럼 찾는다는 의미는 동일하지만 보다 복잡한 연산이 가능하도록 함수를 연결할 수 있음. 짝수, 성인 찾기 등
// 첫 번째 true값만 반환하고 끝남
// find는 없으면 undefined를 반환
// findIndex는 없으면 -1을 반환
let arr = [1, 2, 3, 4, 5];
const result = arr.find((item) => {
    return item % 2 === 0;
}); // 리턴 값이 트루일 때 멈추고 해당 요소를 알려줌
console.log(result); // 2

let userList = [
    { name: "Mike", age: 30 },
    { name: "Jane", age: 27 },
    { name: "Tom", age: 10 }
];
const result = userList.find((user) => {
    if (user.age < 19) {
        return true;
    }
    return false;
});
console.log(result); // { name: "Tom", age: 10 }

const result = userList.findIndex((user) => {
    if (user.age < 19) {
        return true;
    }
    return false;
});
console.log(result); // 2

// arr.filter(fn) : 만족하는 모든 요소를 배열로 반환
// find와 달리 조건을 만족하는 모든 요소를 알고 싶을 때
let arr = [1, 2, 3, 4, 5];
const result = arr.filter((item) => {
    return item % 2 === 0;
}); 
console.log(result); // [2, 4, 6]

// arr.reverse() : 배열을 역순으로 재정렬
let arr = [1, 2, 3, 4, 5];
arr.reverse(); // [5, 4, 3, 2, 1]

// arr.map(fn) : 함수를 받아 특정 기능을 시행하고 새로운 배열을 반환
let userList = [
    { name: "Mike", age: 30 },
    { name: "Jane", age: 27 },
    { name: "Tom", age: 10 }
];
let newUserList = userList.map((user, index) => {
    return Object.assign({}, user, {
        id: index + 1,
        isAdult: user.age > 19,
    });
});
console.log(newUserList);
// usreList는 변경된 게 없음
console.log(userList);

// join
// 배열을 합쳐서 문자열을 만들려면 join
let arr = ["안녕", "나는", "철수야"];
// 인수로 전달되는 게 구분자
// 아무것도 전달하지 않으면 쉼표로 구분되어 합쳐짐
let result = arr.join(); // 안녕,나는,철수야
let result = arr.join(" "); // 안녕 나는 철수야
console.log(result);

// split
// 문자열을 나눠서 배열로 만들어줌
let users = "Mike,Jane,Tom,Tony"
let result = users.split(",") // ["Mike", "Jane", "Tom", "Tony"]
console.log(result);

let str = "Hello, My name is Mike."
let result = str.split("") // ["H", "e", "l", "l", "o", " ", ... "."]
console.log(str);

// Array.isArray()
// 배열인지 아닌지 확인
// 자바스크립트에서 배열은 객체에 속함
let user = {
    name: "Mike",
    age: 30,
}
let userList = ["Mike", "Jane", "Tom"];
console.log(typeof user); // object
console.log(typeof userList); // object

console.log(Array.isArray(user)); // false
console.log(Array.isArray(userList)); // true

lecture8

// 배열 메소드2

// arr.sort()
// 배열 재정렬, 배열 자체가 변경되니 주의
let arr = [1, 5, 4, 2, 3];
arr.sort(); // [1, 2, 3, 4, 5]

let arr = ["a", "c", "d", "e", "b"];
arr.sort(); // ["a", "b", "c", "d", "e"]

// 정렬할 때 요소를 문자열로 취급
// 1과 2로 시작하는 애들이 맨 앞에 온 것
let arr = [27, 8, 5, 13];
arr.sort(); // [13, 27, 5, 8]

// 제대로 된 정렬을 하기 위해 값을 비교해줄 수 있는 함수를 전달해야 함
// sort는 함수를 인자로 받음
let arr = [27, 8, 5, 13];

function fn(a, b) {
    return a - b;
}

arr.sort(fn);

arr.sort((a, b) => {
    return a - b; // 양수, 0, 음수 판단
}); 
// 작은 애를 앞으로 보냄
// 8과 27비교 8이 작으니 앞으로: 8, 27, 5, 13
// 5와 8비교 5가 작으니 앞으로: 5, 8, 27, 13
// 13과 5비교 13 - 5 -> 양수 변화
// 13과 8비교 변화x
// 13과 27비교 13이 앞으로: 5, 8 ,13, 27

// 복잡하므로 Lodash같은 라이브러리 사용
// _.sortBy(arr)로 정렬(숫자든 문자든 객체든) 가능

//arr.reduce() 인수로 함수를 받음
// (누적 계산값, 현재값) => { return 계산값 };

// 배열의 모든 수 합치기
let arr = [1, 2, 3, 4, 5];
let result = 0;
arr.forEach(num => {
    result += num;
})
console.log(result);
// 이 작업을 한번에 해주는 게 reduce

const result = arr.reduce((prev, cur) => {
    return prev + cur;
}, 0) // 초기값은 0 (옵션) 안쓰면 첫 번재 요소가 들어감
console.log(result);

// 실용적인 예제
// 성인만 출력
let userList = [
    { name: "Mike", age: 30 },
    { name: "Jane", age: 10 },
    { name: "Tom", age: 27 },
    { name: "Sue", age: 42 },
    { name: "Harry", age: 60 },
];

let result = userList.reduce((prev, cur) => {
    if (cur.age > 19) {
        prev.push(cur.name);
    }
    return prev
}, [])

// arr.reduceRight() : reduce와 동일한 기능 배열 우측부터 연산 수행

lecture9

// 구조 분해 할당
// Destructing assignment
// 구조 분해 할당 구문은 배열이나 객체의 속성을 분해해서 그 값을 변수에 담을 수 있게 하는 표현식

// 배열 구조 분해
let [x, y] = [1, 2];
console.log(x); // 1
console.log(y); // 2

let users = ['Mike', 'Tom', 'Jane'];
let [user1, user2, user3] = users;
// let user1 = users[0];
// let user2 = users[1];
// let user3 = users[2];

let str = "Mike-Tom-Jane";
let [user1, user2, user3] = str.split('-');
// ['Mike', 'Tom', 'Jane']

//  배열 구조 분해:기본값
// c에는 undefined가 들어감
let [a, b, c] = [1, 2];

// 만약 값이 undefined면 기본값 사용
let [a=3, b=4, c=5] = [1, 2]; // 1, 2, 5
console.log(a); // 1
console.log(b); // 2
console.log(c); // 5

// 배열 구조 분해: 일부 반환값 무시
// 공백과 쉼표를 이용해 필요하지 않은 요소 무시
let [user1, , user2] = ['Mike', 'Tom', 'Jane', 'Tony'];

console.log(user1); // 'Mike'
console.log(user2); // 'Tony'

// 배열 구조 분해: 바꿔치기
[a, b] = [b, a];

// 객체 구조 분해
let user = { name: "Mike", age: 30 };
let {name, age} = user; 
// 아래 코드와 같음
let name = user.name;
let age = user.age;
// 배열 구조 분해와 다른 점: 순서를 신경쓰지 않아도 됨
let {age, name} = user; 

// 객체 구조 분해: 새로운 변수 이름으로 할당
let user = { name: "Mike", age: 30 };
let {name: userName, age: userAge} = user;

// 객체 구조 분해: 기본값
let user = { name: "Mike", age: 30 };
// gender은 undefined
let {name, age, gender} = user;
// user 객체에 gender가 없으면 male이 기본적으로 할당
let {name, age, gender = 'male'} = user;

let user = { name: "Mike", age: 30, gender: 'female' };
let {name, age, gender = 'male'} = user;
// user에 gender가 있다면 그 값이 사용
// 객체로부터 받은 값이 undefined일 때만 기본 값 사용
console.log(gender); // 'male'

lecture10

// 나머지 매개변수, 전개 구문

// 인수 전달
// 자바스크립트에서 함수에 넘겨주는 인수 개수에 제한 없음
function showName(name) {
    console.log(name);
}
// 이름을 하나 더 전달한다면? 에러 발생x 'Mike'
showName('Mike', 'Tom'); 

// 에러 발생x name에 undefined가 찍힐 뿐
showName(); 

// arguments
/* 함수로 넘어 온 모든 인수에 접근
함수 내에서 이용 가능한 지역 변수
length, index
Array 형태의 객체
배열의 내장 메소드 없음(forEach, map) */
function showName(name) {
    console.log(arguments.length); // arguments로 접근 가능
    console.log(arguments[0]);
    console.log(arguments[1]);
}
showName('Mike', 'Tom');
// 2
// 'Mike'
// 'Tom'


// 나머지 매개변수
// 정해지지 않은 개수의 인수를 배열로 나타낼 수 있게 함
// ...을 찍고 뒤에 배열 이름을 정해줌
// 배열에 전달된 인수들이 들어감
// 아무것도 전달하지 않으면 빈 배열이 나타남
function showName(...names) {
    console.log(names);
}
showName(); // []
showName('Mike'); // ['Mike']
showName('Mike', 'Tom'); // ['Mike', 'Tom']

// ex 매번 전달하는 숫자의 개수가 다르다고 할 때 모든 숫자의 합을 구하려면?
function add(...nums){
    let result = 0;
    nums.forEach((num) => (result += num));
    console.log(result);
}
add(1, 2, 3);
add(1, 2, 3, 4, 5, 6);

// 리듀스 사용
function add(...nums){
    let result = 0;
    nums.reduce((prev, cur) => prev + cur);
    console.log(result);
}
add(1, 2, 3);
add(1, 2, 3, 4, 5, 6);

// ex user 객체를 만들어 주는 생성자 함수 만들기
// 나머지 매개변수는 항상 마지막에 있어야 함
function User(name, age, ...skills){
    this.name = name;
    this.age = age;
    this.skills = skills;
}
const user1 = new User('Mike', 30, 'html', 'css');
const user2 = new User('Tom', 20, 'JS', 'React');
const user1 = new User('Jane', 10, 'English');


// 전개 구문: 배열
let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];

let result = [...arr1, ...arr2]; //arr1은 1,2,3, arr2는 4,5,6
console.log(result); // [1, 2, 3, 4, 5, 6]

let result = [0, ...arr1, ...arr2, 7, 8, 9]; 
// [0,1,2,3,4,5,6,7,8,9]
// arr.push(), arr.splice(), arr.concat().. 안해도 됨

// 전개 구문: 객체
// Object.assign을 쓸 필요가 x
let user = {name: 'Mike'}
let mike = {...user, age:30}
console.log(mike) // {name:"Mike", age:30}

// 전개 구문: 복제
let arr1 = [1, 2, 3];
let arr2 = [...arr]; // [1, 2, 3]

let user = {name:'Mike', age:30};
let user2 = {...user};
user2.name = "Tom";

console.log(user.name); // 'Mike'
console.log(user2.name); // 'Tom'

// ex arr1을 [4,5,6,1,2,3]으로 하려면?
let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];

arr2.forEach((num) => {
    arr1.unshift(num);
})
console.log(arr1); // [6,5,4,1,2,3]

arr2.reverse().forEach((num) => {
    arr1.unshift(num);
})
console.log(arr1); // [4,5,6,1,2,3]

// 전개구문 사용
arr1 = [...arr2, ...arr1];

// ex user에 info를 넣고 fe와 lang을 skills을 만들어 넣기
let user = {name:'Mike'};
let info = {age:30};
let fe = ["JS", "React"];
let lang = ["Korean", "English"];

// 전개 구문 미사용시
user = Object.assign({}, user,info,{
        skills: []
});
fe.forEach(item => {
    user.skills.push(item);
});
lang.forEach(item => {
    user.skills.push(item);
});

// 전개 구문 사용해보자
user = {
    ...user, 
    ...info, 
    skills : [...fe, ...lang],
};
    
console.log(user);

lecture11

// 클로저

// 자바스크립트는 어휘적 환경

// here
let one;
one = 1;
function addOne(num) {
    console.log(one + num);
}
addOne(5);
/* 코드가 실행되면 
스크립트 내에서 선언한 변수들이 Lexical 환경에 올라감
Lexical 환경
one: 초기화 x -> 사용 불가
addOne: function 함수 선언문은 변수와 달리 바로 초기화 -> 사용 가능 (변수에 할당한 함수 표현식은 불가능)
*/

let one; // here
one = 1;
function addOne(num) {
    console.log(one + num);
}
addOne(5);
/* Lexical 환경
one: undefined 아직 할당은 안되어 있기 때문에 초기값 undefined를 가짐 -> 사용 가능
addOne: function 
*/

let one;
one = 1; // here
function addOne(num) {
    console.log(one + num);
}
addOne(5);
/* Lexical 환경
one: 1 숫자 1 할당
addOne: function 
*/

let one;
one = 1; 
function addOne(num) {
    console.log(one + num);
}
addOne(5); // here
/* 전역 Lexical 환경
one: 1 숫자 1 할당
addOne: function 

함수가 실행되는 순간 새로운 Lexical 환경이 만들어짐
함수가 넘겨받은 매개변수와 지역변수들이 저장
내부 Lexical 환경
num: 5
내부 Lexical 환경은 외부 Lexical환경에 대한 참조를 가짐

지금은 저 함수의 외부 Lexical환경이 전역 Lexical환경임
코드에서 변수를 찾을 때 내부에서 찾고 없으면 외부, 거기에도 없으면 전역 Lexical 환경까지 범위를 넓혀서 찾음

console.log(one + num)코드의 one과 num은 내부 Lexical 환경에서 우선 찾음
num은 찾았지만 one이 없으니 외부로 넓혀서 있는지 찾음
*/

// 클로저는 함수와 그 함수의 렉시컬 환경의 조합
// 함수가 생성될 당시의 외부 변수를 기억
// 생성 이후에도 그 변수에 계속 접근 가능
// 외부 함수의 실행이 끝나 소멸된 이후에도 내부 함수가 외부 함수의 변수에 접근 가능

// 은닉화

lecture12

// setTimeout 일정 시간이 지난 후 함수를 실행
// setInterval 일정 시간 간격으로 함수 반복

// setTimeout
// 두 개의 매개변수를 받음
// 일정 시간이 지난 뒤 실행하는 함수와 시간 
function fn() {
    console.log(3)
}
setTimeout(fn, 3000); // = 3초
// 아래와 동일
setTimeout(function() {
    console.log(3)
}, 3000);

// 인수가 필요하면 시간 뒤에 적어줌
// clearTimeout()은 예정된 작업을 없앰
// setTimeout은 타임 아이디를 반환하는데 이를 이용해 스케줄링을 취소할 수 있음
function showName(name) {
    console.log(name);
}
const tId = setTimeout(showName, 3000, 'Mike'); // 함수, 시간, 인수
clearTimeout(tId); // 3초가 지나기 전에 이 코드가 실행되기 때문에 아무 일도 일어나지 않음


// setInterval 계속 반복 수행
function showName(name) {
    console.log(name);
}
const tId = setInterval(showName, 3000, 'Mike');
clearTimeout(tId); // 중단하려면


// 주의
// 딜레이 타임을 0으로 해도 바로 실행되지 않음
setTimeout(function() {
    console.log(2)
}, 0);
console.log(1)
// 1이 먼저 찍히고 2가 나중에 찍힘
// 현재 실행 중인 스크립트가 종료된 이후 스케줄링 함수를 실행하기 때문
// 또한 브라우저는 기본적으로 4ms~ 정도의 대기시간이 있음


// ex
let num = 0;
function showTime() {
    console.log(`안녕하세요. 접속한지 ${num++}초가 지났습니다.`);
    if (num > 5) {
        clearInterval(tId);
    }
}
const tId = setInterval(showTime, 1000);

lecture13

// call, apply, bind
// 함수 호출 방식과 관계없이 this를 지정할 수 있음

// call 메소드는 모든 함수에서 사용할 수 있으며, this를 특정값으로 지정할 수 있습니다. 

const mike = {
    name: "Mike",
};

const tom = {
    name: "Tom",
};

function showThisName() {
    console.log(this.name);
}

// 여기서 this는 window를 가리킴
// window.name이 빈 문자열이기 때문에 아무것도 안뜸
showThisName(); 

// 함수를 호출하며 call을 사용하고 this로 사용할 객체를 넘기면 해당 함수가 주어진 겍체의 메소드인 것 처럼 사용 가능
// call의 첫 번째 매개변수는 this로 사용할 값이고 매개변수가 더 있으면 그 매개변수를 호출하는 함수로 전달 됨
showThisName.call(mike); // Mike

// 생년과 직업을 받아 이 객체정보를 새로운 데이터로 업데이트
function update(birthYear, occupation) {
    this.birthYear = birthYear;
    this.occupation = occupation
}
// 첫 번째 매개변수는 this로 사용될 값
// 이후 매개변수부터는 함수가 사용할 매개변수들을 순서대로 적은 것
update.call(mike, 1999, "singer"); 
console.log(mike); // {name: "Mike", birthYear: 1999, occupation: "singer"}


// apply : 함수 매개변수를 처리하는 방법을 제외하면 call과 완전히 같음 
// call은 일반적인 함수와 마찬가지로 매개변수를 직접 받지만, apply는 매개변수를 배열로 받음
function update(birthYear, occupation) {
    this.birthYear = birthYear;
    this.occupation = occupation
}
update.apply(mike, [1999, "singer"]); 
console.log(mike); // {name: "Mike", birthYear: 1999, occupation: "singer"}

// apply는 배열 요소를 함수 매개변수로 사용할 때 유용
const nums = [3, 10, 1, 6, 4];

// apply는 두 번째 매개변수로 배열을 전달하면 그 요소들을 차례대로 인수로 사용
// null은 this로 사용될 값 (min이나 max는 this가 필요하지 않아서 아무 값이나 넣은 것)
const minNum = Math.min.apply(null, nums); 
// = Math.min.apply(null, [3, 10, 1, 6, 4])
const maxNum = Math.max.apply(null, nums);

// 동일한 결과
const minNum = Math.min.call(null, ...nums);
// = Math.min.apply(null, 3, 10, 1, 6, 4)
// call과 apply는 동작 방식은 같음
// 매개변수를 받는 방법만 다를 뿐

console.log(minNum);
console.log(maxNum);


// bind : 함수의 this값을 영구히 바꿀 수 있음
const mike = {
    name: "Mike",
};

function update(birthYear, occupation) {
    this.birthYear = birthYear;
    this.occupation = occupation
}

// bind는 새로 바인딩한 함수를 하나 만듦
// 이 함수는 항상 mike를 this로 가짐
const updateMike = update.bind(mike);

updateMike(1980, 'police');
console.log(mike);


const user = {
    name: "Mike",
    showName: function () {
        console.log(`hello, ${this.name}`);
    },
};

user.showName();

let fn = user.showName; // fn에 할당할 때 this를 잃어버림
// 메소드는 점 앞에 있는게 this
fn(); // hello,
// 호출할 때 fn만 호출하니까 this가 없는 것

fn.call(user); // hello, Mike
fn.apply(user); // hello, Mike

let boundFn = fn.bind(user);
boundFn(); // hello, Mike

lecture14

// 상속, 프로토타입

const user = {
    name: "Mike"
}
user.name // "Mike"
user.hasOwnProperty('name') // true
user.hasOwnProperty('age') // false

// user 객체에 name이라는 프로퍼티가 있으니까 참이 나오고 age는 없으니까 거짓
// hasOwnProperty는 만든 적이 없는데 어디있는 걸까?
// __proto__에 있는데 이것을 프로토타입이라고 함

const user = {
    name: "Mike",
    hasOwnProperty : function() {
        console.log('haha')
    }
}
user.hasOwnProperty(); // haha
// 일단 객체에 프로퍼티가 있으면 거기서 탐색을 멈춤
// 없을 때만 프로토타입에서 프로퍼티를 찾음


const bmw = {
    color: "red",
    wheels: 4,
    navigation: 1,
    drive() {
        console.log("drive..");
    },
};

const benz = {
    color: "black",
    wheels: 4,
    drive() {
        console.log("drive..");
    },
};

const audi = {
    color: "blue",
    wheels: 4,
    drive() {
        console.log("drive..");
    },
};
// wheels랑 drive() 동일 -> 차들이 늘어나면 새로운 변수로 만들어지는 건데 공통된 부분을 어떻게 처리? -> 프로토타입으로 처리

const car = {
    wheels: 4,
    drive() {
        console.log("drive..");
    },
}

const bmw = {
    color: "red",
    navigation: 1,
};

const benz = {
    color: "black",
};

const audi = {
    color: "blue",
};

bmw.__proto__ = car;
benz.__proto__ = car;
audi.__proto__ = car;

console.log(bmw); // {color: "red",navigation: 1}
console.log(bmw.color); // "red"
console.log(bmw.wheels); // 4
// bmw객체 내부에서 wheels 프로퍼티를 찾고 찾으면 탐색을 멈춤
// 없다면 프로토타입에서 확인

// 상속은 계속 이어질 수 있음
const x5 = {
    color: "white",
    name: "x5"
};

x5.__proto__ = bmw;
console.log(x5.name); // "x5"
console.log(x5.color); // "white"
console.log(x5.navigation);
// x5에서 navigation을 찾고 없으니까 프로토타입인 bmw에서 탐색을 하고 있으니까 멈춤
console.log(x5.drive());
// x5에 드라이브 메소드가 없고 bmw에도 없고 프로토타입인 car까지 올라가서 드라이브 메소드를 사용
// 이런것을 프로토타입 체인이라고 함
// prototype chain

for (p in x5) {
    console.log(p);
}
/* 
color 
name
navigation
wheels
drive
*/

// 키, 값과 관련된 객체 내장 메소드는 상속된 프로퍼티는 나오지 않음
Object.keys(x5); // ["color", "name"]
Object.values(x5); // ["white", "x5"]

for (p in x5) {
    if (x5.hasOwnProperty(p)) {
        console.log('o', p);
    } else {
        console.log('x', p);
    }
}
/* 
o color 
o name
x navigation
x wheels
x drive
*/
// hasOwnProperty는 객체가 직접 가지고 있는 프로퍼티만 true를 반환해줌


const car = {
    wheels: 4,
    drive() {
        console.log("drive..");
    },
}

const Bmw = function (color) {
    this.color = color;
}

const x5 = new Bmw("red");
const z4 = new Bmw("blue");

// 생성자 함수를 쓰는 이유가 간편해서인데매번 하나를 만들 때마다 이렇게 하면 귀찮음
x5.__proto__ = car;
z4.__proto__ = car;


// 아래처럼 하자!
const Bmw = function (color) {
    this.color = color;
}

// 생성자함수가 생성하는 객체의 __proto__ 이렇게 설정한다는 의미 
// 이렇게 한 번만 작업을 해주면 생성자로 만들어진 모든 객체에 일일이 작업할 필요가 없어짐
Bmw.prototype.wheels = 4;
Bmw.prototype.drive = function () {
    console.log("drive..")
}

const x5 = new Bmw("red");
const z4 = new Bmw("blue");

// 생성자 함수가 새로운 객체를 만들어 낼 때 그 객체는 생성자의 instance라고 불려짐
// 자바스크립트는 이를 편리하게 확인할 수 있는 instanceof연산자가 있음
// 객체와 생성자를 비교할 수 있고, 해당 객체가 그 생성자로부터 생성된 것인지를 판단해서 참 혹은 거짓을 반환

z4 instanceof Bmw; // true

// 생성자로 만들어진 instance객체에는 constructor라는 프로퍼티가 존재 
z4.constructor === Bmw;// true


// 코드를 좀 더 깔끔히
const Bmw = function (color) {
    this.color = color;
}

// 하지만 이렇게 하면 constructor가 사라짐
Bmw.prototype = {
    wheels : 4,
    drive() {
        console.log("drive..");
    },
    navigation : 1,
    stop() {
        console.log("STOP!");
    }
}

const x5 = new Bmw("red");
const z4 = new Bmw("blue");

z4.constructor === Bmw;// false
// 이런 현상을 방지하기 위해 하나씩 프로퍼티를 추가하는 게 좋음
// 혹은
Bmw.prototype = {
    constructor: Bmw, // 이렇게 명시해줘도 괜찮음
    wheels : 4,
    drive() {
        console.log("drive..");
    },
    navigation : 1,
    stop() {
        console.log("STOP!");
    }
}
z4.constructor === Bmw;// true


const Bmw = function (color) {
    this.color = color;
}
const x5 = new Bmw("red");

x5.color; // "red"
x5.color = "black";
x5.color; // "black"

// 아무나 색상을 바꿀 수 있음 -> 클로저 사용
const Bmw = function (color) {
    const c = color;
    this.getColor = function () {
        console.log(c);
    }
}
const x5 = new Bmw("red");
// 코드를 이렇게 바꾸면 초기에 세팅된 컬러 값을 얻을 수만 있고 바꿀 수는 없음

lecture15

// 클래스 : ES6에 추가된 스펙

// 지금까지 비슷한 형태의 객체를 생성하기 위해 생성자 함수 사용
const User = function (name, age) {
        this.name = name;
        this.age = age;
};

User.prototype.showName = function () {
    console.log(this.name);
}

const mike = new User("Mike", 30);

// 클래스로도 만들 수 있음
class User2 {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
    showName() {
        console.log(this.name);
    }
}
const tom = new User2("Tom", 10);

// new를 통해 호출 했을 때 내부에서 정의된 내용으로 객체를 생성하는 것은 동일
/* class키워드 사용
constructor는 객체를 만들어주는 생성자 메소드 
new를 통해 호출하면 자동으로 constructor실행
constructor는 인수를 넘겨받을 수 있음
클래스 내 정의 메소드는 user2의 프로토타입에 정의
*/

/* 생성자와 클래스 차이 : 클래스의 탄생 이유?

생성자의 경우
new를 빼고 실행시 const mike = User("Mike", 30);
User함수가 반환 하는 값 (지금은 return 문이 없어 아무것도 반환하지 않기 때문에 undefined가 됨)
반환 값이 undefined고 그 값이 mike로 들어감
개발자가 실수한 코드지만 문제없이 동작함
에러라고 알아차릴 수 없음

클래스의 경우
const tom = User2("Tom", 10);
타입 에러 발생
클래스는 new없이 실행할 수 없음

두 경우의 프로토타입을 살펴보면
클래스의 경우에는 컨스트럭터가 클래스라고 명시가 되어있음
이 경우에 new 없이 호출하면 에러가 발생하도록 설계되어 있음


for in문을 사용할 경우
생성자의 경우
showName도 나옴

클래스의 경우
showName은 나오지 않음

for in문은 프로토타입에 포함된 프로퍼티들을 다 보여주고 객체가 가지고 있는 프로퍼티만 판별하기 위해 hasOwnProperty를 사용해야 했음
클래스의 메소드는 for in 문에서 제외 됨
*/

// 생성자에서의 상속은 프로토타입을 이용해 구현
// 클래스에서의 상속은 extends키워드를 사용

class Car {
    constructor(color) {
        this.color = color;
        this.wheel = 4;
    }
    drive() {
        console.log("drive..");
    }
    stop() {
        console.log("STOP!");
    }
}

class Bmw extends Car {
    park() {
        console.log("PARK");
    }
}
const z4 = new Bmw("blue");

// 메소드 오버라이딩
// 동일한 이름으로 메소드를 정의하면 덮어 씀
class Bmw extends Car {
    park() {
        console.log("PARK");
    }
    // 만약 부모의 메소드를 사용하면서 확장하고 싶을 때는 super라는 키워드 사용
    stop() {
        super.stop(); // Car의 stop을 사용함
        console.log("OFF!");
    }
}

// 생성자 오버라이딩
class Bmw extends Car {
    constructor() {
        this.navigation = 1;
    }
    park() {
        console.log("PARK");
    }
} 
const z4 = new Bmw("blue"); // 에러! constructor에서 this를 사용하기 전에 super constructor, 즉 부모 생성자를 반드시 먼저 호출해야함

// 클래스의 constructor는 {} 빈 객체를 만들어주고 this로 이 객체를 가리킴
// 반면 extends를 써서 만든 자식 클래스는 빈 객체가 만들어지고 this에 할당하는 이 작업을 건너 뜀
class Bmw extends Car {
    constructor() {
        super(); // 항상 super키워드로 부모 클래스의 컨스트럭터를 실행해줘야 함
        this.navigation = 1;
    }
    park() {
        console.log("PARK");
    }
} 
const z4 = new Bmw("blue"); // color가 undefined가 됨
// 제대로 동작하기 위해서는 자식 클래스의 constructor에 동일한 인수를 받는 작업을 해줘야 함
class Bmw extends Car {
    constructor(color) {
        super(color);
        this.navigation = 1;
    }
    park() {
        console.log("PARK");
    }
} 


class Bmw extends Car {
    park() {
        console.log("PARK");
    }
} 
// 자식 클래스에 컨스트럭터가 없을 때는 아래처럼 동작함
class Bmw extends Car {
    constructor(...args) {
        super(...args);
    } // 이 부분이 있는 것 처럼 행동
    park() {
        console.log("PARK");
    }
} 

// 자식 생성자는 무조건 부모 생성자를 호출해야함
// super을 이용해 호출해주고 this.프로퍼티도 할당해줘야 함

lecture16

// 프로미스 Promise

const pr = new Promise((resolve, reject) => {
    // resolve 성공한 경우 실행되는 함수
    // reject는 실패한 경우 실행되는 함수
    // 위와 같이 어떤 일이 완료된 이후에 실행되는 함수 : callback 함수
});

// new Promise가 반환하는 프로미스 객체는 state와 result를 프로퍼티로 가짐
/* 
state는 초기에 pending(대기), result는 undefined
-> resolve(value)호출 시(성공 시) fulfilled(이행됨)가 됨, result는 value가 됨 (resolve 함수로 전달된 값)
-> reject(error)가 호출 되면(실패) rejected(거부됨)이 됨,
result는 error가 됨 (reject 함수로 전달된 에러)
*/

const pr = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('OK')
    }, 3000)
});
// state는 peding이었다가 3초 뒤에 fulfilled로 바뀌고 result는 'OK'가 됨

const pr = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject(new Error('error...'))
    }, 3000)
});
// state는 peding이었다가 3초 뒤에 rejected로 바뀌고 result는 error가 됨

// 지금까지 판매자의 코드
// 주문을 받으면 3초 동안 작업 후 성공, 실패를 알려줌

const pr = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('OK')
    }, 3000)
});

// 이제 소비자의 코드 
// then을 이용해서 resolve와 reject를 처리 가능
pr.then(
    function(result){}, // promise가 이행 되었을 때 실행 result에는 OK가 들어옴
    function(err){} // promise가 거부 되었을 때 실행 result에는 에러가 들어옴
)

pr.then(
    function(result){
        console.log(result + ' 가지러 가자.');
    },
    function(err){
        onsole.log('다시 주문해주세요..');
    } 
) 

// then 이외에 사용할 수 있는 것: catch, finally

// catch는 에러가 발생한 경우 즉 reject인 경우에만 실행 됨
// catch: then과 동일하게 동작하지만 가독성이 좋고 첫 번째 함수를 실행했다가 나는 에러도 잡아줄 수 있음
pr.then(
    function(result){}
).catch(
    function(err){} 
)

// finally는 이행이든 거부든 처리가 완료되면 항상 실행
pr.then(
    function(result){}
).catch(
    function(err){} 
).finally(
    function() {
        console.log('---주문 끝---')
    }
)

// Promise.all
console.time('x')
Promise.all([f1(), f2(), f3()]).then((res) => {
    console.log(res);
    console.timeEnd('x');
});
// 세 작업이 모두 완료되어야 then 부분이 실행
// Promise.all은 한꺼번에 시작하고 모두 이행되면 값을 사용할 수 있음 -> 시간 절약
// 만일 reject되는 경우가 있다면 어떤 데이터도 얻지 못함
// 하나의 정보라도 누락되면 페이지를 보여주면 안되는 경우 사용할 수 있음

// Promise.race
Promise.race([f1(), f2(), f3()]).then((res) => {
    console.log(res);
});
// all과의 차이점 : all은 모든 작업이 완료될 때까지 기다리지만 race는 하나라도 1등으로 완료되면 끝냄

lecture17

// async, await
// 프로미스의 then 메소드를 체인 형식으로 호출하는 것보다 가독성이 좋아짐

// 함수 앞에 async라는 키워드를 붙이면 항상 프로미스 반환
async function getName() {
    return "Mike";
}
console.log(getName()); //Promise {<fulfilled>: "Mike"}

getName().then((name) => {
    console.log(name);
});

// await 키워드는 async함수 내에서만 사용할 수 있음
//await 키워드 오른쪽에 프로미스
function getName(name) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(name);
        }, 1000);
    });
}
async function showName() {
    const result = await getName('Mike'); //getName에서 resolve된 값을 기다렸다가 넣어줌
    console.log(result); // 1초 뒤에 Mike 찍힘
}
console.log("시작");
showName();

// 비동기 함수 병렬로 실행

lecture18

// Generator 함수의 실행을 중간에 멈췄다가 재개할 수 있는 기능
// Generator는 function 옆에 *을 써서 만듦
// 내부에 yield 키워드를 사용
function* fn() {
    yield 1;
    yield 2;
    yield 3;
    return "finish";
}

// 제너레이터 함수를 실행하면 제너레이터 객체 반환
// 제너레이터 객체는 next 메소드가 있음
const a = fn();

console.log(a); // 코드 실행시 제너레이터 객체만 반환되고 함수 본문 코드는 실행되지 않음

console.log(a.next()); // 가장 가까운 yield문을 만날 때까지 실행되고 데이터 객체를 반환
// 반환된 데이터 객체는 {value: 1, done: false}
// value는 yield 오른쪽에 있는 값 생략시 undefined
// done은 함수 코드가 끝났는지 나타내며 실행이 끝났으면 true

console.log(a.next()); // {value: 2, done: false}
console.log(a.next()); // {value: 3, done: false}
console.log(a.next()); // {value: "finish", done: true}
console.log(a.next()); // {value: undefined, done: true}


// 제너레이터는 next()메소드 외에 return()과 throw()메소드를 가짐

// 실행 중 리턴 메소드를 호출하면 그 즉시 done이 true가 됨
console.log(a.return('END')); // {value: "END", done: true}
console.log(a.next()); // {value: undefined, done: true}

// throw()도 마찬가지로 done을 true로 바꿈
function* fn() {
    try {
        yield 1;
        yield 2;
        yield 3;
        return "finish";
    } catch (e) {
        console.log(e);
    }
}

console.log(a.next()); // {value: 1, done: false}
console.log(a.next()); // {value: 2, done: false}
console.log(a.throw(new Error('err'))); // catch문에 있는 내용이 실행되고 {value: undefined, done: true}
console.log(a.next()); // {value: undefined, done: true}


/* Generator

iterable (반복이 가능)
- Symbol.iterator 메소드가 있다
- Symbol.iterator는 iterator를 반환해야 한다

iterator (메소드 호출 결과)
- next 메소드를 가진다
- next 메소드는 value와 done 속성을 가진 객체를 반환한다
- 작업이 끝나면 done은 true가 된다

Generator는 iterator이면서 iterable이다 
iterable은 for of를 통해 순회 가능
문자열도 배열도 iterable

Generator는 외부로부터 값을 입력받을 수도 있다
Generator는 값을 미리 만들어 두지 않음
*/

const arr = [1, 2, 3, 4, 5];
// arr가 가지고 있는 Symbol.iterator메소드를 실행한 결과를 넣기
const it = arr[Symbol.iterator]();
it.next(); // {value: 1, done: false}..
it.next(); // {value: undefined, done: true}


function* gen1() {
    yield "w";
    yield "o";
    yield "r";
    yield "l";
    yield "d";
}
function* gen2() {
    yield "Hello, ";
    yield* gen1();
    yield "!";
}
console.log(...gen2()); //Hello, w o r l d !

 


📌 아래 강의의 내용을 정리한 글입니다.

 

복사했습니다!