[Front-end] 개발자 공부

[개발 공부 48일차] Scope Chain, 재귀적 수행 | JS 문법 재청강

MOLLY_ 2024. 3. 4. 06:27
728x90

 

그동안 하려고 했었던 JavaScript 문법 종합반 강의를 다시 완강하였다. 보면서 작성한 코드만 해도 양이 엄청 많다. 이번 연휴엔 너무 졸리고 피곤해서 자고, 쉰 시간이 많긴 하지만 그래도 JS 문법을 전반적으로 다시 공부함으로써 코드에 대한 이해력이 많이 오른 듯하다.

 

 

< 목차 >
1. 재귀적 수행 (재귀 호출)
2. Scope Chain (스코프 체인)
3. 실행 컨텍스트
4. J
S 문법 종합반 들으며 작성한 코드
5. 금일 소감

 

 

1. 재귀적 수행 (재귀 호출)

: 함수가 직접적으로 또는 간접적으로 자기 자신을 호출하는 과정

재귀 함수는 특정 조건(기저 조건)이 충족될 때까지 자기 자신을 계속해서 호출한다.

 

기저 조건재귀가 무한히 반복되는 것을 방지하기 위해 설정된다. 재귀적 수행복잡한 문제를 간단하고 명료한 방법으로 해결할 수 있게 해주며, 특히 데이터 구조와 알고리즘을 다룰 때 유용하다.

 


재귀 함수의 핵심 요소

  • 기저 조건(Base Case): 재귀 호출을 멈추는 조건. 이 조건이 없으면 함수는 무한히 자기 자신을 호출할 것
  • 재귀 단계(Recursive Step): 함수가 자기 자신을 호출하는 부분. 각 호출은 문제의 규모를 줄여 기저 조건에 접근하게 한다.

 


▼  재귀 함수의 예

function factorial(n) {
    // 기저 조건
    if (n === 0 || n === 1) {
        return 1;
    }
    // 재귀 단계
    return n * factorial(n - 1);
}
console.log(factorial(5)); // 120

 

 

 

* 재귀 사용 시, 고려사항

  1. 스택 오버플로우: 자바스크립트 엔진은 호출 스택(call stack)을 사용하여 함수 호출을 관리한다. 재귀 함수가 너무 깊게 호출되면 스택 오버플로우 오류가 발생할 수 있다.
  2. 성능: 때때로 재귀적 접근은 반복적(루프 기반) 접근보다 성능이 낮을 수 있다. 특히 재귀 호출이 많은 메모리를 사용하거나, 같은 계산을 반복할 때다.
  3. 최적화: 일부 경우에는 "꼬리 재귀 최적화(tail recursion optimization)"를 사용하여 성능 문제를 완화할 수 있지만, 모든 자바스크립트 엔진이 이를 지원하는 것은 아니다.

 

 

2. Scope Chain (스코프 체인)

: 변수 또는 함수를 찾기 위해 엔진이 따르는 스코프(유효 범위)의 계층적 체인

 

Scope Chain

 

 

스코프 체인은 현재 실행 컨텍스트에서 시작하여 외부 환경으로 점진적으로 이동하면서, 참조된 식별자(변수, 함수 등)의 값을 찾는다. 이 과정은 가장 가까운 스코프에서 시작하여 전역 스코프까지 이어진다.

 


핵심 개념

  • 렉시컬 스코프(Lexical Scope): 자바스크립트는 렉시컬 스코프(정적 스코프)를 따른다. 이는 함수가 선언된 시점의 스코프에 따라 그 함수의 스코프가 결정된다는 의미다. 함수 실행 시, 함수의 스코프는 실행되는 위치가 아닌, 선언될 때의 환경을 기준으로 결정된다.
  • 스코프 체인의 형성: 함수가 실행될 때, 해당 함수의 스코프 체인이 생성된다. 이 체인은 함수 자체의 스코프와, 함수가 선언될 때의 외부 스코프들을 포함한다.
  • 변수 접근: 스크립트에서 변수를 참조할 때, 자바스크립트 엔진은 현재 스코프에서부터 시작하여 스코프 체인을 따라 올라가며 해당 변수를 찾는다. 가장 먼저 찾은 변수의 값을 사용하고, 만약 전역 스코프까지 찾아도 변수를 찾지 못하면 ReferenceError를 발생시킨다.
  • 성능 고려사항: 스코프 체인이 길어질수록 변수 접근 시간이 늘어날 수 있다. 따라서, 필요 이상으로 깊은 스코프 체인은 가능한 피하는 것이 좋다.

 

 

[Reference]

https://velog.io/@cyongchoi/Scope-Chain-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0

 

 

 

3. 실행 컨텍스트

: 코드가 실행되기 위한 환경 또는 범위

 

 

 

스크립트 또는 함수가 실행되는 동안 엔진이 필요한 모든 정보를 담고 있어, 해당 코드의 실행을 가능하게 한다. 실행 컨텍스트는 자바스크립트 엔진이 코드를 실행할 때 생성되는 추상적인 개념으로, 변수, 함수 선언, this 키워드 값 등 코드 실행에 필요한 모든 정보를 포함한다.

 


실행 컨텍스트의 핵심 구성 요소

  • 변수 환경(Variable Environment): 변수, 함수 선언, 그리고 변수의 최신 상태를 기록하는 환경이다. 여기에는 또한 부모 스코프의 링크도 포함되어, 스코프 체인을 형성한다.
  • 외부 환경(Lexical Environment): 재 컨텍스트가 참조하는 외부 코드의 환경을 가리킨다. 이는 스코프 체인을 형성하는 데에 도움이 된다.
  • This 바인딩실행 컨텍스트가 활성화될 때 this의 값이 결정된다. this의 값은 함수가 어떻게 호출되었는지에 따라 달라진다.

 


실행 컨텍스트의 유형

  • 전역 실행 컨텍스트(Global Execution Context): 가장 바탕이 되는 컨텍스트로, 스크립트가 실행되기 시작할 때 생성된다. 전역 변수와 함수는 이 컨텍스트에 저장된다.
  • 함수 실행 컨텍스트(Function Execution Context): 함수가 호출될 때마다 해당 함수를 위한 새로운 컨텍스트가 생성된다. 각 함수 호출은 고유한 실행 컨텍스트를 가지며, 이는 함수 실행 동안 필요한 모든 정보를 포함한다.
  • eval 실행 컨텍스트(Eval Execution Context)eval 함수를 통해 실행되는 코드를 위한 컨텍스트다. 사용은 권장되지 않는다.

 


실행 과정

  1. 생성 단계(Creation Phase): 실행 컨텍스트가 생성된다. 이 단계에서는 변수와 함수 선언이 메모리에 저장되며, this 바인딩이 결정된다.
  2. 실행 단계(Execution Phase)드가 실제로 실행된다. 이 단계에서 변수에 값이 할당되고, 함수가 호출된다.

 


실행 컨텍스트는 자바스크립트 엔진이 코드를 어떻게 처리하는지 이해하는 데 핵심적인 개념이다. 코드가 실행될 때마다 새로운 실행 컨텍스트가 스택에 push 되고, 코드 실행이 완료되면 해당 컨텍스트는 스택에서 pop 된다. 이 과정을 통해 자바스크립트 엔진은 함수 호출과 변수 접근을 정확히 관리할 수 있다.

 

 

 

4. JS 문법 종합반 들으며 작성한 코드

정말 양이 많은데 기록 삼아 한 번 쭉 적어두려고 한다.

 

 

▼  1주차 들으며 작성한 코드

// [1-2] 변수와 상수
// 변수, 상수: 메모리에 저장하고, 읽어들여서 재사용함

// [변수의 5가지 주요 개념]
// 변수명: 저장된 값의 고유 이름
// 변수 값: 변수에 저장된 값
// 변수 할당: 변수에 값을 저장하는 행위
// 변수 선언: 변수를 사용하기 위해 컴퓨터에 알리는 행위
// 변수 참조: 변수에 할당된 값을 읽어오는 것
const a = 1;
const b = 2;

const value = a + b; // a와 b를 참조했다고 표현함

// 변수를 선언하는 3가지 방법: var, let, const

// [1-3, 1-4] 데이터 타입
// 데이터 타입은 runtime(코드가 실행될 때)에 결정됨
// ex. Java: String a = "abc";
//     JS : const a = "abc";

// 1. 숫자
// 1-1. 정수
let num1 = 10;
console.log(num1);
console.log(typeof num1);

// 1-2. 실수(float)
let num2 = 3.14;
console.log(num2);
console.log(typeof num2);

