[JS] 참조형 타입과 메모리, 객체와 배열

y1nlog·2025년 1월 8일
0


다시 한 번 톺아보는 JS문법.

참조형 타입

참조 하면 주소 참조가 떠오르는데, 데이터 저장 시 주소값을 저장하는 객체나 배열을 생각할 수 있다.

이 때 알게 모르게 열ㅡ일하고 있던 친구가 있는데, 그게 사실은 컴퓨터 뜯으면 있는 시금치 라는 거다.

아 시금치

생물 시금치 말고 이런 시금치 되시겠다.
이 친구가 바로 메모리란 건데, 누구든 컴퓨터에 하나씩 (?) 들어있는 RAM을 생각하면 좋다.

메모리

변수 선언, 데이터 할당, 메모리, 주소값 등등
개발을 처음 시작하다 보면 배울 게 참 많았는데, 그 중 메모리에 대해 잠시 짚어보자.

메모리(주기억장치)는 RAM과 같은 저장소를 의미하고, RAM이란 Random Access Memory의 약어라고 할 수 있음.

컴퓨터 메모리는 모든 컴퓨팅 시스템의 필수적인 부분으로, 빠른 액세스 또는 검색을 위해 데이터가 임시 또는 영구적으로 유지되는 중개 스토리지 공간의 역할을 한다.
(출처: 퓨어스토리지)

우리가 변수에 값을 할당하면서, 그 값들이 다 어디에 저장되어 있는지는 잘 모르지만.. 컴퓨터 내부의 프로세스는

간단히 생각했을 때

  1. 변수 선언됐다! 우왕 메모리에 저장 (변수이름만을 위한 공간)
  2. 변수 값 들어왔다! 우왕 메모리에 저장 (데이터만을 위한 공간)

이건데, 이제 객체를 선언한다거나 배열을 선언하게 되면 저기서 좀 달라지는 거라고 할 수 있겠지.

위에서도 각각의 요소가 저장되는 구역이 다르다고 슬쩍 남겨보았는데, 그거의 연장선이다.

만일 객체를 선언한다 치면,
키값은 키 공간에 주르륵 저장하고,
각 데이터도 데이터 공간에 저장하고,
그 데이터의 주소값을 가져와서 키에 달아줌.

복잡시럽지만 무튼...!
배열/객체와 같은 참조형 데이터는 값을 직접 가지지 않고, 해당 값이 저장된 메모리 주소를 가리킨다.

JavaScript - 직접적인 메모리 주소를 다루지 않으나, 내부적으로는 참조형 데이터가 메모리 주소를 참조한다.

그래서 자바스크립트에선 메모리가 어떻게 쓰이길래?

자바스크립트에는 두가지 메모리영역(저장영역)이 존재함.
자료구조(?)라 할 수 있는 스택과 힙.

Stack

  • 저장되는 값의 유형 : 원시 값 및 참조
  • 메모리 크기 : 컴파일 타임에 크기를 알 수 있음
  • 메모리 할당 방식 : 고정 크기의 메모리 할당

Heap

  • 저장되는 값의 유형 : 객체, 배열, 함수
  • 메모리 크기 : 런타임에 크기를 알 수 있음
  • 메모리 할당 방식 : 객체 당 제한 없음


출처

변수 선언, 객체, 값 등을 할당함에 있어서 어딘가에 저장해 둔 값을 가져다 쓰는 것임을 알고 있자.
그 저장 타입이 Stack/Heap이라는 것 까지!

자료형 / 배열과 객체

배열 Array

같은 타입의 데이터를 여러 개 가지고 있는 자료형

배열의 구성요소

  • value
  • length
  • index

    index 시작 숫자가 0부터인 이유!
    주소값을 통하여 참조하는 메모리의 시작점을 0이라고 표현하기 때문이다.

객체

Object는 {} 중괄호로 묶인 친구들.
특징은 늘 key-value 쌍이라는 것.

메모리에 데이터를 어떻게 저장하는지는 위에서 조금 다루었으니 패스해보겠다.

얕은 복사 / 깊은 복사

객체나 배열을 Copy하는 일이 더러 있다.
보통 생각하는 코드인 let b = a 와 같이, 기존 a의 값을 새로운 변수인 b에 할당하려고 하면, 겉보기엔 "잘 복사됐군!" 생각할 수 있겠다.

