당니의 개발자 스토리

12/27 본문

LG CNS/필기노트

12/27

clainy 2025. 1. 8. 04:02

1교시

 

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <h1>JavaScript</h1>
    <script>
        // 팩토얼을 계산하는 함수를 정의

        // 방법1. 반복문을 이용
        function factorial_loop(n) {
            let result = 1;
            for (let i = 1; i <= n; i++) {
                result *= i;
            }
            return result;
        }

        // 방법2. 재귀함수를 이용
        function factorial_recursive(n) {
            if (n == 1)
                return 1;

            return n * factorial_recursive(n - 1);
        }

        console.log(factorial_loop(10));
        console.log(factorial_recursive(10));

        // 함수 실행에 소요된 시간을 측정
        // 방법1. 함수 내부의 시작과 끝에 시간을 측정하는 코드를 추가
        //         => 루트 방식은 구현이 가능한데, 재귀함수는 구현이 불가(어려움)
        // 방법2. 함수를 호출하는 부분에 시간을 측정하는 코드를 추가
        //         => 루프 방식, 재귀함수 모두 구현이 가능하다

        let start = performance.now();
        console.log(factorial_loop(10));
        let end = performance.now();
        console.log(`실행시간: ${end - start}ms`);
        console.log(`실행시간: ${(end - start).toFixed(5)}ms`);
    </script>
</body>

</html>

 

performance.now()는 현재 시간을 밀리초 단위로 측정하는 함

console.log(실행시간: ${(end - start).toFixed(5)}ms);

  • 이 부분은 **(end - start)**의 결과를 소수점 5자리까지 표시하도록 포맷합니다.
  • **.toFixed(5)**는 숫자를 소수점 이하 5자리까지 표시하는 방법입니다.
  • 이렇게 하면 실행 시간을 더 정확하게 볼 수 있습니다.

 

  • <meta charset="UTF-8">:
    • 이 태그는 HTML 문서가 UTF-8 문자 인코딩을 사용한다고 브라우저에 알려줍니다. UTF-8은 다양한 문자를 처리할 수 있는 표준 문자 인코딩 방식입니다. 이를 통해 특수 문자나 한글 등 다양한 문자가 제대로 표시됩니다.
  • <meta name="viewport" content="width=device-width, initial-scale=1.0">:
    • 이 메타 태그는 반응형 웹 디자인을 위한 설정입니다. 모바일 장치에서 웹 페이지가 제대로 표시되도록 돕습니다.
    • width=device-width는 화면 너비를 디바이스 화면 너비에 맞추도록 설정합니다.
    • initial-scale=1.0은 페이지 로딩 시 화면의 확대 비율을 1배로 설정한다는 의미입니다. 즉, 페이지가 처음 로드될 때 확대/축소되지 않도록 합니다.
  • <title>Document</title>:
    • <title> 태그는 브라우저의 에 표시되는 페이지의 제목을 정의합니다. 여기서는 "Document"라는 제목이 표시됩니다.

 

<body> 태그는 실제로 웹 페이지에서 사용자에게 보여지는 콘텐츠를 포함하는 부분입니다. 즉, 페이지에서 볼 수 있는 텍스트, 이미지, 비디오, 버튼 등 모든 요소들이 <body> 태그 내에 들어갑니다.

 

이 코드는 문제가 있음

시간을 측정하고, 계산하고, 출력하는 부분이 중복되어 있음 

 

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <h1>JavaScript</h1>
    <script>
        // 팩토리얼을 계산하는 함수를 정의

        // 방법1. 반복문을 이용
        function factorial_loop(n) {
            let result = 1;
            for (let i = 1; i <= n; i++) {
                result *= i;
            }
            return result;
        }

        // 방법2. 재귀함수를 이용
        function factorial_recursive(n) {
            if (n == 1)
                return 1;

            return n * factorial_recursive(n - 1);
        }

        console.log(factorial_loop(10));
        console.log(factorial_recursive(10));

        // 함수 실행에 소요된 시간을 측정
        // 방법1. 함수 내부의 시작과 끝에 시간을 측정하는 코드를 추가
        //         => 루트 방식은 구현이 가능한데, 재귀함수는 구현이 불가(어려움)
        // 방법2. 함수를 호출하는 부분에 시간을 측정하는 코드를 추가
        //         => 루프 방식, 재귀함수 모두 구현이 가능하다

        let start = performance.now();
        console.log(factorial_loop(10));
        let end = performance.now();
        console.log(`실행시간: ${end - start}ms`);
        console.log(`실행시간: ${(end - start).toFixed(5)}ms`);

        // 함수를 반환하는 함수를 이용해서 실행 시간을 측정
        function performance_checker(func) {
            return function(n) {
                let start = performance.now();
                console.log(func(n));
                let end = performance.now();
                console.log(`실행시간: ${(end - start)}ms`);
            }
        }

        performance_checker(factorial_loop)(10);
        performance_checker(factorial_recursive)(10);


    </script>
</body>

</html>

기존 함수(factorial_loop, factorial_recursive)의 동작을 확장하거나 새로운 동작을 정의 (기존 함수를 변경하지 않고)

 