// 1-3. 지수형(Exp)
let num3 = 2.5e5; // 250000
console.log(num3);
console.log(typeof num3);

// 1-4. NaN(Not a Number)
let num4 = 'Hello' / 2;
console.log(num4);

// 1-5. Infinity(+무한대)
let num5 = 1 / 0;
console.log(num5);
console.log(typeof num5);

// 1-6. Infinity(-무한대)
let num6 = -1 / 0;
console.log(num6);
console.log(typeof num6);

// 2. 문자: String(문자열 = 문자의 나열)
// ' ' = " "
let str = 'Hello, World!';
console.log(str);
console.log(typeof str);

// 2-1. 문자열 길이 확인하기
console.log(str.length); // 13

// 2-2. 문자열 결합하기(concatenaion)
let str1 = 'Hello, ';
let str2 = 'World!';
let result = str1.concat(str2);
console.log(result);

// 2-3. 문자열 자르기
let str3 = 'Hello, World!';
console.log(str3.substr(7, 5)); // 7번째 문자부터 5개만 잘라서 출력해줘
console.log(str3.slice(7, 12)); // 7번째 문자에서 12번쨰 문자까지만 잘라줘

// 2-4. 문자열 검색
let str4 = 'Hello, World!';
console.log(str4.search('World')); // 7번째부터 시작됨을 알려줌

// 2-5. 문자열 대체
let str5 = 'Hello, World!';
let result01 = str5.replace('World', 'JavaScript'); // World를 JavaScipt로 대체됨
console.log(result01);

// 2-6. 문자열 분할
let str6 = 'apple, banana, kiwi';
let result02 = str6.split(', ');
console.log(result02);

// 3. Boolean(불리언)
// true(참), false(거짓)
let bool1 = true;
let bool2 = false;

console.log(bool1);
console.log(typeof bool1);
console.log(bool2);
console.log(typeof bool2);

// 4. undefined: 값이 할당되지 않은 변수
let x;
console.log(x); // undefined

// 5. null: 값이 존재하지 않음을 '명시적'으로 나타내는 방법
let y = null;
console.log(y); // null

// 6. 객체(object): key-value pair
let person = {
    name: 'choi',
    age: 20,
    isMarried: true,
};
console.log(person);

// 7. 배열(array): 여러 개의 데이터를 순서대로 저장하는 데이터 타입
// index를 가짐
let number = [1, 2, 3, 4, 5];

// [1-5] 형변환: 형태를 바꾼다
// 명시적 형변환: 개발자가 의도적으로 형태를 바꿈
// 암시적 형변환: 의도하지 않았지만 자동으로 바뀌어지는 것

// 1. 암시적
// 1-1. 문자열
let result1 = 1 + '2';
console.log(result1); // 12
console.log(typeof result1); // string

let result2 = '1' + true;
console.log(result2); // 1true
console.log(typeof result2); // string

// { }, null, undefined + "1" ⇒ 문자열

// 1-2. 숫자
// + 연산자를 제외하곤 모두 숫자로 변환됨
let result3 = 1 - '2';
console.log(result3); // -1
console.log(typeof result3); // number

let result4 = '2' * '3';
console.log(result4); // 6
console.log(typeof result4); // number

// 2. 명시적 형 변환
// 2-1. Boolean
// false 나오는 형
console.log(Boolean(0));
console.log(Boolean(''));
console.log(Boolean(null));
console.log(Boolean(undefined));
console.log(Boolean(NaN));

// true 나오는 형
console.log(Boolean('false')); // 빈 문자열이 아닌 어떤 값이 있는 문자열은 true임
console.log(Boolean({})); // 객체는 항상 true

// 2-2. 문자열
let result5 = String(123);
console.log(result5); // 123
console.log(typeof result5); // string

let result6 = String(true);
console.log(result6); // true
console.log(typeof result6); // string

let result7 = String(false);
console.log(result7); // false
console.log(typeof result7); // string

let result8 = String(null);
console.log(result8); // null
console.log(typeof result8); // string

let result9 = String(undefined);
console.log(result9); // undefined
console.log(typeof result9); // string

// 1-3. Number
let result10 = Number('123');
console.log(result10); // 123
console.log(typeof result10); // number

// [1-6] 연산자
// 1. 더하기 연산자
console.log(1 + 1); // 2
console.log(1 + '1'); // 11

// 2. 빼기 연산자
console.log(1 - '2'); // -1
console.log(1 - 2); // -1

// 3. 곱하기 연산자(*)
console.log(4 * 2); // 8
console.log('4' * 2); // 8

// 4. 나누기 연산자(/)
console.log(4 / 2); // 2
console.log('4' / 2); // 2

// 5. 나누기 연산자(/) vs. 나머지 연산자(%)
console.log(5 / 2); // 2.5
console.log(5 % 2); // 1

// 6. 할당 연산자(assignment)
// 6-1. 등호 연산자(=)
let z = 10;
console.log(z);

// 6-2. 더하기 등호 연산자(+=)
z += 5;
console.log(z); // 15
z += 5;
console.log(z); // 20

// 6-3. 빼기 등호 연산자(-=)
z -= 5; // ⇒ x = x -5
console.log(z); // 15

// 6-4. 곱하기 등호 연산자(*=)
let v = 10;
v *= 2;
console.log(v); // 20

// 7. 비교 연산자: true 또는 false를 반환하는 연산자
// 7-1. 일치 연산자 (===)
// 타입까지 일치해야 true를 반환
console.log(2 === 2); // true
console.log(2 === '2'); // false

// 7-2. 불일치 연산자 (!==)
// 타입까지 불일치해야 true를 반환
console.log(2 !== 2); // false
console.log(2 !== '2'); // true

// 7-3. 작다 연산자(<), 작거나 같다 연산자(<=)
console.log(2 < 3); // true
console.log(4 <= 3); // false

// 8. 논리 연산자: true와 false를 비교하는 연산자(boolean 타입)
// 8-1. 논리곱 연산자
// 모두 true일 때, true를 반환함
console.log(true && true); // true
console.log(true && false); // false
console.log(false && true); // false
console.log(false && false); // false

// 8-2. 논리합 연산자
// 두 값 중 하나라도 true일 경우, true 반환
console.log(true || true); // true
console.log(true || false); // true
console.log(false || true); // true
console.log(false || false); // false

// 8-3. 논리부정 연산자: !
// 값을 반대로 바꿈
console.log(!true); // false

let t = true;
console.log(!a); // false

// 9. [중요] 삼항 연산자
// 조건에 따라 값을 선택함
let p = 10;
let result11 = x > 5 ? '크다' : '작다';
console.log(result);

// [과제] 3항 연산자를 사용해서 q가 10보다 작은 경우: "작다",  큰 경우: "크다"를 출력하기
let q = 20;
let result12 = q < 10 ? '작다' : '크다';
console.log(result12);

// 10. 타입 연산자(typeof)
console.log(typeof '5'); // string

// [1-7] function(함수)
// : input과 output을 가지고 있는 기능의 단위
// 기능의 단위로 묶고, 재활용함

// 1. 함수 선언문
function 함수명(매개변수) {
    // 함수 내부에서 실행할 로직
}

// 2개의 숫자를 입력 받아서 덧셈을 한 뒤, 내보내는 함수
function add(x, y) {
    return x + y;
}

// 2. 함수 표현식
let add2 = function (x, y) {
    return x + y;
};

// 3. 함수 호출(= 사용)
// 사용법: 함수명()
// ex. add(입력값)
console.log(add(2, 3));

let functionResult = add(3, 4);
console.log(functionResult);

// [과제] add2를 가지고 10과 20을 더한 값을 출력해보기
console.log(add2(10, 20));

// input: 함수의 input ⇒ 매개변수(매개체가 되는 변수!)
// output: return문 뒤에 오는 값: 반환값

// [1-8] 스코프, 전역변수, 지역변수, 화살표 함수
let r = 10;

function printR() {
    console.log(r);
}

console.log(r);
printR();

// 1. 화살표 함수
// ES6 신 문법
function add1(x, y) {
    return x + y;
}

// 1-1. 기본적인 화살표 함수
let arrowFunc01 = (x, y) => {
    return x + y;
};

// 1-2. 한 줄로 사용
// return을 뺄 수 있는 조건: 중괄호 {} 안에 있는 로직이 한 줄일 때 가능
let arrowFunc02 = (x, y) => x + y;

function testFunc(x) {
    return x;
}

// 위 로직을 화살표 함수로!
let arrowFunc03 = (x) => x;

// [1-9] 조건문(if, eles if, else, switch 등)
// 1. if문
let n = 10;

