heyday2024 님의 블로그
[JS 강의 2회차] 객체&배열 의 데이터 저장, 수정, 삭제, 복사 정리 본문
- 콜백 함수
- 다른 함수에 인자로 전달되어 나중에 호출되는 함수
- sort(), map() 등의 배열 메소드에서 인자로 자주 사용됩니다.
- 변경 메소드 / 비변경 메소드
- 변경 메서드(Mutable Method): 배열 원본 자체를 변경하는 메서드
- 비변경 메서드(Immutable Method): 배열 원본을 변경하지 않고, 새로운 배열을 반환하는 메서드
참조형 타입이란?
참조형 타입은 메모리에 값이 저장된 주소를 참조하는 타입(객체, 배열, 함수...)
참조형 타입은 값을 직접 가지지 않고, 해당 값이 저장된 메모리 주소를 가리킴.
따라서, 같은 참조형 타입 변수를 여러 개 만들면 모두 동일한 메모리 주소를 가리키기 때문에 한 변수를 수정하면 다른 변수에도 영향을 미칠 수 있음. (참조 복사: const obj2 = obj1)
자바스크립트에는 두가지 메모리영역(저장영역) 이 존재
위 이미지에서 볼 수 있듯이 기본형 데이터들은 call stack에만 쌓이는데에 비해 참조형인 array는 heap에 별도로 데이터가 저장되는 것을 볼수 있음.
배열: 여러 개의 값을 순서대로 저장하는 리스트 형태의 데이터 구조
1. 참조 복사 (Reference Copy)
참조 복사는 객체나 배열을 복사할 때, 단순히 그 객체의 메모리 주소(참조값)만을 복사하는 것을 의미. 이 경우, 원본 객체와 복사본은 완전히 동일한 객체를 가리키기 때문에 둘 중 하나를 변경하면 다른 하나에도 영향을 미침. 따라서 두 객체는 사실상 동일한 객체임.
const original = { a: 1, b: 2 };
const referenceCopy = original; // 참조 복사
referenceCopy.a = 3;
console.log(original.a); // 3 (같은 객체를 가리킴)
2. 얕은 복사 (Shallow Copy)
얕은 복사는 객체의 상위 속성(1단계 속성)을 복사하여 새로운 객체를 만들지만, 참조형 데이터(예: 객체, 배열)에 대해서는 참조값만 복사함. 즉, 얕은 복사된 객체는 원본 객체와 다른 객체이지만, 그 내부의 참조형 속성들은 같은 참조값을 공유하게 됨.
- 복사본의 속성이 원본 객체의 속성과 같은 참조(주소값)를 공유하는 복사
얕은 복사의 2가지 특징
- 원본 ≠ 얕은 복사본 (서로 같지 않습니다.)
- 원본의 속성값 === 얕은 복사본의 속성값 (내부 속성값은 서로 같습니다.)
let original = [{ fruit: 'Apple' }, { fruit: 'Banana' }, { fruit: 'Cherry' }];
let shallowCopy = [...original]; // 얕은 복사
// let referenceCopy = original; // 이건 얕은 복사가 아닙니다. 첫 번째 특징 위반.
// shallowCopy의 첫 번째 요소를 수정
shallowCopy[1].fruit = 'Blueberry';
console.log(original); // [{ fruit: 'Apple' }, { fruit: 'Blueberry' }, { fruit: 'Cherry' }]
console.log(shallowCopy); // [{ fruit: 'Apple' }, { fruit: 'Blueberry' }, { fruit: 'Cherry' }]
이런 식으로 배열 내부가 객체들만으로 이루어졌다면(참조형), 이 배열을 얕은 복사로 카피한 배열 shallowCopy의 각 참조형 속성들은 original의 참조형 속성과 같은 참조값을 공유함으로 변경에 서로 영향을 줌.
const original = { a: 1, b: { c: 2 } };
const shallowCopy = { ...original }; // 얕은 복사
shallowCopy.a = 3;
console.log(original.a); // 1 (원본 객체는 영향 없음)
shallowCopy.b.c = 4;
console.log(original.b.c); // 4 (내부 객체는 참조를 공유함)
이 경우, 객체 내부에 기본형과 참조형 속성이 다 있는데, 얕은 복사로 기본형은 영향 없이 복사되고, 참조형은 앞서 말한대로 같은 참조 주소를 공유하기 때문에 영향이 있음.
3. 깊은 복사 (Deep Copy)
깊은 복사(Deep Copy)는 원본 객체의 모든 속성(기본형, 참조형 포함)을 완전히 복사하는 방식
얕은 복사와 달리, 깊은 복사는 참조형 데이터(객체나 배열 등)에 대해서도 새로운 메모리 공간을 할당하여 독립적인 복사본을 만들어냄. 즉, 원본 객체와 복사된 객체는 전혀 다른 메모리 공간을 사용하며, 어떤 쪽을 변경해도 다른 쪽에 영향을 미치지 않음.
- 배열 깊은 복사는 배열의 모든 요소를 새로운 배열에 복사하여 원본 배열과 복사된 배열이 완전히 서로 다른 배열을 참조하게 만든다. 깊은 복사를 하면 배열의 요소로 참조형 데이터가 있어도 한 배열을 수정해도 다른 배열에 영향을 미치지 않는다.
깊은 복사하는 방법
- 재귀적 복제
function deepCopyArray(arr) {
var result = [];
for (var i = 0; i < arr.length; i++) {
if (typeof arr[i] === 'object' && arr[i] !== null) {
// 객체나 배열이면 재귀적으로 복사
result[i] = deepCopyArray(arr[i]);
} else {
// 원시값이면 그대로 복사
result[i] = arr[i];
}
}
return result;
}
let originalArray = ['Apple', ['Banana', 'Cherry'], 'Date'];
let copiedArray = deepCopyArray(originalArray);
// 깊은 복사 후 수정
copiedArray[1][0] = 'Blueberry';
console.log(originalArray); // ['Apple', ['Banana', 'Cherry'], 'Date']
console.log(copiedArray); // ['Apple', ['Blueberry', 'Cherry'], 'Date']
말그대로 함수 내부에 본인 함수를 집어 넣어 참조형 객체가 완전 복사될 때까지(서로 다른 주소값을 가지게...) 재귀하여 함수 실행됨.
- JSON.parse, JSON.stringify 로 복사
- 깊은 복사는 가능하지만 함수나 undefined 등 JSON 으로 변경할 수 없는 프로퍼티는 무시한다. 배열도 깊은 복사가 가능하지만 배열 관련 함수는 사용 불가능하다.
let original = ['Apple', ['Banana', 'Cherry'], 'Date'];
let deepCopy = JSON.parse(JSON.stringify(original));
deepCopy[1] = 'Blueberry';
console.log(original); // ['Apple', ['Banana', 'Cherry'], 'Date']
console.log(deepCopy); // ['Apple', 'Blueberry', 'Date']
- lodash 의 cloneDeep 메소드로 복사 (깊은 복사 실전: 가장 많이 쓰임...)
- 실전용: npm(node package manager)에 있는 lodash(_) 사용
- Lodash는 자바스크립트에서 자주 사용되는 유틸리티 라이브러리로, 배열, 객체, 함수 등을 다룰 때 매우 유용한 다양한 함수들을 제공하는 라이브러리. 그중에서도 깊은 복사를 위한 함수인 _.cloneDeep은 매우 많이 쓰임. Lodash를 사용하면 복잡한 객체 구조나 참조형 데이터가 포함된 객체를 손쉽게 다룰 수 있음.
const _ = require('lodash');
let originalArray = [
['Apple', 'Banana'],
{ fruit: 'Cherry', color: 'Red' },
42,
];
// Lodash cloneDeep을 이용한 깊은 복사
let deepCopiedArray = _.cloneDeep(originalArray);
// 복사본에서 값을 변경
deepCopiedArray[0][1] = 'Blueberry';
deepCopiedArray[1].fruit = 'Blueberry';
console.log('원본 배열:', originalArray);
console.log('깊은 복사 배열:', deepCopiedArray);
- require() 함수는 모듈을 가져오는 함수로, 가져온 모듈을 변수에 할당할 수 있음.
- Lodash 라이브러리를 가져올 때 꼭 언더스코어를 사용해야 하는 것은 아니지만, 보통 언더스코어(_)를 사용하는 것이 관례임.
객체: 키(key)와 값(value)으로 이루어진 데이터를 저장하는 구조. 각 키는 문자열 또는 심볼(Symbol)로, 값은 어떠한 데이터 타입(숫자, 문자열, 함수, 또 다른 객체 등)도 가능.
객체는 주로 관련된 데이터나 속성들을 하나로 묶기 위해 사용.
- computed property : 객체의 key 값을 동적으로 정의할 수 있게해주는 JS 문법.
- 보통 객체의 키는 문자열로 고정되지만, Computed Property를 사용하면 표현식(expression)을 이용하여 키를 동적으로 생성
const propName = "age";
const person = {
name: "John",
[propName]: 30, // 'age'라는 속성명이 동적으로 생성됨
["greet" + "ing"]: "Hello", // 'greeting'이라는 속성명도 동적으로 생성됨
};
console.log(person);
// 출력: { name: 'John', age: 30, greeting: 'Hello' }
- 객체의 key를 동적으로 변경하고 싶을 때 사용함...
- 객체의 key 값은 기본적으로 문자열로 처리됩니다.
- { name: “John” } 으로 객체 데이터를 만들어도 { “name”: “John” } 로 처리 됩니다.
- Symbol: Symbol은 ECMAScript 6(ES6)에서 도입된 자바스크립트의 새로운 원시 데이터 타입(primitive data type) 중 하나. Symbol은 고유하고 변경 불가능한(immutable) 값으로, 주로 객체의 키(key)로 사용되어 이름 충돌을 방지.
- 고유성(Uniqueness): 생성된 각 Symbol은 유일(unique)하며, 같은 설명(description)을 가지고 있더라도 서로 다른 Symbol.
- 원시 데이터 타입: Symbol은 string, number, boolean 등과 같은 원시 타입 중 하나.
- 객체의 키로 사용 가능: Symbol은 객체의 키로 사용될 수 있으며, 이는 다른 코드나 라이브러리와의 프로퍼티 충돌을 방지하는 데 도움이 줌.
- 자동 형변환이 불가능: Symbol은 문자열이나 숫자로 자동 형변환되지 않음. 이를 통해 의도치 않은 타입 변환을 방지.
- 객체 수정
- dot notation은 객체 속성을 특정해서 값 집어넣음
- 객체 값에 접근하는 코드조차도 동적으로 만들고 싶다면 bracket notation으로 객체 속성 이름 변경해서 값 수정 가능.
let person = {
name: 'John',
age: 30
};
console.log(person); // { name: 'John', age: 30 }
// 객체 속성 수정
person.age = 31;
person['name'] ='James';
//let variable = "age" || "name"
//person[variable] = 'James';
console.log(person); // { name: 'James', age: 31 }
array도 object도 typeof 찍으면 object인데 어떻게 구분함?
console.log(typeof arr); // "object"
console.log(typeof obj); // "object"
isArray 메소드로 확인 가능!!!!!!
const arr = [1, 2, 3];
const obj = { name: "John" };
console.log(Array.isArray(arr)); // true
console.log(Array.isArray(obj)); // false
얕은 복사와 깊은 복사는 언제 사용하면 좋을까?
- 얕은 복사는
- 성능 (빠른 연산)이 중요하거나,
- 내부 속성이 원시값으로만 이루어져 있거나,
- 내부 속성과 상관없이 객체 자체의 참조값만 다르면 동작에 문제없는 경우 사용
- 깊은 복사는
- 내부 속성도 참조형 데이터로 이루어져 있어 중첩객체이면서 내부 속성 변경 시 원본에 영향을 주는 위험이 있는 경우,
- 그리고 depth가 2단계 이상 깊은 속성을 수정하는 것이 매우 번거로울 때 사용
💡
객체나 배열과 같은 참조형 데이터의 복사가 필요한 경우는 결국 아래와 2가지로 정리할 수 있습니다.
- 원본 유지 및 보관이 필요하고, 복사본이 필요한 경우
- 복사본의 수정이 원본에 영향을 주지 않아야 하는 경우
→ 얕은 복사 만으로 위 목적을 달성할 수 있다면, 얕은 복사를
→ 얕은 복사 만으로는 위 목적을 달성하기 어려운 경우라면, 깊은 복사를 수행합니다.
- 요즘은 깊은 복사를 하는 경우보단 필요한 만큼만 얕은 복사를 하는 방식을 취함. 얕은 복사가 성능이 더욱 높아서.
(리액트의 필수....)
배열 메서드 (Array Methods)
배열 메서드는 배열 데이터를 처리하기 위해 다양한 기능을 제공합니다. 배열 메서드는 크게 변경 메서드와 비변경 메서드로 나눌 수 있습니다.
변경 메서드 (Mutable)
변경 메서드는 배열의 원본 데이터 자체를 변경하는 메서드입니다. 따라서 주의해서 사용해야 합니다.
let fruits = ['Apple', 'Banana'];
fruits.push('Cherry');
console.log(fruits); // ['Apple', 'Banana', 'Cherry']
let fruits = ['Apple', 'Banana', 'Cherry'];
let lastFruit = fruits.pop();
console.log(fruits); // ['Apple', 'Banana']
console.log(lastFruit); // 'Cherry'
let fruits = ['Apple', 'Banana', 'Cherry'];
let firstFruit = fruits.shift();
console.log(fruits); // ['Banana', 'Cherry']
console.log(firstFruit); // 'Apple'
let fruits = ['Banana', 'Cherry'];
fruits.unshift('Apple');
console.log(fruits); // ['Apple', 'Banana', 'Cherry']
let fruits = ['Apple', 'Banana', 'Cherry'];
fruits.splice(1, 1, 'Blueberry');
console.log(fruits); // ['Apple', 'Blueberry', 'Cherry']
sort()
배열의 요소 정렬, 기본적으로 요소를 문자열로 취급하여 정렬.
sort((a,b) => a-b)
함수를 인자로 가짐(화살표 함수): 콜백함수...
- 콜백함수 이용: 함수가 일급(first-class) 객체 로 불리는 이유는 최소한의 제약으로 값처럼 다뤄질 수 있는 성질 때문입니다.
- 변수에 함수 할당,
- 함수를 인자로 전달(콜백 함수),
- 함수가 다른 함수의 반환값으로 사용됨, 함수를 배열이나 객체에 저장 가능
let fruits = ['ABCD', 'A', 'AB', 'ABC'];
fruits.sort((a, b) => a.length - b.length); // 문자열 길이를 기준으로 정렬
console.log(fruits); // ['Mango', 'Apple', 'Banana', 'Cherry']
비변경 메서드 (Immutable)
비변경 메서드는 배열 원본을 변경하지 않고, 새로운 배열을 반환하거나 값을 반환하는 메서드입니다.
- concat()
- 두 개 이상의 배열을 결합하여 새로운 배열을 반환합니다.
concat 메서드 보다 spread operator를 더욱 많이 씀. 하지만, 옛날 코드를 보게될 수도 있기 떄문에 알아두면 좋음.let fruits = ['Apple', 'Banana']; let moreFruits = ['Cherry', 'Date']; let allFruits = fruits.concat(moreFruits); console.log(allFruits); // ['Apple', 'Banana', 'Cherry', 'Date']
- slice()
- 배열의 일부를 복사하여 새로운 배열을 반환합니다.
let fruits = ['Apple', 'Banana', 'Cherry']; let citrus = fruits.slice(1, 3); console.log(citrus); // ['Banana', 'Cherry']
- join()
- 배열의 모든 요소를 문자열로 결합합니다.
let fruits = ['Apple', 'Banana', 'Cherry']; let fruitString = fruits.join(', '); console.log(fruitString); // 'Apple, Banana, Cherry'
- includes()
- 배열에 특정 요소가 포함되어 있는지 확인하고, 불리언 값을 반환합니다.
let fruits = ['Apple', 'Banana', 'Cherry']; console.log(fruits.includes('Banana')); // true console.log(fruits.includes('Date')); // false
- indexOf()
- 배열에서 특정 요소를 찾고, 그 요소의 첫 번째 인덱스를 반환합니다.
let fruits = ['Apple', 'Banana', 'Cherry']; console.log(fruits.indexOf('Banana')); // 1 console.log(fruits.indexOf('Date')); // -1
- forEach()
- 배열의 각 요소마다 순서대로 콜백함수를 실행 시킵니다.
- for문은 중간제어가 가능하나 forEach는 처음부터 끝까지 못 멈춤
-
const array1 = ['a', 'b', 'c']; array1.forEach((element) => console.log(element));
- map()
- 배열의 각 요소에 대해 주어진 함수를 호출한 결과를 모아 새로운 배열을 반환합니다.
let numbers = [1, 2, 3]; let doubled = numbers.map(n => n * 2); console.log(doubled); // [2, 4, 6]
- filter()
- 배열의 각 요소에 대해 주어진 함수를 호출한 결과가 참인 요소만 모아 새로운 배열을 반환합니다.
let numbers = [1, 2, 3, 4, 5];
let even = numbers.filter(n => n % 2 === 0);
console.log(even); // [2, 4]
객체 메서드 (Object Methods)
객체 메서드는 객체의 속성에 접근하거나 객체를 조작하는 다양한 기능을 제공합니다.
- Object.keys()ff
- 객체의 열거 가능한 속성 이름(key)들을 배열로 반환합니다.
let person = { name: 'John', age: 30, city: 'New York' }; let keys = Object.keys(person); console.log(keys); // ['name', 'age', 'city']
- Object.values()
- 객체의 열거 가능한 속성 값(value)들을 배열로 반환합니다.
let person = { name: 'John', age: 30, city: 'New York' }; let values = Object.values(person); console.log(values); // ['John', 30, 'New York']
- Object.entries()
- 객체의 열거 가능한 속성 [key, value] 쌍을 배열로 반환합니다.
let person = { name: 'John', age: 30, city: 'New York' }; let entries = Object.entries(person); console.log(entries); // [['name', 'John'], ['age', 30], ['city', 'New York']]
- Object.assign()
- 하나 이상의 출처 객체(source object)로부터 대상 객체(target object)에 속성을 복사합니다.
let target = { name: 'John' }; let source = { age: 30, city: 'New York' }; let returnedTarget = Object.assign(target, source); console.log(target); // { name: 'John', age: 30, city: 'New York' } console.log(returnedTarget); // { name: 'John', age: 30, city: 'New York' }
- Object.freeze()
- 객체를 동결(freeze)하여 더 이상 수정할 수 없게 만듭니다.
let person = { name: 'John', age: 30 }; Object.freeze(person); person.age = 31; // 무시됨 console.log(person.age); // 30
- Object.seal()
- 객체를 밀봉(seal)하여 새로운 속성을 추가하거나 기존 속성을 삭제할 수 없게 하지만, 속성의 값은 변경할 수 있습니다.
let person = { name: 'John', age: 30 };
Object.seal(person);
person.age = 31; // 변경 가능
person.city = 'New York'; // 추가 불가
delete person.name; // 삭제 불가
console.log(person); // { name: 'John', age: 31 }
'프론트엔드 부트캠프' 카테고리의 다른 글
Web APIs , DOM, 로컬 스토리지, 이벤트 처리 방식 (0) | 2024.10.18 |
---|---|
[Github 특강] Github 이용해서 협업 과정 + 팁(dev) (2) | 2024.10.17 |
[JS 기초 문법 3주차(2)] 실행 컨텍스트 (스코프, 변수, 객체, 호이스팅) (4) | 2024.10.17 |
[알고리즘 특강(4)] (1) | 2024.10.15 |
[3주차 JS 기초 문법] 데이터 타입, 메모리, 변수 선언과 데이터 할당, 얕은 복사와 깊은 복사, null/ undefined (1) | 2024.10.15 |