performance_checker(factorial_loop)(10)가 익숙하지 않은 이유는 함수 호출을 두 번 연속으로 하는 방식이라서 처음에 조금 낯설 수 있습니다. 이를 "커링(Currying)"이라고 부르기도 합니다.

 

 

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <h1>JavaScript</h1>
    <script>

        // 함수 실행에 필요한 인자값과 결과값을 출력하는 함수
        const add = (a, b) => a + b;  // 두 숫자를 더하는 함수
        add(10, 20);  // add 함수 호출 (결과값은 30)

        const sub = (a, b) => a - b;  // 두 숫자를 빼는 함수

        function logger(func) {          // 고차 함수: func 함수를 인자로 받아서 새로운 함수를 반환
            return function (...args) {  // 반환된 함수는 'func'에 넘길 인자들을 받는다.
                console.log(`인자값: ${args}`);  // 전달된 인자값을 출력
                const result = func(...args);  // func를 실행하고 결과값을 받아옴
                console.log(`결과값: ${result}`);  // 결과값을 출력
                return result;                   // 최종 결과값을 반환
            }
        }

        // add와 sub 함수에 logger를 적용
        logger(add)(10, 20);  // add(10, 20)을 호출하면서 인자값과 결과값을 로그로 출력
        logger(sub)(20, 10);  // sub(20, 10)을 호출하면서 인자값과 결과값을 로그로 출력

    </script>
</body>

</html>

 

핵심 개념 정리

  1. 고차 함수 (Higher-order Function):
    • logger(func)는 고차 함수입니다. 이 함수는 func라는 다른 함수를 받아 그 함수의 실행 결과를 로깅합니다.
    • 고차 함수는 다른 함수를 인자로 받거나, 함수를 반환하는 함수입니다.
  2.  스프레드 연산자 (전개 연산자) ...args:
    • logger 내부에서 ...args는 함수가 받는 모든 인자를 배열로 받아오는 역할을 합니다. 예를 들어 10, 20이 전달되면 args는 [10, 20]이 됩니다. 10, 20, 30, 40이 전달된다고 해도 에러나지 않고 10, 20만 인자로 사용합니다.
    • **func(...args)**는 이 인자들을 원래의 함수(add나 sub)에 전달합니다.
  3. 함수 호출 패턴:
    • logger(add)(10, 20)는 두 번의 함수 호출로 이루어집니다.
      • 첫 번째는 logger(add): add 함수에 로깅 기능을 덧붙인 새로운 함수를 반환합니다.
      • 두 번째는 반환된 함수에 **(10, 20)**을 넘겨서 실제로 실행하고 결과를 로깅합니다.

이 코드들의 예시가 나중에 나오는 스프링의 AOP이다.

 

performance_checker와 같은 함수가 생성되고 사라지지 않는 과정을 **"클로저(Closure)"**라고 합니다. 이 과정이 자바스크립트의 클로저 개념과 관련이 있습니다.

자바스크립트에서 **함수는 실행될 때마다 자신의 **“스코프(변수 환경)”을 기억하는데, 이때 생성되는 함수는 자신이 선언된 환경을 기억하기 때문에 함수가 사라지지 않게 유지되는 방식입니다. 이 과정을 클로저라고 부릅니다.

 

클로저는 내부 함수가 외부 함수의 변수에 접근할 수 있게 해주는 특성입니다. 즉, 함수가 '자기만의 환경'을 기억하고 있다는 의미죠.

 

한편, 스프링의 **AOP(Aspect-Oriented Programming)**는 프로그램의 **공통적인 관심사(로깅, 트랜잭션 처리 등)**를 특정 부분에만 적용할 수 있게 도와주는 기법입니다. 자바스크립트에서 performance_checker 같은 함수가 어떤 함수의 전후에 뭔가를 추가하는 것과 비슷해요.

AOP: 프로그램에서 공통적인 관심사(예: 성능 측정, 로깅 등)를 핵심 로직과 분리해서 관리하는 기법. 자바스크립트의 performance_checker처럼, 기존 함수에 공통 기능을 추가하는 방식.

 

클로저의 활용 예시

function counter() {
    let count = 0;  // count는 외부 함수의 변수
    return function() {  // 반환된 함수
        count++;  // 외부 함수의 변수에 접근
        return count;  // count 값을 반환
    }
}

const countUp = counter();  // countUp은 반환된 함수
console.log(countUp());  // 1
console.log(countUp());  // 2

이 예시에서 counter 함수는 count 라는 변수를 반환된 함수에 "캡처"하게 됩니다. 그래서 countUp()을 여러 번 호출해도 count 변수의 값이 유지됩니다.

 

객체와 배열을 이용한 데이터 처리

학생별 점수를 아래와 같은 형식으로 콘솔에 출력

<!doctype html>
<html lang="ko">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no">  
</head>
<body>
    <h1>객체와 배열을 이용한 데이터 처리</h1>
    <script>
        //  학생별 점수를 아래 형식에 맞춰서 콘솔에 출력
       
        let scores = [];
        /*  ### 학생별 점수 데이터
            0: {name: '홍길동', korean: 80, math: 90, english: 90}
            1: {name: '고길동', korean: 90, math: 80, english: 80}
            2: {name: '신길동', korean: 70, math: 80, english: 70}
       
            ### 출력 형식
            ----------- --------- --------- --------- --------- ---------
            학생이름     국어      영어      수학      합계      평균
            ----------- --------- --------- --------- --------- ---------
            홍길동       80        90        90        260       86.7
              :          :         :         :          :        :
        */
       
       
    </script>