// if (true 혹은 false가 나오는 조건)
if (n > 0) {
    // main logic
    console.log('n은 양수입니다');
}

// [과제] u의 길이가 5보다 크거나 같으면 길이를 console.log로 출력
let u = 'Hello World';

if (u.length >= 5) {
    console.log(u.length); // 11
}

// 2. if ~ else문
let o = -3;

if (o > 0) {
    // main logic #1
    console.log('o는 양수입니다.');
} else {
    // main logic #2
    console.log('o는 음수입니다.');
}

// 3. if ~ else if ~ else문
let g = 2;

if (g < 0) {
    // main logic #1
    console.log('1');
} else if (g >= 0 && g < 10) {
    // main logic #2
    console.log('2');
} else {
    // main logic #3
    console.log('3');
}

// 4. switch
// 변수의 값에 따라 여러 개의 경우(case) 중, 하나를 선택
// defalt
// break; 꼭 써줘야 함. 그래야 조건에 맞을 때 멈추고 반환함

let fruit = '사과';

switch (fruit) {
    case '사과':
        console.log('사과입니다.');
        break;
    case '바나나':
        console.log('바나나입니다.');
        break;
    case '키위':
        console.log('키위입니다.');
        break;
    default:
        console.log('과일이 아닙니다.');
        break;
}

// [1-10] 조건문의 중첩
// 1. if문 중첩
let age = 16;
let gender = '여성';

// 미성년자 구분
if (age >= 18) {
    // console.log('성인입니다.');
    if (gender === '여성') {
        console.log('성인 여성입니다.');
    } else {
        console.log('성인 남성입니다.');
    }
} else {
    // console.log('미성년자입니다.');
    if (gender === '여성') {
        console.log('미성년 여성입니다.');
    } else {
        console.log('미성년 남성입니다.');
    }
}

// 2. 조건부 실행
let l = 10;

// 원래는 아래처럼 작성해야 하는데 그 아래처럼 더 짧게도 작성 가능
if (l > 0) {
    console.log('l은 양수입니다.');
}

// &&(and) 조건 때문에 실행됨
l > 0 && console.log('l은 양수입니다.');

// 3. 삼항 연산자와 단축평가
// ||(or 조건)
let k;
let j = k || 20;
// = k가 undefined이면, 기본값으로 20을 세팅해줘

console.log(j);

// 4. falsy한 값, truthy한 값
// 조건이 false에 가깝냐, true에 가깝냐

if (0) {
    // main logic
    console.log('Hello');
}

if ('') {
    // main logic
    console.log('Hello');
}

if (null) {
    // main logic
    console.log('Hello');
}

if (undefined) {
    // main logic
    console.log('Hello');
}

if (NaN) {
    // main logic
    console.log('Hello');
}

if (false) {
    // main logic
    console.log('Hello');
}

// 이것만 실행됨! 나머지는 falsy한 값임
if (true) {
    // main logic
    console.log('Hello');
}

// [1-11] 객체
// key - value pair
// [장점] 하나의 변수에 여러 개의 값을 넣을 수 있음!

// 1. 객체 생성 방법
// 1-1. 기본적인 객체 생성 방법
let person1 = {
    name: '페이커',
    age: 28,
    gender: '남자',
};

// 1-2. 생성자 함수를 이용한 객체 생성 방법
function Person(name, age, gender) {
    this.name = name;
    this.age = age;
    this.gender = gender;
}

let person2 = new Person('이상혁', 28, '남자');
let person3 = new Person('대상혁', 24, '남자');

// 2. 접근하는 방법
console.log('1', person1.name);
console.log('2', person1.age);
console.log('3', person1.gender);

// 3. 객체 메소드(객체가 가진 여러 가지 기능!)
// 사용법: Object.~~~
// 3-1. Object.keys(): key를 가져오는 메서드
let keys = Object.keys(person1);
console.log('keys => ', keys);

// 3-2. values
let values = Object.values(person1);
console.log('values => ', values);

// 3-3. entries
// key와 value를 묶어서 배열로 만든 배열 (2차원 배열)
let entries = Object.entries(person1);
console.log('entries => ', entries);

// 3-4. assign
// 객체 복사
// 소괄호() 안에 매개변수 2개 넣어줘야 함
// 어디에다가 복사할지, 뭘 복사할지
let newPerson = {};
Object.assign(newPerson, person1, { age: 31, gender: '여자' });
console.log('newPerson => ', newPerson);

// 3-5. 객체 비교
// 객체는 크기가 상당히 큼! ⇒ 그래서 메모리에 저장할 때, 별도의 공간에 저장함

// person4는 별도 공간에 대한 주소
let person4 = { name: '페이커', age: 28, gender: '남자' };

// person5도 별도 공간에 대한 주소
let person5 = { name: '페이커', age: 28, gender: '남자' };

// str7, str8은 값이 크지 않으니까 같은 저장공간에 저장함
let str7 = 'aaa';
let str8 = 'aaa';

console.log('answer => ', person4 === person5); // false
console.log('answer => ', str7 === str8); // true

// ??: 아 그럼 객체를 서로 같게 하려면 어떻게 해요?;
// JSON.stringify: 객체를 문자열화시킨다
console.log(JSON.stringify(person4) === JSON.stringify(person5)); // true

// 3-6. 객체 병합
let person6 = { name: '페이커', age: 28, gender: '남자' };
let person7 = { gender: '남자' };

// ...: spread operator
// 중괄호{}를 없애고, 다 풀어헤쳐줘!
let perfectMan = { ...person6, ...person7 };
console.log(perfectMan);

// [1-12, 1-13] 배열
// 1. 생성
// 1-1. 기본 생성
let fruits = ['사과', '바나나', '오렌지'];

// 1-2. 크기 지정
let number1 = new Array(5); // 5개의 요소를 가진 배열을 만들어줘

console.log(fruits.length); // 3
console.log(number1.length); // 5

// 2. 요소 접근
console.log(fruits[0]); // 사과
console.log(fruits[1]); // 바나나
console.log(fruits[2]); // 오렌지

// 3. 배열 메소드
// 3-1. push
let fruits1 = ['사과', '바나나'];
console.log('1 => ', fruits1); // 1 =>  [ '사과', '바나나' ]

fruits1.push('오렌지');
console.log('2 => ', fruits1); // 2 =>  [ '사과', '바나나', '오렌지' ]

// 3-2. pop
let fruits2 = ['사과', '바나나']; // 1 =>  [ '사과', '바나나' ]
console.log('1 => ', fruits2);

fruits2.pop();
console.log('2 => ', fruits2); // 2 =>  [ '사과' ]

// 3-3. shift
let fruits3 = ['사과', '바나나', '키위'];
console.log('1 => ', fruits3); // 1 =>  [ '사과', '바나나', '키위' ]

fruits3.shift();
console.log('2 => ', fruits3); // 2 =>  [ '바나나', '키위' ]

// 3-4. unshift
fruits3.unshift('포도');
console.log(fruits3); // [ '포도', '바나나', '키위' ]

// 3-5. splice
// splice(지우기 시작할 인덱스 숫자, 지울 개수, 지운 것을 대체할 요소)
// 원본 배열을 건드려서 삭제함
let fruits4 = ['사과', '바나나', '키위'];
fruits4.splice(1, 1, '포도');
console.log(fruits4);

// 3-6. slice
// 어떤 배열의 begin 부터 end 까지 포함한 얕은 복사본을 새로운 배열 객체로 반환
// 원본 배열은 바뀌지 않음
// slice(시작하는 인덱스, 끝나는 인덱스)
// [중요] 끝나는 인덱스는 포함되지 않고, 끝나는 인덱스 직전 인덱스까지만 포함됨
let fruits5 = ['사과', '바나나', '키위'];
let slicedFruits = fruits5.slice(0, 2); // [ '사과', '바나나' ]
console.log(slicedFruits);

// forEach, map, filter, find
// return문이 필요한 것들은 다 새로운 배열 or 값을 내뱉음
// 그래서 새로운 값을 선언해서 반환값을 받아줘야 했음
// (1) forEach
let numbers = [1, 2, 3, 4, 5];

// 콜백함수: 매개변수 자리에 함수를 넣는 것
numbers.forEach(function (item) {
    console.log('item입니다 => ' + item);
});

// (2) map
// 기존에 있던 배열을 가공해서 새로운 배열을 생산함
// 항상 원본 배열의 길이 만큼이 return 됨
// return 꼭 넣어줘야 함. 안 넣더라도 원본 배열만큼은 undefined로라도 출력됨
let newNumbers = numbers.map(function (item) {
    return item * 2;
});

console.log(newNumbers); // [ 2, 4, 6, 8, 10 ]

