[LG CNS AM Inspire Camp 1기] FE - 객체 및 배열을 다루는 JavaScript 문법 (feat. 얕은복사, 깊은복사)

이슬아·2025년 1월 4일

객체 및 배열을 다루는 방법

📍 단축 속성명 (Shorthand Property Names)

객체를 생성할 때

사용 예 1) 매개변수를 객체로 반환하는 함수를 정의하는 경우

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

function returnObject2(age, name) {
    return { age, name }; 					 // 단축 속성명
}
console.log(returnObject2(20, "홍길동"));		// {age: 20, name: "홍길동"}

사용 예 2) 변수가 가지고 있는 값을 로그로 출력할 때
-> 변수 이름과 변수 값이 함께 나와야 디버깅 및 내용 파악이 용이하다

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

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

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

📍 계산된 속성명(computed property names)

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

// 속성 이름(key)과 속성 값(value)을 전달받아 객체를 반환하는 함수를 정의 
function returnObject(key, value) {
    /*
    const obj = {};
    obj[key] = value;
    return obj;
    */
    return { [key]: value };  		// 이때 key는 꼭 대괄호로 묶어줘야 함
}
console.log(returnObject("name", "John"));		// {name: "John"}
// 속성 이름이 일련번호 형태를 가지는 객체를 반환하는 함수
function returnObject2(key, value, no) {
    /*
    const obj = {};
    obj[key + no] = value;
    return obj;
    */
    return { [key+no]: value };
}
console.log(returnObject2("name", "John", 1)); 	// {name1: "John"}
console.log(returnObject2("name", "Jane", 2)); 	// {name2: "Jane"}

📍 전개 연산자(Spread Operator) 🌟

...을 사용하여 배열이나 객체를 분해, 복사, 결합할 수 있다.

사용 예시 1) 배열의 값 복사
👀 여기서 배열의 복사를 잠깐 자세히 살펴보자면 배열이 실제로 저장하는 것은 "값"이 아니라 "주소"이다.

즉 메모리에 값을 저장하는 것이 아니라, 값이 저장된 메모리의 주소(참조)를 저장하는 것이다.
그래서 배열을 다른 변수에 할당히면, 값을 복사하는 것이 아니라 메모리 주소를 공유하게 된다.

let arr1 = [1, 2, 3];
let arr2 = arr1; // 주소를 복사
arr2[0] = 100;

console.log(arr1); // [100, 2, 3] (arr1도 변경됨)
console.log(arr2); // [100, 2, 3] (같은 주소를 참조하고 있음)

이렇게 되면 arr1과 arr2는 같은 메모리 주소를 가리키고 있으므로, 어느 한쪽을 수정하면 다른 쪽에도 영향을 미치게 된다. 이런 상황에서는 배열의 값을 복사해서 새로운 배열로 만들어야 한다.

여기서 등장하는 개념이 "깊은 복사, 얕은 복사"인데 너무 깊게 가면 본질이 흐려지니 간단하게만 언급하자면

  • 얕은 복사는 복사된 객체나 배열의 단순한 데이터만 복사하고 중첩된 객체나 배열은 참조(주소)만 복사한다.
  • 깊은 복사는 객체나 배열의 모든 값을 새로운 메모리 공간에 복사한다 (중첩된 객체나 배열 포함)
    여기서 배우고 있는 전개 연산자는 얕은 복사에 해당한다.

예를 들어 설명하자면

const arr1 = [1, 2, [3, 4]];
const arr2 = [...arr1]; // 전개 연산자 사용

arr2[2][0] = 100;

console.log(arr1); // [1, 2, [100, 4]] (원본이 영향을 받음)
console.log(arr2); // [1, 2, [100, 4]]

단순 데이터 값은 원본이 영향을 받지 않지만 중첩된 배열 부분에서는 원본이 영향을 받는 것을 볼 수있다.
따라서 단순 데이터만 사용할 경우 얕은 복사를 사용해도 되지만 중첩된 값을 독립시키려면 깊은 복사를 사용해야 한다.

사용 예 2) 객체의 값 복사

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

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

  • 속성 값 수정 -> 덮어쓰는 형식으로 값을 변경