</body>
</html>

실습

 

<!doctype html>
<html lang="ko">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no">  
</head>
<body>
    <h1>객체와 배열을 이용한 데이터 처리</h1>
    <script>
        //  학생별 점수를 아래 형식에 맞춰서 콘솔에 출력
       
        let scores = [];
        /*  ### 학생별 점수 데이터
            0: {name: '홍길동', korean: 80, math: 90, english: 90}
            1: {name: '고길동', korean: 90, math: 80, english: 80}
            2: {name: '신길동', korean: 70, math: 80, english: 70}
       
            ### 출력 형식
            ----------- --------- --------- --------- --------- ---------
            학생이름     국어      영어      수학      합계      평균
            ----------- --------- --------- --------- --------- ---------
            홍길동       80        90        90        260       86.7
              :          :         :         :          :        :
        */


        // 배열에 학생별 점수를 추가
        scores[0] = {name: '홍길동', korean: 80, math: 90, english: 90};
        scores.push({name: '고길동', korean: 90, math: 80, english: 80});
        scores.push({name: '신길동', korean: 70, math: 80, english: 70});
        console.log(scores);


        // 타이틀 출력
        console.log('-----------\t----\t----\t----\t----\t----');
        console.log('학생이름\t국어\t영어\t수학\t합계\t평균');
        console.log('-----------\t----\t----\t----\t----\t----');
       
        // 학생별 점수 출력
        scores.forEach(score => {
            const total = score.korean + score.math + score.english;
            const average = total / 3;
            console.log(`${score.name}\t\t${score.korean}\t\t${score.math}\t\t${score.english}\t\t${total}\t\t${average.toFixed(1)}`);
        });
    </script>
</body>
</html>

 

push는 인덱스를 따로 관리하지 않아도 편함.

배열이기 때문에 forEach가 편함

 

 

객체 및 배열을 다루는 방법

단축 속성명(shorthand property names)

속성 이름속성값을 가지고 있는 변수 이름이 동일한 경우 ⇒ 속성 이름을 생략

속성 이름함수(메서드) 이름이 동일한 경우 ⇒ function 키워드와 함께 속성 이름을 생략


<script>

        let name = "John";

        const obj = {

            age: 21, 

            name: name

            getName: function getName() {

                return this.name;

            }

        };

        console.log(obj);           // {age: 21, name: 'John', getName: ƒ}

        console.log(obj.getName()); // John

 

        const obj2 = { // 객체

            age: 21, 

            name, // 속성 이름을 생략

            getName() { // 

                return this.name;

            }

        };

        console.log(obj2);           // {age: 21, name: 'John', getName: ƒ}

        console.log(obj2.getName()); // John

</script>

=> name이라는 변수가 선언되어 있지 않으면, obj2에서 단축 속성명(shortened property name)을 사용할 수 없음. 단 name은 obj1바깥에 선언이 있으므로 가능

 

// 사용 예1

// 매개변수를 객체로 반환하는 함수를 정의하는 경우 

function returnObject(age, name) {

    return { age: age, name: name };

}

console.log(returnObject(20, "홍길동"));        // {age: 20, name: "홍길동"}

 

function returnObject2(age, name) {

    return { age, name };                                  // key는 returnObject2에 주어진 매개변수가 되는 거임.

}

console.log(returnObject2(20, "홍길동")); // {age: 20, name: "홍길동"}

 

=> age와 name은 문자열(string)이 아니라 속성명 (property name) 또는 key 라고 함

 

 

// 사용 예2

// 로그를 출력할 때

// 변수가 가지고 있는 값을 로그로 출력 => 변수 이름과 함께 출력되어야 내용 파악이 용이

console.log("age", age);                    // age 20

console.log("name", name);                  // name 홍길동

 

console.log(`age: ${age}, name: ${name}`);  // age: 20, name: 홍길동, 이게 더 good 


console.log({age, name});   // {age: 20, name: "홍길동"} , 중괄호로 묶으면 자바스크립트가 객체 리터럴로 인식함. 단축 속성명이 적용된 거임.

 

계산된 속성명(computed property names)

변수를 이용해서 객체 속성의 키(key)를 만드는 방법 

 

// 속성 이름(key)과 속성 값(value)을 전달받아 객체를 반환하는 함수를 정의 

function returnObject(key, value) {

    /*

    const obj = {}; // 빈 객체

    obj[key] = value;

    return obj;

    */

    return { [key]: value }; // key를 대괄호로 묶어서 표현해줌 : '계산된 속성명', key가 변수일 때 속성명을 동적으로 지정해줌

}

console.log(returnObject("name", "John")); // {name: "John"} 어떻게 구현해야 이런 결과가 나올까

 

=> 대괄호가 없으면 그냥 키 이름이 key 라는 의미가 됨

 

// 속성 이름이 일련번호 형태를 가지는 객체를 반환하는 함수

function returnObject2(key, value, no) {

    /*

    const obj = {};

    obj[key + no] = value;

    return obj;

    */ 이걸 계산된 속성명을 써서 더 간단하게 표현할 거임

    return { [key+no]: value }; // [key+no]이 계산된 속성명

}

console.log(returnObject2("name", "John", 1)); // {name1: "John"}