// (3) filter
// 조건에 맞는 것만 return
let filteredNumbers = numbers.filter(function (item) {
    // fliter 할 조건
    return item === 2;
});

console.log(filteredNumbers); // 2

// (4) find
// 조건의 맞는 첫 번째 것만 반환함
let findedNumber = numbers.find(function (item) {
    return item > 3; // 4
});

console.log(findedNumber);

// [반복문] for, while: ~동안
// [사용법] for (초기값; 조건식; 증감식) { }

// (1) i라는 변수는 0부터 시작할 거야
// (2) i라는 변수가 10에 도달하기 전까지 계속 할 거야
// (3) i라는 변수는 한 사이클을 돌고 나면 1을 더할 거야 (++i: 한 개씩 증가하게 할 거야)
for (let i = 0; i < 10; i++) {
    console.log('For문 돌아가고 있음 => ' + i);
}

// 배열과 for문은 짝꿍임
const arr = ['one', 'two', 'three', 'four', 'five'];
for (let i = 0; i < arr.length; i++) {
    console.log(i);
    console.log(arr[i]);
}

// [과제] 0부터 10까지의 수 중에서 2의 배수만 console.log로 출력해보자
for (let i = 1; i <= 10; i++) {
    // i를 2로 나눈 나머지가 0이면
    if (i % 2 === 0) {
        console.log(i + '는 2의 배수입니다.');
    }
}

// for ~ in문
// 객체 속성을 출력하는 문법
let person8 = {
    name: 'Faker',
    age: 29,
    gender: 'male',
};

// person8[key]
for (let key in person8) {
    console.log(key + ': ' + person8[key]);
}

// [1-15] while, for
// (1) while
let i = 0;

while (i < 10) {
    console.log(i);
    i++;
}

// [과제] while문을 활용해서 3 초과 100 미만의 숫자 중, 5의 배수인 것만 출력해보자
i = 3;

while (i < 100) {
    if (i % 5 === 0 && i >= 5) {
        console.log(i + '는 5의 배수입니다.');
    }
    i++;
}

// (2) do ~ while
// 일단 한 번은 실행시켜 줌
i = 0;

do {
    console.log(i);
    i++;
} while (i < 10);

// (3) break continue
// [과제] 0부터 10까지 하나씩 늘어나는 for문 작성
for (let i = 0; i <= 10; i++) {
    if (i === 5) {
        break; // break를 만나면 로직을 깨트리고 밖으로 냅다 나가버림
    }
    console.log(i);
}

for (let i = 0; i <= 10; i++) {
    if (i === 5) {
        continue; // continue를 만나면 건너뛰고 다음 순서 실행함
    }
    console.log(i);
}

 

 

 

▼  2주차 들으며 작성한 코드

// [2-1, 2-2] ES6 문법 소개
// 구조분해할당 (destructuring)
// 배열 or 객체의 속성을 분해해서 변수에 각각 담을 수 있게 해주는 문법

// (1) 배열의 경우
let [value1, value2] = [1, 'new'];
console.log('1', value1); // 1 1
console.log('2', value2); // 2 new

let arr1 = ['value1', 'value2', 'value3', 'value4'];
// 여기서 쓴 d = 5는 초기값임. value4처럼 들어올 값이 있으면 초기값은 쓰이지 않음
let [a, b, c, d = 5] = arr1;

console.log(a); // value1
console.log(b); // value2
console.log(c); // value3
console.log(d); // 5

// (2) 객체인 경우
let user = {
    name: 'Faker',
    age: 29,
};

// let { name, age } = user;
// console.log(name); // Faker

// 새로운 이름으로 할당 (key의 이름을 갈아껴줌)
// let { name: newName, age: newAge } = user;
// console.log('newName => ', newName); // Faker

let { name, age, birthday = 'today' } = user;
console.log(name);
console.log(age);
console.log(birthday); // today

// 단축 속성명: property shorthand
// const name = 'sanghyuk';
// const age = '24';

// key - value pair
const obj = { name, age };
const obj1 = { name: name, age: age };

// 전개 구문 = spread operator
// destructuring과 함께 가장 많이 사용되는 ES6 문법 중 하나
let arr = [1, 2, 3];
console.log(arr); // [1, 2, 3]
console.log(...arr); // 1, 2, 3 - 대괄호가 없어지고 전개됨 ㅇㅇ

let newArr = [...arr, 4];
console.log(arr); // [ 1, 2, 3 ]
console.log(newArr); // [ 1, 2, 3, 4 ]

// 나머지 매개변수 (rest parameter)
function exampleFunc(a, b, c, ...args) {
    console.log(a, b, c);
    console.log(...args);
}

exampleFunc(1, 2, 3, 4, 5, 6, 7);

// 템플릿 리터럴(Template Literal)
const testValue = '안녕하세요!';
console.log(`Hello, World ${testValue}`);
console.log(`Hello,
            my name is JavaScript!`);

// [2-3, 2-4] 일급객체로서의 함수
// 일급객체: 다른 객체들에 '적용 가능한 연산을 모두 지원'하는 객체

// (1) 변수에 함수를 할당할 수 있다.
// 함수가 마치 '값'으로 취급된다.
// 함수가 '나중에 사용될 수 있도록' 되었다.
const sayHello1 = function () {
    console.log('Hello!');
};

// (2) 함수를 인자로 다른 함수에 전달할 수 있다.
// (2-1) 콜백함수: 매개변수로써 쓰이는 함수
// (2-2) 고차함수: 함수를 인자로 받거나 return 하는 함수 (콜백함수가 고차함수에 속하는 개념임)

function callFunction(func) {
    // 매개변수로 받은 변수가 사실은 함수다.
    func();
}

const sayHello = function () {
    console.log('Hello!');
};

callFunction(sayHello);

// (3) 함수를 반환할 수 있다.
function createAdder(num) {
    return function (x) {
        return x + num;
    };
}

const addFive = createAdder(5);
// const addFive = function (x) {
//        return x + 5;
console.log(addFive(10)); // 15

// 객체 요소로 함수를 할당
// this는 '자기 자신'을 가리킴
// 화살표 함수는 this를 사용할 수 없음
const person = {
    name: 'John',
    age: 31,
    isMarried: true,
    sayHello: function () {
        console.log('Hello, My name is ' + this.name);
        // console.log(`Hello, My name is ${this.name}`);
    },
};

person.sayHello();

// 배열 요소로 함수를 할당
const myArr = [
    function (a, b) {
        return a + b;
    }, // 0번째 요소
    function (a, b) {
        return a - b;
    }, // 1번째 요소
];

console.log(myArr[0](1, 3)); // 4
console.log(myArr[1](10, 7)); // 3

// 고차함수 예제
function multiplyBy(num) {
    return function (x) {
        return x * num;
    };
}

function add(x, y) {
    return x + y;
}

const multiplyByTwo = multiplyBy(2);
const multiplyByThree = multiplyBy(3);

console.log(multiplyByTwo(10)); // 20
console.log(multiplyByThree(10)); // 30

// 다른 예제
const result = add(multiplyByTwo(5), multiplyByThree(10));
console.log(result); // 40

// Map
// JS는 객체, 배열 - 다양하고 많은 복잡한 프로그램을 만들어왔는데, 그럼에도 여러 가지를 표현하기엔 어려웠음
// 그래서 등장한 게 Map, Set 같은 추가적인 자료구조임

// Map, Set의 목적: 데이터의 구성, 검색, 사용을 효율적으로 처리 ⇒ 기존의 객체 or 배열보다 효율적

// (1) Map
// Key / Value
// Key에 어떤 데이터 타입(유형)도 다 들어올 수 있음
// Map은 Key가 정렬된 순서로 저장되기 때문
// 기능: 검색, 삭제, 제거, 여부 확인

// 'set'과 'get'은 항상 '쌍'임
// const myMap = new Map();
// myMap.set('key', 'value');

// 나중에 get의 key로 검색함
// myMap.get('key');

// map을 쓰는 이유: 대량 데이터를 처리하려고
// '반복' 중요 ⇒ method: keys, values, entries

const myMap = new Map();
myMap.set('one', 1);
myMap.set('two', 2);
myMap.set('three', 3);

// Iterator: 반복자
console.log(myMap.keys()); // [Map Iterator] { 'one', 'two', 'three' }

// for ~ of
for (const key of myMap.keys()) {
    console.log(key); // one two three
}

console.log(myMap.values()); // [Map Iterator] { 1, 2, 3 }

for (const value of myMap.values()) {
    console.log(value); // 1 2 3
}

