2022. 10. 4. 21:41ㆍFE/JavaScript
데이터 타입은 크게 원시 타입과 객체(참조) 타입으로 구분할 수 있다.
원시 타입과 객체 타입으로 구분하는 이유는 세 가지 측면에서 다르다.
- 원시 값은 변경 불가능한 값인 반면 참조 타입은 변경 가능한 값이다.
- 원시 값을 변수에 할당하면 확보된 메모리 공간에 실제 값이 저장되는 반면 객체를 변수에 할당하면 참조 값이 저장된다. → 실제로 모든 변수는 주소값을 저장한다.
- 원시 값을 갖는 변수를 다른 변수에 할당하면 원본의 원시 값이 복사되어 전달되는 반면 객체를 가리키는 변수를 다른 변수에 할당하면 원본의 참조 값이 복사되어 전달된다.
원시 값 - 변경 불가능한 값
한번 생성된 원시 값은 읽기 전용 값이다.
원시 값을 변경 할 수 없다는 것과 원시 값을 저장하는 변수를 변경할 수 없다는 것과 혼동하면 안된다.
let str = "bear";
str[0] = 'p';
console.log(str); // pear가 아닌 bear로 변경 되지 않았다, 에러도 발생하지 않는다.
string은 대표적인 원시 값으로 값을 변경하려 했지만 변경되지 않는 것을 확인 할 수 있다.
let str = "bear";
str = "pear";
console.log(str);
이는 변수를 재할당한 것일뿐 값 자체를 변경한 것이 아니기 때문에 가능하다.
var x; // 변수 선언
console.log(x); // undefined 출력
var 키워드로 변수를 선언과 동시에 초기에 변수는 undefined 값이 저장된 메모리 주소를 가리킨다.
var x = 100;
console.log(x); // 100 출력
변수를 선언하면 초기에 undefined가 할당된 상태에서 100이라는 값을 대입한다면
변수가 100이라는 값이 들어있는 메모리 주소를 가리킨다.
이처럼 새로운 값으로 재할당이 되면 새로운 메모리 공간을 확보한다.
재할당이 되면서 이전 값이 있는 메모리 주소는 참조되지 않는다.
아무도 참조하고 있는 않는 메모리는 가비지 컬렉터에 의해 자동 해제된다.
원시 값을 저장하려면 메모리 공간의 크기를 결정해야 하기 때문에 원시 타입별로 메모리 공간의 크기가 미리 정해져 있다고 한다. 물론 변수 자체는 주소값이 들어가기 때문에 메모리 크기는 일정할 것으로 생각된다.
ECMAScript 스펙에 문자열 타입 (2바이트)과 숫자 타입(8바이트) 이외의 원시 타입은 크기를 명확히 규정하고 있지 않고 브라우저 제조사 구현에 따라 다를 수 있다고 한다.
문자열은 원시 값 중에서 유사 배열 객체이면서 이터러블하여 인덱스로 값에 접근할 수 있고 배열 객체와 동일하게 length 프로퍼티를 갖는다.
※ 원시 값을 객체처럼 사용하면 원시 값을 감싸고 있는 Wrapper 객체로 자동 변환된다고 한다.
Wrapper 객체는 Number, String, Boolean, Symbol 가 있다.
원시 값 - 값에 의한 전달
var x = 100;
var y = x;
x = 200;
console.log(x, y);
변수 y에 변수 x 를 대입하면 기존 100이 있는 메모리 말고 새로운 100을 담는 메모리 공간이 확보된다. 이후 y는 이 메모리 주소를 가리킨다. 변수에 원시 값을 갖는 변수를 할당하면 원시 값이 복사되어 전달된다.
변수 x, y 는 값 자체는 같은 메모리를 가리키지만 메모리 주소가 다르다.
실제 JS엔진 내부 동작은 명확하게 정의되어 있지 않아 엔진을 구현하는 제조사에 따라 동작 방식이 다를 수 있다고 한다...
원시 값이 같으면 같은 메모리 주소를 가리키고 있도록 동작할 수도 있다.
결론은 원시 값이 같은 두 변수의 값이 변경되어도 서로 영향을 주지 않는다는 것이다.
객체 - 변경 가능한 값
객체는 프로퍼티가 동적으로 추가될 수 있기 때문에 원시 값처럼 확보해야 할 메모리 공간의 크기를 정할 수 없다.
객체는 원시 값과 다른 방식으로 동작하도록 설계 되어 있다.
자바스크립트 객체는 프로퍼티 키를 인덱스로 사용하는 해시 테이블과 유사한 형태로 구현한다고 한다.
V8 JS 엔진에서는 프로퍼티에 접근하기 위해 히든 클래스라는 방식을 사용해 C++ 객체 프로퍼티 접근하는 정도의 성능을 보장한다고 한다.
var foo = { // 할당이 이루어진 시점에 객체 리터럴이 해석되고 객체가 생성된다.
a : "apple",
b : "bear",
c : "carrot",
};
foo.b = "pear"; // 객체는 변경이 가능한 값이기 때문에 갱신이 가능하다.
객체를 할당한 변수에는 힙 영역을 가리키는 메모리 주소를 가리킨다.
해당 메모리에 가면 힙 메모리에 있는 객체의 주소가 있고 이 주소로 객체에 접근 할 수 있다.
메모리 영역이 다르기 때문에 객체를 저장한 변수는 하나의 주소를 통해 직접 접근하지 못한다는 것을 알 수 있다.
객체를 할당한 변수에는 힙 메모리를 참조하는 주소를 갖고 있지 객체를 참조하는 주소를 갖고 있지 않다.
객체의 프로퍼티를 변경하여도 foo는 객체가 있는 힙 메모리 주소를 참조하고 있는 주소를 참조하고 있는 건 변함이 없다.
힙 영역에서 변경하였기 때문에 변수 foo에 재할당한 것이 아니라 객체는 변경이 가능한 값이라고 하는 것 같다.
객체는 원시 값처럼 값을 복사해서 새롭게 생성하는 방식을 택하면 원시 값보다 크기가 크기 때문에 비용이 많이 들고 성능이 나빠져 원시 값과 다르게 설계되었다고 이해하면 되겠다.
객체 - 참조에 의한 전달
const person = {
name : "Lee",
}
const copy = person;
person라는 변수는 객체가 있는 힙 메모리를 가리키는 참조값을 갖는 메모리의 주소를 저장하고 있다.
copy라는 변수는 동일한 객체가 있는 힙 메모리를 가리키는 참조값을 갖는 메모리 주소를 저장하는데 기존의 메모리와 다르다.
객체를 참조하는 값은 동일하나 서로 다른 메모리에 저장되어 있고 person과 copy는 서로 다른 메모리를 참조하고 있는 것이다.
객체를 가리키는 변수를 다른 변수에 할당하면 객체를 참조값이 복사되어 전달되기 때문에 여러 개의 식별자가 하나의 객체를 공유할 수 있게 된다. 할당해도 동일한 참조값을 갖는 서로 다른 메모리만 만들어질뿐 모두 동일한 객체를 가리킨다.
원시 값과 참조 값 대입
원시 값이든 참조 값이든 대입을 하면 식별자가 기억하는 메모리 공간에 저장되어 있는 값을 새로운 메모리에 확보해서 값을 복사해서 전달된다는 것을 알 수 있다.
변수에 저장되어 있는 값이 원시 값이냐, 참조 값에 따라 깊은 복사, 얕은 복사가 되는 것이다.
'FE > JavaScript' 카테고리의 다른 글
[Javascript] 스코프 (0) | 2022.10.07 |
---|---|
[Javascript] 함수 (0) | 2022.10.07 |
[Javascript] 객체 리터럴 (0) | 2022.09.29 |
[JavaScript] 타입 변환과 단축 평가 (0) | 2022.09.11 |
[JavaScript] 제어문 (0) | 2022.09.10 |