console.log(returnObject2("name", "Jane", 2)); // {name2: "Jane"}

 

 

전개 연산자(spread operator)

 

console.log(Math.max(10, 20, 1, 30, 3, 2)); // 30

console.log(Math.max([10, 20, 1, 30, 3, 2]));   // NaN이 출력되어 버림

 

const numbers = [10, 20, 1, 30, 3, 2];

console.log(Math.max(numbers)); // NaN 이 출력됨

console.log(Math.max(numbers[0], numbers[1], numbers[2], numbers[3], numbers[4], numbers[5])); // 30

console.log(Math.max(...numbers)); // 30


console.log(...numbers); // 10 20 1 30 3 2

=> ...numbers 이 전개 연산자 , 해당하는 배열이 가지고 있는 것들을 시켜 주는 겁니다.

 

사용 예 1. 배열의 값을 복사

 

자바 스크립트는 배열이나 객체 이런 구조는 변수가 값을 가지고 있는게 아니고 주소를 가지고 있음.

주소를 가지고 있기 때문에 그 변수의 값을 전달하는 방식으로, 또는 할당하는 방식으로 사용하게 되면 주소 공유의 문제가 발생함.

그것을 막기 위해서 동일랑 값을 가지고 있는 것을 복사해 줘야 됩니다.

교재를 보면 깊은 복사, 얕은 복사가 있습니다.

let a = 10;
let b = a;
console.log(a);         // 10
console.log(b);         // 10


let arr1 = [1, 2, 3];
let arr2 = arr1;
console.log(arr1);      // [1, 2, 3]
console.log(arr2);      // [1, 2, 3]


a = 20;
console.log(a);         // 20
console.log(b);         // 10 바뀌지 않는다.


arr1[0] = 10;
console.log(arr1);      // [10, 2, 3]
console.log(arr2);      // [10, 2, 3] 바뀜


// 위의 문제(객체와 배열을 복사할 때 참조가 복사되는 문제)를 해결
// 주소가 아닌 '값'을 복사하는 것이 필요
let arr3 = [ 1, 2, 3 ];
let arr4 = []; // 빈 배열

for (let i = 0; i < arr3.length; i++) {
    arr4[i] = arr3[i];
}

console.log(arr3);      // [1, 2, 3]
console.log(arr4);      // [1, 2, 3]
arr3[0] = 10;
console.log(arr3);      // [10, 2, 3]
console.log(arr4);      // [1, 2, 3] 그대로 유지


// 전개 연산자를 이용해서 배열의 값을 복사
let arr5 = [ 1, 2, 3 ];
let arr6 = [ ...arr5 ]; // 값만 복사 된다.
console.log(arr5);      // [1, 2, 3]
console.log(arr6);      // [1, 2, 3]
arr5[0] = 10;  
console.log(arr5);      // [10, 2, 3]
console.log(arr6);      // [1, 2, 3]

 

 

 

전개 연산자를 쓰면 쉽게 배열의 값을 복사할 수 있음.

 

사용 예 2. 객체의 값 복사

let obj1 = { age: 23, name: '홍길동' };
let obj2 = obj1;

console.log(obj1);  // { age: 23, name: '홍길동' }
console.log(obj2);  // { age: 23, name: '홍길동' }

obj1.age = 200;
console.log(obj1);  // { age: 200, name: '홍길동' }
console.log(obj2);  // { age: 200, name: '홍길동' }


// 객체 값이 복사
let obj3 = { age: 23, name: '홍길동' };
let obj4 = { ...obj3 };

console.log(obj3);  // { age: 23, name: '홍길동' }
console.log(obj4);  // { age: 23, name: '홍길동' }

obj3.age = 200;
console.log(obj3);  // { age: 200, name: '홍길동' }
console.log(obj4);  // { age: 23, name: '홍길동' }

 

사용 예 3. 객체를 복사하는 과정에서 새로운 속성을 추가하거나 속성의 값을 변경하는 경우

let obj1 = { name: '홍길동', age: 23 };
obj1.age = 40;                          // 객체의 속성값을 변경
obj1.colors = ['red', 'blue', 'green']; // 객체에 새로운 속성을 추가
			                            // obj1.colors; 이렇게만 하면 undefined로 할당
console.log(obj1); 
                  // { name: '홍길동', age: 40, colors: [ 'red', 'blue', 'green' ] }


// obj1과 동일한 속성을 가지는 obj2를 정의하고, name 속성의 값을 고길동으로 변경
/*
let obj2 = { ...obj1 };
obj2.name = '고길동';  
*/

// 위 보다 더 쉽게, 한 큐에 가능 : 전개 연산자로 복사한 name 속성의 값을 덮어씀
let obj2 = { ...obj1, name: '고길동' }; // 이어서 써줌 (뒤에 똑같은 키가 반복되면 덮어쓰기가 됨)
console.log(obj2);  // { name: '고길동', age: 40, colors: [ 'red', 'blue', 'green' ] }


// obj1과 동일한 속성을 가지는 obj3를 정의하고, email 속성을 추가
// 전개 연산자로 복사한 객체에 email 속성을 추가
let obj3 = { ...obj1, email: "go@test.com" }; // 새로운 항목을 추가할 수도 있음.
console.log(obj3); 
// { name: '홍길동', age: 40, colors: [ 'red', 'blue', 'green' ], email: 'go@test.com' }

 