// entries는 key와 value를 배열로 묶어줌
// console.log(myMap.entries()); // [Map Entries] { [ 'one', 1 ], [ 'two', 2 ], [ 'three', 3 ] }

for (const entry of myMap.entries()) {
    console.log(entry); // [ 'one', 1 ] ['two', 2][('three', 3)];
}

console.log(myMap.size); // map의 사이즈(길이) // 3
console.log(myMap.has('two')); // key 기반 검색 // true

// [2-6] Set 소개 및 예시
// Set
// - 고유한 값을 저장하는 자료구조임
// - 값만 저장함
// - Key를 저장하지 않음
// - 값이 중복되지 않는 유일한 요소로만 구성됨
// [기능] 값 추가, 검색, 값 삭제, 모든 값 제거, 존재 여부 확인
const mySet = new Set();
mySet.add('value1');
mySet.add('value2');
mySet.add('value3');
mySet.add('value5');
mySet.add('value8');
// mySet.add('value2'); // 중복된 값은 데이터로 안 침

console.log(mySet.size); // 2
console.log(mySet.has('value1')); // true
console.log(mySet.has('value2')); // true
console.log(mySet.has('value3')); // false

// Iterator, 반복했던 그 친구
for (const value of mySet.values()) {
    console.log(value); // value1 value2 value3 value5 value8
}

 

 

 

▼  3주차 들으며 작성한 코드

// [3-1] 데이터 타입의 종류 (심화)
// 변수 = 데이터 | 식별자 = 변수명

// STEP01. 선언을 먼저 해보자
var a = 10; // 기본형
var obj1 = { c: 10, d: 'ddd' }; // 참조형

// STEP02. 복사를 수행해보자
var b = a; // 기본형
var obj2 = obj1; // 참조형

// 복사 이후 값 변경(객체의 프로퍼티 변경)
b = 15;
obj2.c = 20;

// [기본형]
//     - 숫자 15라는 값을 데이터 영역에서 검색 후, 없다면 생성
//     - 검색한 결과주소 또는 생성한 주소를 변수 영역 b에 갈아끼움
//     - a와 b는 서로 다른 데이터 영역의 주소를 바라보고 있기 때문에 **영향 없음**
// 따라서, 기본형 변수 복사의 결과는 다른 값!
a !== b;

// [참조형]
//     - 숫자 20이라는 값을 데이터 영역에서 검색 후 없다면 생성
//     - 검색한 결과주소 또는 생성한 주소 obj2에게 지정되어 있는 별도 영역에 갈아끼움
//     - obj1도 똑같은 주소를 바라보고 있기 때문에 **obj1까지 변경됨**
// 따라서, 참조형 변수 복사의 결과는 같은 값!
obj1 === obj2;

// 복사 이후 값 변경(객체 자체를 변경)
// 기본형 데이터
var a = 10;
var b = a;

// 참조형 데이터
var obj1 = { c: 10, d: 'ddd' };
var obj2 = obj1;

b = 15;
obj2 = { c: 20, d: 'ddd' };

// 불변 객체의 필요성
// 객체의 가변성에 따른 문제점
// user 객체를 생성
var user = {
    name: 'wonjang',
    gender: 'male',
};

// 이름을 변경하는 함수, 'changeName'을 정의
// 입력값 : 변경 대상 user 객체, 변경하고자 하는 이름
// 출력값 : 새로운 user 객체
// 특징 : 객체의 프로퍼티(속성)에 접근해서 이름을 변경함 → 가변
var changeName = function (user, newName) {
    var newUser = user;
    newUser.name = newName;
    return newUser;
};

// 변경한 user정보를 user2 변수에 할당함
// 가변이기 때문에 user1도 영향을 받게 될 것
var user2 = changeName(user, 'twojang');

// 결국 아래 로직은 skip하게 될 것
if (user !== user2) {
    console.log('유저 정보가 변경되었습니다.');
}

console.log(user.name, user2.name); // twojang twojang
console.log(user === user2); // true

// 위 예제는 아래와 같이 개선될 수 있음
// user 객체를 생성
var user = {
    name: 'wonjang',
    gender: 'male',
};

// 이름을 변경하는 함수 정의
// 입력값 : 변경 대상 user 객체, 변경하고자 하는 이름
// 출력값 : 새로운 user 객체
// 특징 : 객체의 프로퍼티에 접근하는 것이 아니라, 아에 새로운 객체를 반환 -> 불변
var changeName = function (user, newName) {
    return {
        name: newName,
        gender: user.gender,
    };
};

// 변경한 user 정보를 user2 변수에 할당함
// 불변이기 때문에 user는 영향이 없음
var user2 = changeName(user, 'twojang');

// 결국 아래 로직이 수행됨
if (user !== user2) {
    console.log('유저 정보가 변경되었습니다.');
}

console.log(user.name, user2.name); // wonjang twojang
console.log(user === user2); // false 👍

// 더 나은 방법 : 얕은 복사
// 패턴과 적용
var copyObject = function (target) {
    var result = {};

    // for ~ in 구문을 이용하여, 객체의 모든 프로퍼티에 접근할 수 있음
    // 하드코딩을 하지 않을 수 있어짐
    // 이 copyObject로 복사를 한 다음, 복사를 완료한 객체의 프로퍼티를 변경하면 됨
    for (var prop in target) {
        result[prop] = target[prop];
    }
    return result;
};

// 위 패턴을 우리 예제에 적용해보자
var user = {
    name: 'wonjang',
    gender: 'male',
};

var user2 = copyObject(user);
user2.name = 'twojang';

if (user !== user2) {
    console.log('유저 정보가 변경되었습니다.');
}

console.log(user.name, user2.name);
console.log(user === user2);

// 중첩된 객체에 대한 얕은 복사 살펴보기
var user = {
    name: 'wonjang',
    urls: {
        portfolio: 'http://github.com/abc',
        blog: 'http://blog.com',
        facebook: 'http://facebook.com/abc',
    },
};

var user2 = copyObject(user);
user2.name = 'twojang';

// 바로 아래 단계에 대해서는 불변성을 유지하기 때문에 값이 달라짐
console.log(user.name === user2.name); // false

// 더 깊은 단계에 대해서는 불변성을 유지하지 못하기 때문에 값이 같음
user.urls.portfolio = 'http://portfolio.com';
console.log(user.urls.portfolio === user2.urls.portfolio); // true

// 아래 예도 똑같음
user2.urls.blog = '';
console.log(user.urls.blog === user2.urls.blog); // true

// 중첩된 객체에 대한 깊은 복사 살펴보기
var user = {
    name: 'wonjang',
    urls: {
        portfolio: 'http://github.com/abc',
        blog: 'http://blog.com',
        facebook: 'http://facebook.com/abc',
    },
};

// 1차 copy
var user2 = copyObject(user);

// 2차 copy -> 이렇게까지 해줘야만 함
user2.urls = copyObject(user.urls);

user.urls.portfolio = 'http://portfolio.com';
console.log(user.urls.portfolio === user2.urls.portfolio);

user2.urls.blog = '';
console.log(user.urls.blog === user2.urls.blog);

// 재귀적 수행
// ⇒ 함수나 알고리즘이 자기 자신을 호출하여 반복적으로 실행되는 것
var copyObjectDeep = function (target) {
    var result = {};
    if (typeof target === 'object' && target !== null) {
        for (var prop in target) {
            result[prop] = copyObjectDeep(target[prop]);
        }
    } else {
        result = target;
    }
    return result;
};

// 아래와 같이 하면 '깊은 복사'를 완벽히 구현할 수 있음
// 결과 확인
var obj = {
    a: 1,
    b: {
        c: null,
        d: [1, 2],
    },
};

var obj2 = copyObjectDeep(obj);

obj2.a = 3;
obj2.b.c = 4;
obj2.b.d[1] = 3;

console.log(obj);
console.log('obj2 ⇒ ', obj2);

// undefined와 null
// (1) undefined
var a;
console.log(a); // (1) 값을 대입하지 않은 변수에 접근

var obj = { a: 1 };
console.log(obj.a); // 1
console.log(obj.b); // (2) 존재하지 않는 property에 접근
// console.log(b); // 오류 발생

var func = function () {};
var c = func(); // (3) 반환 값이 없는 function
console.log(c); // undefined

// (2) null
var n = null;
console.log(typeof n); // object

//동등연산자(equality operator)
console.log(n == undefined); // true
console.log(n == null); // true

//일치연산자(identity operator)
console.log(n === undefined);
console.log(n === null);

// 실행컨텍스트 구성 예시
// ---- 1번
var a = 1;
function outer() {
    function inner() {
        console.log(a); // undefined
        var a = 3;
    }
    inner(); // ---- 2번
    console.log(a);
}
outer(); // ---- 3번
console.log(a);