// 전개 연산자로 복사한 name 속성의 값을 덮어씀
let obj2 = { ...obj1, name: '고길동' };	
console.log(obj2);  // { age: 40, name: '고길동', colors: [ 'red', 'blue', 'green' ] }
Rest Parameters
  • 속성 값 추가
// obj1과 동일한 속성을 가지는 obj3를 정의하고, email 속성을 추가
// 전개 연산자로 복사한 객체에 email 속성을 추가
let obj3 = { ...obj1, email: "go@test.com" };
console.log(obj3);  // { age: 40, name: '홍길동', 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(...args) {
    console.log(args);
    let result = 0;
    for (let i = 0; i < args.length; i++) {
        result += args[i];
    }
    console.log(result);
}

나는 이게 전개 연산자의 일부인가 생각했다. 생긴 것도 비슷하고..."사용 예 5) 배열 요소를 함수의 개별 인수로 전달하라 때"의 예시와 비슷하게 동작하는 것 같아서 😅

하지만 생긴 것은 비슷하지만 펼치고 모은다는 점에서 완전히 상반되는 개념이었다.

전개 연산자와 달리 Rest Parameters은 항상 함수 매개변수에서만 사용된다.

📍 배열 비구조화(array destructuring)

배열 데이터(요소)를 변수에 나눠서 할당하는 방법

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

// 두 변수의 값을 교환
[x, y] = [y, x];

// 배열의 크기 보다 변수의 개수가 많은 경우
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 }

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


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

📍 객체 비구조화(object destructuring)

객체의 속성을 변수에 쉽게 할당하는 방법

const obj = { name: "John", age: 21 };
let { name, age } = obj; // 객체의 속성을 변수에 할당
console.log(name, age); // John 21

✔️ obj1 객체가 가지고 있지 않은 이름을 이용한다면?

let { newAge, newName } = obj1;
console.log({ newAge, newName });   // {newAge: undefined, newName: undefined}

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

✔️ 객체 존재하지 않는 요소를 변수에 할당하려고 하면?

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

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

obj1 객체의 age 속성의 값을 hongAge 변수에 할당하고, 나머지 값을 rest 이름의 객체에 할당

const { age: hongAge, ...rest } = obj1;
console.log(hongAge);       // 21
console.log(rest);          // { name: "홍길동", email: "hong@test.com" }

📍 Array 메서드

🍏 map()

배열의 각 요소에 대해 콜백 함수를 실행한 결과값으로 새로운 배열을 만든다.

ex) 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]
}

ex) 여러 맵 함수를 연결해서 사용

	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); // [ 220, 460, 920, 1840 ]

🍏 filter()

: 배열의 각 요소에 대해 조건(콜백 함수)을 확인하고, 조건을 만족하는 요소만 새로운 배열로 반환한다.
ex) 길이가 여섯 글자 이상인 단어만 추출

{
    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);      
}

ex) 짝수만 추출해서 10배수한 결과를 출력

const numbers = [ 1, 3, 4, 6, 11, 14 ];
console.log(numbers.filter(n => n % 2 === 0).map(n => n * 10));

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

🍏 reduce()

배열의 각 요소에 대해 누적 계산을 수행하여 단일 값을 반환한다.

reduce(callback, initialValue)

  • callback: 각 요소를 처리할 함수.
    -initialValue: 누적값의 초기값(옵션).

ex) 배열 요소들의 합계

{
    let sum = 0;
    for (let i = 0; i < numbers.length; i++) {
        sum += numbers[i];
    }
    console.log(sum);       // 15
}
{
    let sum = numbers.reduce((acc, cur) => acc + cur, 0);
    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
}

ex) numbers 배열의 각 항목의 값에 13을 곱한 결과 중 짝수의 합을 구하시오.

const number = [1,2,3,4,5]

const f1 = n => n *13 ;
const f2 = n => n % 2 ==0 ;
const f3 = (a,c) => a+c ;

const result = numner.map(f1).fliter(f2).reduce(f3,0);
console.log(result);

const reuslt = number.map(n=>n*13).fliter(n=>n%2 ===0).reduce((a,c) => a+c,0)

ex) numbers 배열에서 짝수를 추출해서 13을 곱한 결과의 합계를 구하시오

const result = number.fliter(n => n%2 ===0).map(n => n*13).reduce((a,c)=>a+c,0)
const result2 = numbers.filter(f2).map(f1).reduce(f3, 0);

0개의 댓글