사용 예 4. 배열 또는 객체를 결합할 때 

// 두 배열을 결합
const arr1 = [ 1, 2, 3 ];
const arr2 = [ 3, 4, 5 ];
const arr3 = [ ...arr1, ...arr2 ]; // ','로 연결하면 결합된다. 단 '순서'가 중요!
console.log( arr3 );        // [ 1, 2, 3, 3, 4, 5 ]
const arr4 = [ ...arr2, ...arr1 ];
console.log( arr4 );        // [ 3, 4, 5, 1, 2, 3 ]


// 두 객체를 결합
const obj1 = { a: 1, b: 2, c: 3 };
const obj2 = { c: 33, d: 4, e: 5 };
const obj3 = { ...obj1, ...obj2 };
console.log( obj3 );        // { a: 1, b: 2, c: 33, d: 4, e: 5 } 덮어쓰기 됨

const obj4 = { ...obj2, ...obj1 };
console.log( obj4 );        // { c: 3, d: 4, e: 5, a: 1, b: 2 } 덮어써버림

뒤에 오는 것으로 덮어쓰기 된다.

 

사용 예 5. 배열 요소를 함수의 개별 인수로 전달하라 때 

function sum (a, b, c) {
    return a + b + c;
}

const numbers = [10, 20, 30];
console.log(sum(...numbers));


console.log(Math.max(10, 20, 30));
console.log(Math.max(...numbers));

 

Rest Parameters 

함수의 인수를 배열로 수집하는 방법

함수가 전달받은 가변 길이의 인수를 처리할 때 사용

/*
function sum(a, b) {
    const result = a + b;
    console.log(result);
}

sum(10, 20); // 30이 나옴
sum(10, 20, 30); // 60이 나왔으면 좋겠는데 무시가 되어버림
sum(10, 20, 30, 40); // 100이 나왔으면 좋겠는데 무시가 되어버림

모두 다 30이 나옴
*/

function sum(...args) {
    console.log(args); // 찍어보면 배열로 전달되는걸 볼 수 있음.
    let result = 0;
    for (let i = 0; i < args.length; i++) {
        result += args[i];
    }
    console.log(result);
}


sum(10, 20);            // 30
sum(10, 20, 30);        // 60
sum(10, 20, 30, 40);    // 100

 

...을 사용하는 전개 연산자나머지 매개변수는 둘 다 동일한 문법을 사용하지만, 사용 목적이 다릅니다.

  • 나머지 매개변수(...args)는 함수의 인자들을 '배열'로 모으는 용도이고,
  • 전개 연산자(...)는 배열이나 객체를 개별적인 값으로 펼치는 용도입니다.

자주 쓰이는 것 중에 하나인데 이 문법은 뒤에 비구조화를 배우면서 다시 한번 볼게요.

 

배열 비구조화(array destructuring)

배열 데이터(요소)를 변수에 나눠서 할당. 배열이 있으면 그 배열 데이터들을 개별 변수에다가 차곡차곡 꽂아줌.

// 배열 요소의 값을 변수에 할당
const arr = [ 1, 2, 3, 4, 5 ];


// 배열 인덱스를 이용해서 개별 변수에 배열이 가지고 있는 값을 할당
let a = arr[0];
let b = arr[1];
console.log({ a, b });          // { a: 1, b: 2 } 단축속성명!!


// c, d, e, f 변수에 arr 배열에 첫번째 인덱스의 값부터 차례로 할당
let [ c, d, e, f ] = arr; // 이 부분이 배열 비구조화 라는 것이다. 
			// 오른쪽에 있는 배열의 값을 왼쪽에 있는 지역변수에게 하나씩 하나씩 대입시켜줌
console.log({ c, d, e, f });    // { c: 1, d: 2, e: 3, f: 4 }

 

// 두 변수의 값을 교환
let x = 10;
let y = 20;
console.log({x, y});    // {x: 10, y: 20} 객체 리터럴에서 변수명과 동일한 속성명 사용 (단축 속성명)

let temp = x;
x = y;
y = temp;
console.log({x, y});    // {x: 20, y: 10} 기존 swap 하는 방법


// 배열 비구조화를 이용해서 두 변수의 값을 교환
x = 10
y = 20
console.log({x, y});    // {x: 10, y: 20}


[x, y] = [y, x]; // 오른쪽의 [y, x]는 y와 x값을 가지는 배열을 의미
~~~~~~   ~~~~~~  // 왼쪽의 [x, y]는 비구조화. [y, x] 배열이 가지는 요소의 값들을 왼쪽의 변수에다가 할당
비구조화   배열을 선언
console.log({x, y});    // {x: 20, y: 10}

 

 

자바 스크립트에서 대괄호를 쓰는 케이스에는

배열을 정의 할 때 대괄호를 써요.

배열 요소의 접근 할 때, 인덱싱할 때 let a = arr[0]

객체 요소의 값을 세팅할 때 대괄호를 씀

그런데 위의 대괄호들은 일반적으로 = 의 오른쪽에다가 씀.

 

그래서 대괄호가 왼쪽에 있으면 변수를 나열하고 있다고 보고,

오른쪽에 배열이 있으면 오른쪽 배열의 요소의 값을 이 순서대로 들어간다고 보면 된다.

 

