[JS] DeepDive(11) 원시 값과 객체의 비교
FrontEnd/Deep Dive

[JS] DeepDive(11) 원시 값과 객체의 비교

728x90

이전 "데이터 타입" 에 대해서 공부했었듯이 JS에서 제공하는 데이터 타입은 원시타입과 객체 타입으로 나누어져 있다. 이 두 타입을 비교하면서 한번 알아보자.

 

 

 

원시타입과 객체 타입은 크게 세가지 측면에서 다르다.

 

1. 원시 타입의 값, 즉 원시 값은 변경 불가능한 값이다. 이에비해 객체(참조) 타입의 값, 즉 객체는 변경 가능한 값이다.

2. 원시값을 변수에 할당하면 변수에는 실제 값이 저장되지만 객체를 변수에 저장하면 변수에는 참조값이 저장된다.

3. 원시값을 갖는 변수를 다른 변수에 할당하면 값에의한 전달 즉 깊은복사가 진행되지만 객체를 가리키는 변수를 할당하면 참조에 의한 전달 즉 얕은 복사가 진행되게 된다.

 

 

원시 값 

 

원시 값은 변경 불가능한 값이다. 한번 생성된 원시값은 읽기 전용이란 말이다.

 

여기서 조금 생각해야할 점은 변수의 값을 바꿀수 없다는 말이 아니다. 변수는 당연히 재할당을 통해서 값을 변경해줄 수 있다. 여기서 상수의 개념과 변수 개념의 차이를 짚고 가보자.

 

상수는 재할당이 금지된 변수로써 상수와 변수 모두 값을 저장한다. 단 둘의 차이는 재할당을 할 수 있는 지 없는지 차이에서 나오게 된다.

 

 

 

 

 

위 그림에서 알 수 있듯이 변수가 참조하던 메모리 공간이 재할당이 이뤄질대마다 변경되는 이유는 변수에 할당된 원시 값이 변경 불가능한 값이기 때문이다.

 

이러한 값의 특성을 불변성이라 한다.

 

 

 

문자열과 불변성

 

원시값인 문자열은 다른 원시 값과 비교할 때 독특한 특징이 잇다. 문자열은 0개 이상의 문자로 이루어진 집합을 의미한다. 1개 문자가 2바이트 메모리 공간에 저장되게 되므로 몇 개의 문자로 이뤄졌느냐에 따라서 필요한 메모리가 달라지는 조금 특이한 타입이다.

 

 

숫자를 저장할때는 1이나 1000000이나 똑같은 8바이트가 필요하지만 문자열은 그렇지 않다.

 

따라서 C같은 언어는 하나의 문자를 위한 데이터 타입만 존재하고 문자열 타입이 따로 존재하지 않는다. JS는 개발자의 편의를 위해 문자열 타입을 제공한다.

 

이점은 Js에서 가장 장점 중 하나이다. 

 

 

 

정리해보자면 JS에서 문자열은 원시타입이기때문에 변경이 불가능하다.

 

 

문자열 자체를 재할당 하는것은 가능하지만, 문자열을 변경하는건 불가능하다는 것이다. C와같은 언어를 해본 사람이라면 문자열을 배열 다루듯이 문자에 접근하는 것을 해보았을 것이다.

 

 

 

var str = 'string';

// 문자열은 유사 배열이므로 배열과 유사하게 인덱스를 사용해 각 문자에 접근할 수 있다.



// 하지만 문자열은 원시값이므로 변경할 수 없다!
str[0] = 'T';

console.log(str); // string

 

하지만 위와같이 JS에서는 인덱스를 활용해서 문자열에 접근하는건 가능하지만 교체하는건 불가능하단 특성이 있다.

 

 

 

값에 의한 전달

 

 

 

var score = 90;
var copy = score;

console.log(score); // 90
console.log(copy);  // 90

score = 100

console.log(score); // 100
console.log(copy);  // 90

 

 

조금 전에 변수는 값에의한 전달이 이루어진다고 했다. 위 예시를 보면 이해가 빠를 것이다.

 

 

copy라는 변수에 score의 값을 복사해서 받아온다고 생각하면 편할 것이다.

 

 

 

 

객체

 

객체는 프로퍼티의 개수가 정해져 있지 않으며 동적으로 추가되고 삭제할 수 있다. 또한 프로퍼티의 값에 대한 제약도 없기 때문에 원시 값과는 달리 메모리공간의 크기를 사전에 정해 둘수가 없다.

 

객체는 복합적인 자료구조이므로 객체를 관리하는 방식이 원시 값과 비교해서 복잡하고 구현 방식또한 브라우저 제조사마다 다를 수 있음을 알아두자.

 

 

사실 자바스크립트의 객체는 키를 인덱스로 사용하는 해시 테이블 구조라고 생각해도 괜찮다.

 

 

대부분 JS엔진은 해시 테이블과 유사하지만 높은 성능을 위해 더 나은 방법으로 객체를 구현한다. 

 

 

보통 자바, C++ 과 같은 클래스 기반 객체지향 프로그래밍 언어는 사전에 정의된 클래스를 기반으로 객체를 생성한다. 따라서 객체가 생성된 이후에는 프로퍼티를 삭제하거나 추가할 수 없다.

 