// 함수 선언문 & 함수 표현식
// 함수 선언문. 함수명 a가 곧 변수명
// function 정의부만 존재, 할당 명령이 없는 경우
// function a() {
//     /* ... */
// }
// a(); // 실행 ok

// 함수 표현식. 정의한 function을 별도 변수에 할당하는 경우
// (1) 익명함수표현식 : 변수명 b가 곧 변수명(일반적 case에요)
// var b = function () {
//     /* ... */
// };
// b(); // 실행 ok

// (2) 기명 함수 표현식 : 변수명은 c, 함수명은 d
// d()는 c() 안에서 재귀적으로 호출될 때만 사용 가능하므로 사용성에 대한 의문
// var c = function d() {
//     /* ... */
// };
// c(); // 실행 ok
// d(); // 에러!

// this
// CASE 1 : 함수
// 호출 주체를 명시할 수 없기 때문에 this는 전역 객체를 의미
var func = function (x) {
    console.log(this, x);
};
func(1); // Window { ... } 1

// CASE 2 : 메서드
// 호출 주체를 명시할 수 있기 때문에 this는 해당 객체(obj)를 의미
// obj는 곧 { method: f }를 의미함
var obj = {
    method: func,
};
obj.method(2); // { method: f } 2

// 함수로서의 호출과 메서드로서의 호출 구분 기준 : . []
// 점(.)으로 호출하든, 대괄호([])로 호출하든 결과는 같음
var obj = {
    method: function (x) {
        console.log(this, x);
    },
};
obj.method(1); // { method: f } 1
obj['method'](2); // { method: f } 2

// 메서드의 내부함수에서의 this
var obj1 = {
    outer: function () {
        console.log(this); // (1)
        var innerFunc = function () {
            console.log(this); // (2), (3)
        };
        innerFunc();

        var obj2 = {
            innerMethod: innerFunc,
        };
        obj2.innerMethod();
    },
};
obj1.outer();

// call, apply 메서드 활용
// 유사배열객체(array-like-object)에 배열 메서드를 적용

// 객체에는 배열 메서드를 직접 적용할 수 없음
// 유사배열객체에는 call 또는 apply 메서드를 이용해 배열 메서드를 차용할 수 있음
var obj = {
    0: 'a',
    1: 'b',
    2: 'c',
    length: 3,
};
Array.prototype.push.call(obj, 'd');
console.log(obj); // { 0: 'a', 1: 'b', 2: 'c', 3: 'd', length: 4 }

var arr = Array.prototype.slice.call(obj);
console.log(arr); // [ 'a', 'b', 'c', 'd' ]

// Array.from 메서드(ES6)
// 유사배열
var obj = {
    0: 'a',
    1: 'b',
    2: 'c',
    length: 3,
};

// 객체 → 배열
var arr = Array.from(obj);

// 찍어보면 배열이 출력됨
console.log('arr => ', arr); // [ 'a', 'b', 'c' ]

// 생성자 내부에서 다른 생성자를 호출(공통된 내용의 반복 제거)
function Person(name, gender) {
    this.name = name;
    this.gender = gender;
}
function Student(name, gender, school) {
    Person.call(this, name, gender); // 여기서 this는 student 인스턴스!
    this.school = school;
}
function Employee(name, gender, company) {
    Person.apply(this, [name, gender]); // 여기서 this는 employee 인스턴스!
    this.company = company;
}
var kd = new Student('길동', 'male', '서울대');
var ks = new Employee('길순', 'female', '삼성');

// apply 적용 예시
// 효율
var numbers = [10, 20, 3, 16, 45];
var max = Math.max.apply(null, numbers);
var min = Math.min.apply(null, numbers);
console.log(max, min);

// 펼치기 연산자(Spread Operation)를 통하면 더 간편하게 해결도 가능
var numbers = [10, 20, 3, 16, 45];
var max = Math.max(...numbers);
var min = Math.min(...numbers);
console.log(max, min);

// bind 메서드
var func = function (a, b, c, d) {
    console.log(this, a, b, c, d);
};

func(1, 2, 3, 4); // window 객체

// 함수에 this 미리 적용
var bindFunc1 = func.bind({ x: 1 }); // 바로 호출되지는 않음! 그 외에는 같음
bindFunc1(5, 6, 7, 8); // { x: 1 } 5 6 7 8

// 부분 적용 함수 구현
var bindFunc2 = func.bind({ x: 1 }, 4, 5); // 4와 5를 미리 적용
bindFunc2(6, 7); // { x: 1 } 4 5 6 7
bindFunc2(8, 9); // { x: 1 } 4 5 8 9

 

 

 

▼  4주차 들으며 작성한 코드

// [4-1] 콜백함수 - 기본 개념
// : 다른 코드의 인자로 넘겨주는 함수

// 콜백함수야 제어권을 넘겨줄 테니 너가 알고 있는 그 로직으로 처리해줘
// 인자로 넘겨줌으로써 제어권도 함께 위임한 함수
// 콜백함수는 자체적으로 내부 로직에 의해 적절한 시점에 실행함
// ex) forEach, setTimeout

setTimeout(function () {
    console.log('Hello');
}, 1000); // 1초 뒤에 수행

const numbers = [1, 2, 3, 4, 5];

numbers.forEach(function (number) {
    console.log(number);
});

// [4-2, 4-3] 콜백함수의 제어권
// 1. '호출 시점'에 대한 제어권 가짐
// setInterval: 반복해서 매개변수로 받은 콜백함수의 로직을 수행함
var count = 0;
var cbFunc = function () {
    console.log(count);
    if (++count > 4) clearInterval(timer);
};

var timer = setInterval(cbFunc, 300); // 0.3초

// 2. '인자'에 대한 제어권 가짐
// map 함수: currentValue, index를 인자로 받음 [몇 번째인지 순서가 중요함. 그걸로 map 돌릴 것과 인덱스를 인식함]
var newArr = [10, 20, 30].map(function (currentValue, index) {
    console.log(currentValue, index); // 10 0 20 1 30 2
    return currentValue + 5;
});

console.log(newArr); // [ 15, 25, 35 ]

// this
// [예외사항] 제어권을 넘겨받을 코드에서 콜백함수에 별도로 this가 될 대상을 지정한 경우엔 그 대상을 참조함
Array.prototype.map123 = function (callback, thisArg) {
    var mappedArr = [];

    for (var i = 0; i < this.length; i++) {
        var mappedValue = callback.call(thisArg || global, this[i]);
        mappedArr[i] = mappedValue;
    }
    return mappedArr;
};

var newArr = [1, 2, 3].map123(function (number) {
    return number * 2;
});

console.log(newArr);

// obj
// 2가지 속성
var obj = {
    vals: [1, 2, 3],
    logValues: function (v, i) {
        if (this === global) {
            console.log('this가 global입니다. 원하지 않는 결과!');
        } else {
            console.log('test =>', this, v, i); // { vals: [ 1, 2, 3 ], logValues: [Function: logValues] } 1 2
        }
    },
};

// method로써 호출
obj.logValues(1, 2);

// forEach, map, filter
// 첫 번째 인자: 기준이 되는 배열의 n번째 요소, 그 요소의 index
[4, 5, 6].forEach(obj.logValues);

// [콜백함수 내부의 this에 다른 값 바인딩하기]
// 콜백함수 내부의 this가 문맥에 맞는 객체를 바라보게 할 수는 없을까?
// 콜백함수 내부의 this에 다른 값을 바인딩하는 방법

// closure 방식
var obj1 = {
    name: 'obj1',
    func: function () {
        var self = this; // 이 부분!
        return function () {
            console.log(self.name); // obj1
        };
    },
};

var obj2 = {
    name: 'obj2',
    func: obj1.func,
};

var callback = obj2.func();
setTimeout(callback, 1000);

// this를 없애도 되잖아! 해서 나온 게 아래 로직
// 근데 완전 하드코딩임. 지양해야 할 코드
var obj1 = {
    name: 'obj1',
    func: function () {
        console.log(obj1.name); // obj1
    },
};

setTimeout(callback, 1000);

// bind
var obj1 = {
    name: 'obj1',
    func: function () {
        console.log(this.name);
    },
};

var boundObj1 = obj1.func.bind(obj1);
setTimeout(boundObj1, 1000);

var obj2 = { name: 'obj2' };
setTimeout(obj1.func.bind(obj2), 1500);