// 배열의 크기 보다 변수의 개수가 많은 경우
const arr = [1, 2];
const [a, b, c] = arr; // 비구조화
console.log({a, b, c});    // { a: 1, b: 2, c: undefined }


// 기본값 설정이 가능
const [d, e = 20, f = 30] = arr;
console.log({d, e, f});    // { d: 1, e: 2, f: 30 }, 2는 기본값이 있어도 덮어쓰기됨

 

// 배열의 일부값을 변수에 할당할 경우 => 할당하지 않을 자리는 비워둠
const arr = [ 1, 2, 3, 4, 5 ];
let [a, b, c, d, e] = arr;
console.log({ a, b, c, d, e });   // { a: 1, b: 2, c: 3, d: 4, e: 5 }


// 변수 x에 첫번째 값을, y에 세번째 값을, z에 다섯번째 값을 할당
let [x, , y, , z] = arr;
console.log({ x, y, z });    // { x: 1, y: 3, z: 5 }

 

// 배열의 값을 할당하고 남은 나머지를 새로운 배열로 만드는 경우
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

// arr 배열의 첫번째 값을 first 변수에 넣고, 나머지 값을 rest 이름의 배열에 추가
const [first, ...rest] = arr; // first는 변수이고, rest는 배열임
console.log(first); // 1
console.log(rest);  // [2, 3, 4, 5, 6, 7, 8, 9, 10]

 

객체 비구조화(object destructuring)

// 객체 비구조화를 할 때는 객체의 속성명(key)이 중요
const obj1 = { age: 21, name: "홍길동" };
const obj2 = { age: 43, name: "고길동" };


// obj1의 age와 name 속성의 값을 age와 name 변수에 할당
/*
let age = obj1.age;
let name = obj1.name;
*/
let { age, name } = obj1;   // 객체 비구조화 할당
console.log({ age, name }); 
           // { age: 21, name: '홍길동' } <= 단축 속성명을 이용해서 변수가 가지고 있는 값을 출력

// obj1 객체가 가지고 있지 않은 이름을 이용
let { newAge, newName } = obj1;
console.log({ newAge, newName }); // {newAge: undefined, newName: undefined}, 할당되지 않음
					// obj1 객체에는 newAge와 newName 속성이 없기 때문

자바스크립트에서는 속성명이 객체에 없을 때 해당 변수에 undefined가 자동으로 할당됨.

 

const obj1 = { age: 21, name: "홍길동" };
const obj2 = { age: 43, name: "고길동" };


// obj1 객체가 가지고 있는 값을 가져와서 새로운 이름으로 사용
let { age: newAge, name: newName } = obj1;
console.log({ newAge, newName });   // { newAge: 21, newName: '홍길동' }

 

const obj1 = { age: 21, name: "홍길동" };
const obj2 = { age: 43, name: "고길동" };


// 객체 존재하지 않는 요소를 변수에 할당하려고 하면 undefined가 할당됨
let { age: newAge, name: newName, email } = obj1;
console.log({ newAge, newName, email });   // { newAge: 21, newName: '홍길동', email: undefined }

 

const obj1 = { age: 21, name: "홍길동" };
const obj2 = { age: 43, name: "고길동" };


// 객체 존재하지 않는 변수의 기본값 설정
let { age: newAge, name: newName, email = "default" } = obj1;
console.log({ newAge, newName, email });   // { newAge: 21, newName: '홍길동', email: 'default' }

 

// 객체 비구조화를 할 때는 객체의 속성명(key)이 중요
const obj1 = { age: 21, name: "홍길동", email: "hong@test.com" };


// obj1 객체의 age 속성의 값을 hongAge 변수에 할당하고, 나머지 값을 rest 이름의 객체에 할당
const { age: hongAge, ...rest } = obj1;
console.log(hongAge);       // 21
console.log(rest);          // { name: "홍길동", email: "hong@test.com" }

 

 

이제 배열의 메서드들을 정리해보자.

Array 메서드 : map, filter, reduce

 

map()

const source = [1, 4, 9, 16];

// source 배열의 값을 두 배수한 결과 배열(twoTimes)을 만들어서 출력
// 방법1. for문을 이용한 방법
{  
    const twoTimes = [];
    for (let i = 0; i < source.length; i++) {
        // twoTimes.push(source[i] * 2);
        twoTimes[i] = source[i] * 2;
    }
    console.log(twoTimes);      // [2, 8, 18, 32]
}


// 방법2. map() 메서드를 이용
{
    const twoTimes = source.map(value => value * 2);
    console.log(twoTimes);      // [2, 8, 18, 32]
}

{
    const func = value => value * 2;
    const twoTimes = source.map(func);
    console.log(twoTimes);      // [2, 8, 18, 32]
}

{
    const func = function(value) { return value * 2; };
    const twoTimes = source.map(func);
    console.log(twoTimes);      // [2, 8, 18, 32]
}

{
    const twoTimes = source.map(function(value) {
        return value * 2;
    });
    console.log(twoTimes);      // [2, 8, 18, 32]
}

{
    function func(value) {
        return value * 2;
    }
    const twoTimes = source.map(func);
    console.log(twoTimes);      // [2, 8, 18, 32]
}