하지만 JS는 이와 다르게 프로퍼티와 메서드를 추가하는것이 가능하다. 

 

해당 방식은 사용하기엔 편리하지만 성능 면에서는 이론적으로 클래스 기반 객체보다 객체 생성과 프로퍼티 접근에 비용이 더 많이드는 비효율적인 방식임은 알아두자.

 

 

이런 문제를 해결하기 위해서 V8 JS엔진에서는 프로퍼티에 접근하기 위해서 동적 탐색 대신 히든 클래스라는 방식을 사용하여 C++ 객체의 프로퍼티에 접근하는 정도의 성능을 보장한다.

 

 

변경 가능한 값

 

객체 타입의 값 즉 객체는 변경 가능한 값이다. 

 

 

var person = {
  name : 'mingyu'
}

 

위와같이 객체를 선언하면 아래와 같은 일이 일어난다.

 

 

 

 

 

 

 

계속 설명하고 있지만 원시값은 변경 불가능한 값이므로 원시 값을 갖는 변수의 값을 변경하려면 재활당을 해야만 했다. 하지만 객체는 변경 가능한 값으로 재할당 없이 객체를 직접 변경하는것이 가능하다.

 

 

var person = {
  name : 'mingyu'
}

person.name = 'Kim';

person.address = 'Seoul';

console.log(person) // {name:'Kim' , address : 'Seoul' }

 

 

 

객체는 이러한 성질 때문에 객체 자체를 생성하고 관리하는 방식은 매우 복잡하다. 조금 쉽게 말하면 객체를 많이 사용하게 되면 메모리의 효율적 소비가 어렵고 성능이 나빠진다.

 

 

 

이런 이유 때문에 객체를 변경 가능한 값으로 설계되어 있다.

 

 

단 이런 문제로 부작용이 발생하기도 하는데, 여러개의 식별자가 하나의 객체를 공유하는 경우가 생긴다는 것이다.

 

 

 

 

위 그림처럼 때때로 두개의 식별자가 한개의 객체를 가리켜서 copy를 바꾸지 않고 person에 접근해서 값을 변경한 경우에도 copy에 접근했을때 값이 변경되어 있는 경우가 존재한다.

 

 

 

이런 문제를 해결하기 위해서 스프레드 문법을 활용해 볼 수 있다. 하지만 스프레드 문법의 경우 객체를 한 단계까지만 복사해올 수 있음을 알아두자. 해당 문법도 나중에 자세히 한번 다뤄볼 것이다.

 

const o = { x: { y: 1 } };

// 얕은 복사
const c1 = { ...o };       // 스프레드 문법
console.log(c1 === o);     // false
console.log(c1.x === o.x); // true

// lodash의 cloneDeep을 사용한 깊은 복사
// "npm install lodash"로 lodash를 설치한 후, Node.js 환경에서 실행
const _ = require('lodash');
// 깊은 복사
const c2 = _.cloneDeep(o);
console.log(c2 === o);     // false
console.log(c2.x === o.x); // false

const v = 1;

// "깊은 복사"라고 부르기도 한다.
const c1 = v;
console.log(c1 === v);     // true

const o = { x: 1 };

// "얕은 복사"라고 부르기도 한다.
const c2 = o;
console.log(c2 === o);     // true

 

 

 

중요한점은 아래와 같다.

 

 

 

값에 의한 전달과 참조에 의한 전달은 식별자가 기억하는 메모리 공간에 저장되어 있는 값을 복사해서 전달한다는 점에서는 동일하다. 단, 식별자가 기억하는 변수에 저장되어 있는 값이냐 참조값이냐의 차이만 존재한다.

 

 

엄연히 말하면 자바스크립트에는 참조에 의한 전달은 존재하지 않고 값에 의한 전달만이 존재한다고 말할 수 있다.

 

따라서 JS의 이 같은 동작방식을 설명할때 "공유에 의한 전달" 이란 표현을 사용하기도 하나 ECMAScript 사양에 정의된 JS의 공식적인 용어는 아니며 완벽한 JS의 동작방식을 설명하지는 못한다.

 

 

조금 쉽게 풀이하면 JS에는 포인터라는 개념이 존재하지 않기때문에 다른 언어의 참조에 의한 전달과는 의미가 조금 다르다는 것만 기억하고 가자.

 

 

 

 

 

 

객체 자체는 어느정도 알고 있었다고 생각했는데 내가 놓쳤던 부분들이 많았던 것 같고 이번 기회에 JS에서 객체의 구조에 대해서 조금 더 확실히 알고 가게 된 것 같다.

 

 

 

 

 

 

728x90

'FrontEnd > Deep Dive' 카테고리의 다른 글

[JS] DeepDive(13) 스코프  (0) 2023.08.01
[JS] DeepDive(12) 함수  (0) 2023.07.31
[JS] DeepDive(10) 객체 리터럴  (0) 2023.07.27
[JS] DeepDive(9) 타입 변환과 단축 평가  (0) 2023.07.26
[JS] DeepDive(8) 제어문  (0) 2023.07.22