하지만 메모리 내부를 들여다 봤을 때는, 해당 값이 저장된 (메모리) 주소가 같다. 그냥 둘이 완전 똑같은 객체(배열)이라는 거다.

그래서 "아~ 원본 a는 그대로 두고, b만 복사해서 수정해야겠다!" 생각하고서 배열/객체를 이렇게 복사해서 수정한다면?

원본도 같이 수정되어버리는 Magic ⭐️

이런 문제를 해결하면서, 기존의 배열이나 객체를 어떻게 복사할 수 있을까? 의 해답이 깊은 복사.

값은 동일하지만, 비하인드를 들춰보자면 값이 저장된 주소가 다르다:)
그래서 깊은 복사로 생성한 배열/객체는 아무리 수정을 해도 원본은 그대로 유지된단 점.

깊은 복사는 immutable(비변경)이 필요할 때 사용하게 됨.
얕은복사를 하게 되면, 원본주소값이 그대로 불려오므로 2개 객체가 같이 변경되거든요...

깊은 복사 방법 정리

  • 재귀적 방법 : 재귀함수 이용

  • JSON.parse / JSON.stringify
    : string화 시켜서 새로운 배열로 만들기
    parsing = 새로운 다른 자료구조를 만들기
    let deepCopy = JSON.parse(JSON.stringify(original));

    잠깐, JSON 방식의 한계점
    object 안의 메소드/함수 등은 복사가 되지 않는다.
  • lodash / cloneDeep 메소드를 이용한 복사

  • structuredClon() 전역 함수
    : JS 내장 깊은복사 메소드

객체의 비공개 속성 관리

  • 객체의 key 는 문자열 또는 Symbol 타입이 가능

메소드와 함수의 차이?
함수는 독립적으로 사용가능하지만, 메소드는 종속적으로 사용하게 된다.

배열 메소드

많이 쓰는 만큼 익숙해질수 있도록!
원본 배열의 수정 여부에 따라서 변경 메소드 (mutable) / 비변경 메소드 (immutable)로 나눈다.

변경 mutable

  • push() : 마지막 위치에 (하나 이상의) 요소 삽입, 새로운 배열의 길이 반환
  • pop() : 마지막 위치의 요소 삭제 후, 그 요소를 반환
  • shift() : 첫번째 위치의 요소 삭제 후, 그 요소를 반환
  • unshift() : 첫번째 위치에 (하나 이상의) 요소 삽입, 새로운 배열의 길이 반환
  • splice(index,deleteCount,new) : 배열의 기존 요소를 삭제/교체/새 요소를 추가하
  • sort() : 정렬 메소드

    정렬 시의 인자로 콜백함수를 받는데, 그 자리에는 compareFunction을 넣어준다. numbers.sort((a,b) => a-b);
    (UNICODE 기반. 숫자정렬 시엔 compareFn을 넣어주자)

이 중에 실행 속도가 제일 빠른 것은 push/pop.
맨 뒤 인덱스에 접근하는 게, 앞쪽으로 가는 것보다 빠르겠죠?
Stack 생각 중...

비변경 immutable

  • concat : 배열 합치기
  • slice(start,end) : start 부터 end 직전의 요소까지 복사해 새로운 배열로 반환
  • join : 배열 내 모든 요소를 문자열로 결합
  • includes : 배열에 특정 요소가 포함되어 있는지 확인 -> Boolean 반환
  • indexOf : 배열에서 특정 요소를 찾고, 그 요소의 첫 번째 인덱스 반환
  • forEach : 배열 내 각 요소마다 순서대로 콜백함수 실행
  • map : 배열의 각 요소에 대해 주어진 함수를 호출 & 그 결과를 모아 새로운 배열로 반환
  • filter : 배열의 각 요소에 대해 주어진 함수를 호출 & 결과가 참인 것만 모아 새로운 배열로 반환

객체 메소드

  • keys : key 값을 배열로 반환
  • values : value 값을 배열로 반환
  • entries : key-value 쌍을 배열로 반환
  • assign : 하나 이상의 출처 객체로부터 대상 객체에 속성 복사
  • freeze : 객체를 동결 - obj 변경이 불가능해짐. for 상수 표현을 위해 사용
  • seal : 객체를 밀봉 - 새로운 속성 추가/삭제 불가능. 하지만 속성값은 변경 가능
profile
FrontEnd Developer

0개의 댓글