// 여러 맵 함수를 연결해서 사용
{
    const twoTimes = v => v * 2;
    const tenTimes = v => v * 10;
    const plusTwo = v => v + 2;


    const result = source.map(plusTwo).map(twoTimes).map(tenTimes);
    console.log(result); // [ 60, 120, 220, 360 ]


    result = source.map(twoTimes).map(tenTimes).map(plusTwo);
    console.log(result); // [ 22, 42, 82, 162 ]
}

 

map() 메서드는 배열의 각 요소를 변형하는 새로운 배열을 만듭니다.

여기서 value => value * 2는 각 요소를 두 배로 만드는 화살표 함수입니다.

map()은 source 배열을 순차적으로 돌면서 각 요소를 value * 2로 바꿔서 새로운 배열을 만듭니다.

결과적으로 twoTimes 배열은 [2, 8, 18, 32]가 됩니다.

 

map() 메서드는 체이닝(연결)을 할 수 있습니다. 즉, 여러 개의 map()을 연속적으로 사용하여, 각 함수의 변형 결과를 다음 map()에서 다시 변형하는 방식입니다.

 

map() 메서드 안에는 반드시 "함수"가 들어가야 합니다. 이 함수는 배열의 각 요소를 변형하는 역할을 합니다

map() 메서드는 주어진 배열의 각 요소에 대해 함수를 호출하고, 그 함수가 반환하는 값을 새로운 배열로 만들어서 반환합니다.

 

filter() : 걸러낸다.

const words = [ 'spring', 'summer', 'fall', 'winter', 'destruction', 'creation', 'rebirth' ];

// 길이가 여섯 글자 이상인 단어만 추출
{
    const newWords = [];
    for (let i = 0; i < words.length; i++) {
        if (words[i].length >= 6) {
            newWords.push(words[i]);
        }
    }
    console.log(newWords);      
}

{
    const newWords = words.filter(w => w.length >= 6);
    console.log(newWords);      
}

{
    function func(w) {
        return w.length >= 6;
    }
    const newWords = words.filter(func);
    console.log(newWords);      
}

{
    const func = function(w) {
        return w.length >= 6;
    };
    const newWords = words.filter(func);
    console.log(newWords);      
}

{
    const newWords = words.filter(function(w) {
        return w.length >= 6;
    });
    console.log(newWords);      
}

const numbers = [ 1, 3, 4, 6, 11, 14 ];

// 짝수만 추출해서 10배수한 결과를 출력
console.log(numbers.filter(n => n % 2 === 0).map(n => n * 10));  // [ 40, 60, 140 ]
// 메서드 체이닝은 한 메서드의 결과를 바로 다음 메서드의 입력으로 사용하여,
// 여러 메서드를 연결해서 호출하는 방식

const even = numbers.filter(n => n % 2 === 0);
const evenTenTimes = even.map(n => n * 10);
console.log(evenTenTimes);          // [ 40, 60, 140 ]

 

 

reduce()

reduce는 콜백 함수의 매개 변수로 많이 들어감.

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

// 배열 요소들의 합계
{ // 루프
    let sum = 0;
    for (let i = 0; i < numbers.length; i++) {
        sum += numbers[i];
    }
    console.log(sum);       // 15
}

{ // reduce는 매개변수가 2개가 들어감. 
  // 첫 번째는 acc: 누적기, 두 번째는 current value: 현재값 이라고 함.
    let sum = numbers.reduce((acc, cur) => acc + cur, 0);   // 0은 initValue
    console.log(sum);       // 15
}

{   // initvalue를 설정한 경우
    let sum = numbers.reduce((acc, cur) => {
        const result = acc + cur;
        console.log(acc, cur, result)
        return result;
    }, 0);
    console.log(sum);       // 15
}

{   // initValue를 생략한 경우
    let sum = numbers.reduce((acc, cur) => {
        const result = acc + cur;
        console.log(acc, cur, result)
        return result;
    });
    console.log(sum);       // 15
}

reduce() 기본 동작: 프로토타입 arr.reduce(callback, initialValue);

  • acc (누적값): 이전 reduce 호출에서 반환된 값이 들어옵니다. 처음에는 initValue로 설정된 값이 들어갑니다.
  • cur (현재값): 현재 순회 중인 배열의 요소가 들어갑니다.
  • initValue가 있으면 처음에 acc에 들어간다.

reduce는 배열을 하나의 값으로 축소하거나, 배열의 모든 요소를 합산하는 데 사용됩니다.

reduce의 콜백 함수는 누산기(accumulator)와 현재 요소(current value)를 처리하여 결과를 누적합니다.

 

reduce

누산기(acc)                         현재값(cur)        계산결과                                                                                         (누산기(acc))

0 1 0 + 1 = 1 1
1 2 1 + 2 = 3 3
3 3 3 + 3 = 6 6
6 4 6 + 4 = 10 10
10 5 10 + 5 = 15 15

최종 결과값인 sum은 15입니다.

initValue를 안 써주면 배열의 첫 번째 값이 acc(=1)에 들어가고 두 번째 값이 cur(=2)에 들어감

 

initValue 0으로 설정함
initValue 설정 안함

 

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