// [4-7] 콜백지옥
// : 콜백함수로 전달하는 과정이 반복돼, 코드의 들여쓰기 수준이 헬인 수준
// 주로 이벤트 처리, 서버 통신과 같은 비동기적 작업을 수행할 때 발생
// [문제] 가독성, 유지보수 최악

// 동기 vs. 비동기
// 동기 (sync)
// 현재 실행 중인 코드가 끝나야 다음 코드를 실행하는 방식

// 비동기 (async)
// 실행 중인 코드의 완료 여부와 무관하게 즉시 다음 코드로 넘어가는 방식
// setTimeout, addEventListener
// 별도의 요청, 실행 대기, 보류와 관련된 코드는 모두 비동기적 코드!
// 웹의 복잡도가 올라갈수록 비동기적 코드의 비중이 늘어난다.

// [4-8] 콜백함수 - 비동기 작업의 동기적 표현! Promise
// Promise: 비동기 처리가 끝나면 알려달라는 '약속'
// new 연산자로 호출한 Promise의 콜백은 바로 실행됨

// resolve: 성공했을 때, reject: 실패했을 때
// 위 성공/실패 둘 중 하나가 실행되기 전까진 then(그러면), catch(오류)로 넘어가지 않음
// 비동기 작업이 완료될 때, resolve 혹은 reject를 호출함

new Promise(function (resolve) {
    setTimeout(function () {
        var name = '에스프레소';
        console.log(name);
        resolve(name);
    }, 500);
})
    .then(function (prevName) {
        // 이 안에서 새롭게 Promise를 만들자
        return new Promise(function (resolve) {
            var name = prevName + ', 아메리카노';
            console.log(name);
            resolve(name);
        }, 500);
    })
    .then(function (prevName) {
        // 이 안에서 새롭게 Promise를 만들자
        return new Promise(function (resolve) {
            var name = prevName + ', 카페모카';
            console.log(name);
            resolve(name);
        }, 500);
    })
    .then(function (prevName) {
        // 이 안에서 새롭게 Promise를 만들자
        return new Promise(function (resolve) {
            var name = prevName + ', 카페라떼';
            console.log(name);
            resolve(name);
        }, 500);
    });

// [4-9] 콜백함수 - Promise refactoring
// refactoring: 비효율적인 코드를 효율적이게 수정하는 것
var addCoffee = function (name) {
    return function (prevName) {
        // 이 안에서 새롭게 Promise를 만들자
        return new Promise(function (resolve) {
            // 백틱
            var newName = prevName ? `${prevName}, ${name}` : name;
            console.log(newName);
            resolve(newName);
        }, 500);
    };
};

addCoffee('에스프레소')().then(addCoffee('아메리카노')).then(addCoffee('카페모카')).then(addCoffee('카페라떼'));

// [4-10] 비동기 작업의 동기적 표현 - Generator
// : 반복할 수 있는 iterator 객체를 생성한다
// iterator: 반복된다

// 비동기 작업이 완료되는 시점마다 next 메서드를 호출해주면 Generator 함수 내부소스가 위 → 아래 순차적으로 진행됨
// (1) 제너레이터 함수 안에서 쓸 addCoffee 함수 선언
var addCoffee = function (prevName, name) {
    setTimeout(function () {
        coffeeMaker.next(prevName ? `${prevName}, ${name}` : name);
    }, 500);
};

// *이 붙은 함수가 제너레이터 함수임
// 이 함수를 실행하면 Iterator 객체가 반환됨
// yield를 써주면 stop을 걸어주는 거임

// (2) 제너레이터 함수 선언
// yield 키워드로 순서 제어
var coffeeGenerator = function* () {
    var espresso = yield addCoffee('', '에스프레소');
    console.log(espresso);
    var americano = yield addCoffee(espresso, '아메리카노');
    console.log(americano);
    var mocha = yield addCoffee(americano, '카페모카');
    console.log(mocha);
    var latte = yield addCoffee(mocha, '카페라떼');
    console.log(latte);
};

var coffeeMaker = coffeeGenerator();
coffeeMaker.next();

// 비동기 작업의 동기적 표현(4) - Promise + Async/await
// async 함수 내부에서 비동기 작업이 필요한 위치마다 await(기다리다)를 붙여주면 됨
// Promise ~ then과 동일한 효과를 얻을 수 있음

// (1) coffeeMaker 함수에서 호출할 함수, 'addCoffee'를 선언
// Promise를 반환
var addCoffee = function (name) {
    return new Promise(function (resolve) {
        setTimeout(function () {
            resolve(name);
        }, 500);
    });
};

// [중요]
var coffeeMaker = async function () {
    var coffeeList = '';
    var _addCoffee = async function (name) {
        coffeeList += (coffeeList ? ', ' : '') + (await addCoffee(name));
    };
    // await를 만난 메서드는 앞 로직이 끝날 때까지 무조건 기다리게 돼있음
    // await 뒤 메서드는 항상 Promise를 반환해야 함
    await _addCoffee('에스프레소');
    console.log(coffeeList);
    await _addCoffee('아메리카노');
    console.log(coffeeList);
    await _addCoffee('카페모카');
    console.log(coffeeList);
    await _addCoffee('카페라떼');
    console.log(coffeeList);
};
coffeeMaker();

 

 

▼  5주차 들으며 작성한 코드

// [5-3] 클래스 소개
// 클래스라는 설계도를 만들어 보자
class Person {
    // name, age
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }

    // 메서드 형태의 동사 표현
    sayHello() {
        console.log(`${this.name}님 안녕하세요!`);
    }

    // [과제] "내 나이는 ~~살이에요!"라고 출력하는 메서드를 만들어보자
    sayAge() {
        console.log(`${this.name}님은 ${this.age}살이에요.`);
    }
}

// 설계도를 통해 인스턴스(실제 사물)를 만들어 보자
// 이름은 이상혁이고, 나이는 28살인 사람 한 명을 만들어줘. 설계도에 근거해서
const person1 = new Person('이상혁', '28');
const person2 = new Person('대상혁', '29');

person1.sayHello();
person1.sayAge();
person2.sayHello();
person2.sayAge();

// [5-4] 클래스 생성 연습
// [과제 요구사항] 1) Car라는 새로운 클래스를 만들되, 처음 객체를 만들 땐 다음 4개의 값이 필수로 입력돼야 한다.
// (1) modelName
// (2) modelYear
// (3) type
// (4) price

class Car {
    constructor(modelName, modelYear, type, price) {
        (this._modelName = modelName), (this._modelYear = modelYear), (this._type = type), (this._price = price);
    }
    // 2) makeNoise() 메서드를 만들어 클락션을 출력해보자.
    makeNoise() {
        console.log(`${this._modelName}, 빵빵!`);
    }
    // 2-1) 해당 자동차가 몇 년도 모델인지 출력해보자
    printYear() {
        console.log(`${this._modelName}는 ${this._modelYear} 모델입니다.`);
    }

    // [5-6] 추가 요구사항
    // 1. modelName, modelYear, price, type을 가져오는 메서드
    // 2. 1을 세팅하는 메서드
    // 3. 만든 인스턴스를 통해서 마지막에 set 해서 get 하는 로직까지
    get modelName() {
        return this._modelName;
    }

    // 입력값에 대한 검증까지 가능하다
    set modelName(value) {
        // 유효성 검사: 잘못된 데이터가 들어오지 않도록 하는 검증 절차
        // 검증 1: value가 음수면 오류
        if (value.length <= 0) {
            console.log('[오류] 모델명이 입력되지 않았습니다.');
            return;
        } else if (typeof value !== 'string') {
            console.log('[오류] 입력된 모델명이 문자형이 아닙니다.');
            return;
        }
        // 검증이 완료된 경우에만 setting
        this._modelName = value;
    }

    get modelYear() {
        return this._modelYear;
    }

    set modelYear(value) {
        // 유효성 검사
        if (value.length !== 4) {
            console.log('[오류] 입력된 년도가 4자리가 아닙니다.');
            return;
        } else if (typeof value !== 'string') {
            console.log('[오류] 입력된 연도가 문자형이 아닙니다.');
            return;
        }
        // 검증이 완료된 경우에만 setting
        this._modelYear = value;
    }

    get price() {
        return this._price;
    }

    set price(value) {
        if (typeof value !== 'number') {
            console.log('[오류] 가격으로 입력된 값이 숫자가 아닙니다.');
            return;
        } else if (value < '1000000') {
            console.log('[오류] 가격은 100만원보다 작을 수 없습니다. 확인해주세요.');
        }
        // 검증이 완료된 경우
        this._price = value;
    }

    get type() {
        return this._type;
    }