// numbers 배열의 각 항목의 값에 13을 곱한 결과 중 짝수의 합을 구하시오.
//             ~~~~~~~~~~~~~~~~~~~  ~~~~~~~~~~  ~~~
//                     map           filter     reduce
const f1 = n => n * 13; // 동일한 개수
const f2 = n => n % 2 === 0;  // 짝수를 고르는 것
const f3 = (a, c) => a + c; // 합계는 하나밖에 없음

const result = numbers.map(f1).filter(f2).reduce(f3, 0);
console.log(result); // 78

const result1 = numbers.map(f1);
const result2 = result1.filter(f2);
const result3 = result2.reduce(f3, 0);
console.log(result1); // [ 13, 26, 39, 52, 65 ]
console.log(result2); // [ 26, 52 ]
console.log(result3); // 78


// 콜백함수를 직접 정의
const result4 = numbers
                .map(n => n * 13)
                .filter(n => n % 2 === 0)
                .reduce((a, c) => a + c, 0);

 

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

// numbers 배열에서 짝수를 추출해서 13을 곱한 결과의 합계를 구하시오.
const f1 = n => n * 13;
const f2 = n => n % 2 === 0;
const f3 = (a, c) => a + c;

const result2 = numbers.filter(f2).map(f1).reduce(f3, 0);
console.log(result2); // 78, 동일한 결과가 나옴.

 

const students = [
    { name: '홍길동', age: 16, score: 88 },
    { name: '홍길서', age: 18, score: 78 },
    { name: '홍길남', age: 20, score: 98 },
    { name: '홍길북', age: 22, score: 68 }
];


// 점수가 80점 이상인 학생의 이름을 출력
const f1 = student => student.score >= 80;
const f2 = student => student.name;


console.log(students.filter(f1).map(f2));
students.filter(f1).map(f2).forEach(name => console.log(name));


// 점수가 80점 이상이고, 나이가 20세 이상인 학생의 이름을 출력
const f3 = student => student.age >= 20;
const result = students.filter(f1).filter(f3).map(f2);
console.log(result);

 

forEach는 배열의 각 요소에 대해 주어진 함수를 한 번씩 실행하는 메서드

 

forEach의 동작 방식

forEach는 배열의 각 요소에 대해 콜백 함수를 실행하는 메서드입니다. 그래서 forEach는 전달받은 배열의 각 요소를 하나씩 처리합니다.

 

 

배열과 객체

  • students.filter(f1).map(f2)의 결과배열입니다. 그 배열 안의 각 요소는 name(문자열)입니다.
  • 즉, forEach에서 전달받는 name은 문자열이며, 배열을 순회하면서 각 이름을 출력하는 것입니다.

name의 역할

  • name은 객체의 속성이 아닌 배열의 요소입니다.
  • students.filter(f1).map(f2)는 배열을 반환하고, 그 배열의 각 요소는 문자열(학생 이름)입니다.
  • forEach에서 name은 배열에서 하나씩 꺼내지는 값이므로 배열의 요소로 이해할 수 있습니다. 객체가 아닌 문자열입니다.

 

실습 - 과목별 총점을 계산해서 출력

<!doctype html>
<html lang="ko">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no">  
</head>
<body>
    <h1>객체와 배열을 이용한 데이터 처리</h1>
    <script>

        // 배열에 학생별 점수를 추가
        scores[0] = {name: '홍길동', korean: 80, math: 90, english: 90};
        scores.push({name: '고길동', korean: 90, math: 80, english: 80});
        scores.push({name: '신길동', korean: 70, math: 80, english: 70});
        console.log(scores);

        // 타이틀 출력
        console.log('-----------\t----\t----\t----\t----\t----');
        console.log('학생이름\t국어\t영어\t수학\t합계\t평균');
        console.log('-----------\t----\t----\t----\t----\t----');
       
        // 학생별 점수 출력
        scores.forEach(score => {
            const total = score.korean + score.math + score.english;
            const average = total / 3;
            console.log(`${score.name}\t\t${score.korean}\t\t${score.math}\t\t${score.english}\t\t${total}\t\t${average.toFixed(1)}`);
        });

        // 각 과목의 점수를 추출해서 합계를 계산 (추가된 기능)
        const ksum = scores.map(s => s.korean).reduce((a, c) => a + c, 0);
        const esum = scores.map(s => s.english).reduce((a, c) => a + c, 0);
        const msum = scores.map(s => s.math).reduce((a, c) => a + c, 0);

        // 특정 과목의 점수를 추출해서 합계를 계산(반환)하는 함수를 정의
        const sum = subject => scores.map(s => s[subject]).reduce((a, c) => a + c, 0);

        // 풋터 출력, 둘 다 결과 값이 똑같음
        console.log('-----------\t----\t----\t----\t----\t----');
        console.log(`총점\t\t${ksum}\t\t${esum}\t\t${msum}\t`);
        console.log(`총점\t\t${sum('korean')}\t\t${sum('english')}\t\t${sum('math')}\t`);
        console.log('-----------\t----\t----\t----\t----\t----');
       
    </script>
</body>
</html>

풋터(footer)는 최종적인 출력 결과를 담당하는 부분을 의미

'LG CNS > 필기노트' 카테고리의 다른 글

1/9  (0) 2025.01.09
리액트 컴포넌트 만들기 실습  (0) 2025.01.09
1/6  (0) 2025.01.06
12월 26일 (목)  (0) 2025.01.05
12/24 화  (0) 2024.12.24