    set type(value) {
        // 유효성 검사
        if (value.length !== 0) {
            console.log('[오류] 타입이 입력되지 않았습니다.');
            return;
        } else if (value !== 'g' && value !== 'd' && value !== 'e') {
            // g(가솔린), d(디젤), e(전기차)가 아닌 경우 오류
            console.log('[오류] 입력된 타입이 잘못되었습니다.');
            return;
        }
        // 검증이 완료된 경우에만 setting
        this._type = value;
    }
}

// 3) 이후 자동차를 3개 정도 만들어보자 (객체 생성)
const car1 = new Car('제네시스', '2024년', 'd', 50000000);
const car2 = new Car('BMW', '2021년', 'd', 70000000);
const car3 = new Car('테슬라', '2023년', 'e', 60000000);

// getter 예시 1
console.log(car1.modelName);

// setter 예시 1
car1.modelName = '기아자동차';
console.log(car1.modelName);

// car1.makeNoise();
// car1.printYear();

// car2.makeNoise();
// car2.printYear();

// car3.makeNoise();
// car3.printYear();

// [5-5] Getters와 Setters
// 객체 지향 프로그래밍 언어에서는 Getters와 Setters에 대한 기본적인 개념이 있음
// Class를 통해서 객체(인스턴스) 만듦
// 프로퍼티(constructor)에서 정의한 필수 입력값들을 정해서 new Class 줌 ⇒ new Class(a, b, c)

// 직사각형을 만들어 보자
class Rectangle {
    constructor(height, width) {
        // underscore: private(은밀하고, 감춰야 할 때)
        // _: '이 인스턴스 내에서만 사용하겠다' 변수명을 똑같이 써버리면 겹치니까
        this._height = height;
        this._width = width;
    }

    // width를 위한 getter
    get width() {
        return this._width;
    }

    // height 위한 setter
    set width(value) {
        // 검증 1: value가 음수면 오류
        if (value <= 0) {
            console.log('[오류] 가로 길이는 0보다 커야 합니다.');
            return;
        } else if (typeof value !== 'number') {
            console.log('[오류] 가로 길이로 입력된 값이 숫자 타입이 아닙니다.');
            return;
        }
        this._width = value;
    }

    // width를 위한 getter
    get height() {
        return this._height;
    }

    // width를 위한 getter
    set height(value) {
        return this._value;
    }

    // getArea: 가로 * 세로 ⇒ 넓이
    getArea() {
        const area = this._width * this._height;
        console.log(`넓이는 ${area}입니다.`);
    }
}

// Instance 생성
const rect1 = new Rectangle(10, 7);
const rect2 = new Rectangle(10, 30);
const rect3 = new Rectangle(15, 20);

rect1.getArea();

// [5-7] 클래스 상속(Inheritance)
// Class → 유산으로 내려주는 주요 기능
// 부모 ↔ 자식

// 동물 전체에 대한 클래스
class Animal {
    // 생성자
    constructor(name) {
        this.name = name;
    }

    // 메서드(말하다)
    speak() {
        console.log(`${this.name} says!`);
    }
}

// extends: 객체 지향 언어에서 공통적으로 사용하는 키워드
class Dog extends Animal {
    // overring: 부모에게서 내려받은 메서드를 재정의 가능
    speak() {
        console.log(`${this.name} barks!`);
    }
}

const cuttyPuppy = new Dog('복실이');
cuttyPuppy.speak();

// 정적 메서드 (Static Method)
// Class: 객체를 만들기 위해 사용
// 다량으로, 안전하고, 정확하게
class Calculator {
    // static을 사용하면 .add 로 바로 접근 가능
    static add(a, b) {
        console.log('[계산기] 더하기를 진행합니다');
        return a + b;
    }

    static substract(a, b) {
        console.log('[계산기] 빼기를 진행합니다');
        return a - b;
    }
}

console.log(Calculator.add(3, 5)); // 8
console.log(Calculator.substract(3, 5)); // -2

// [5-9, 5-10] Closure (클로저)
// : 클로저는 함수와 그 함수가 선언된 렉시컬 환경과의 조합
const x = 1;

function outerFunc() {
    const x = 10;
    function innerFunc() {
        // 함수가 선언된 렉시컬 환경: 함수가 선언될 당시의 외부 변수 등의 정보
        // Scope Chaining이란 호출된 함수에서 밖으로 한 단계, 한 단계 나가는 것
        console.log(x); // 10
    }
    innerFunc();
}

outerFunc();

// [렉시컬 스코프]
// JS 엔진은 함수를 어디서 '호출'했는지가 아니라 어디에 '정의'했는지에 따라서 스코프(or 상위 스코프)를 결정함
// '외부 렉시컬 환경에 대한 참조값': outer (함수 정의가 평가되는 시점)

const xx = 1;

// outerFunc1에 innnerFunc1이 '호출'되고 있음에도 불구하고
function outerFunc1() {
    const x = 10;
    innerFunc1(); // 1
}

// innerFunc1과 outerFunc1은 서로 다른 Scope를 가지고 있음
function innerFunc1() {
    console.log(x); // 1
}

outerFunc();

// 클로저와 렉시컬 환경
// 외부 함수보다 중첩 함수가 더 오래 유지되는 경우, 중첩 함수는 생명주기가 종료한 외부 함수의 변수를
// ***여전히*** 참조할 수 있음

const xxx = 1;

function outer() {
    const x = 50;
    const inner = function () {
        console.log(x);
    };
    return inner;
}

// outer 함수를 '실행'해서 innerFunc2에 담음
// outer 함수의 return 부분을 innerFunc2에 담는다는 것
const innerFunc2 = outer();
// ====== 여기서 outer 함수의 실행 컨텍스트는 이미 날라감

innerFunc2();

// 카운트 상태 변경 함수 #1
// 함수가 호출될 때마다 호출된 횟수를 누적하여 출력하는 카운터를 구현해보자

// 카운터 상태 변수
let num = 0;

// 카운터 상태 변경 함수
const increase = function () {
    // 카운터 상태를 1만큼 증가시킴
    return ++num;
};

console.log(increase());
num = 100; // 중간에 접근해서 초기값을 맘대로 바꿀 수 있단 치명적인 단점이 있음
console.log(increase());
console.log(increase());

// 보완해야 할 사항
// 1. 카운트 상태 (num 변수의 값)
// ⇒ increase 함수가 호출되기 전까진 변경되지 않음
// 2. 이를 위해서 count 상태는 increase 함수만이 변경
// 3. 전역변수 num이 문제인 듯 ⇒ 지역변수로 만들까?

// 카운터 상태 변경 함수 #2
const increase2 = (function () {
    // 카운트 상태 변수
    let num = 0;

    // 클로저
    return function () {
        return ++num;
    };
})();

// 이전 상태값을 유지
console.log(increase2()); // 1
console.log(increase2()); // 2
console.log(increase2()); // 3

// [코드 설명]
// 1. 위 코드가 실행되면 '즉시실행함수'가 호출됨 → 함수가 반환됨 → increase에 할당됨
// 2. increase 변수에 할당된 함수는 자신이 정의된 위치에 의해서 결정된 상위 스코프인 즉시실행함수의 '렉시컬 환경'을 기억하는
//    클로저 ⇒ let num = 0; 을 기억함
// 3. 즉시실행함수는 즉시 소멸됨 (outer 함수가 불리자마자 바로 call stack에서 popup 되는 거랑 비슷)

// **결론** num은 초기화되지 않음. 외부에서 접근할 수 없는 은닉된 값임. 의도되지 않은 변경도 걱정할 필요 없음
// ⇒ increase에서만 변경할 수 있기 때문

 

 

 

5. 금일 소감

휴 JS 문법 종합반 강의가 10시간이 넘는데, 전부 타이핑하며 들으니 생각보다 오래 걸렸다.

 

그래도 다시 들으니까 감이 안 잡히던 부분이 많이 이해가 되었다. 지금까지는 팀 프로젝트 시기에 매번 7~10일 정도 팀원들 몫을 대신하고 신경써야 했어서 내 부족한 공부를 못했는데 이번 연휴를 통해서 보충할 수 있어서 너무 다행이라는 생각이 들었다. 아직 React 강의와 React Query 강의도 보충해서 들어야 하긴 하지만.. 오늘 낮과 저녁 시간 활용해서 전부 끝내도록 해야겠다.

 

이제 어느 정도 감이 잡혀서 앞으로는 코드를 많이 작성하고, 문제도 풀고, 그간 해온 팀 프로젝트 코드도 보면서 공부를 틈틈이 해야겠다. 오늘부터는 TypeScript 강의도 듣게 되는데, 오늘 안에 React 강의까지 얼른 끝내야겠다.

 

728x90