자바스크립트의 객체는 원시 타입과 객체 타입으로 구별할 수 있습니다!
이것을 구별하는 이유는, 서로 근본적으로 다르기 때문에 구분하는 것이다. 그 이유를 같이 한 번 살펴보자.
- 원시 타입의 값, 원시 값은 변경 불가능하다. 이에 비해 객체 타입의 값은 객체는 변경 가능한 값이다.
- 원시 값을 변수에 할당하면, 확보된 메모리 공간인 변수에는 실제 값이 저장된다. 이에 비해 객체를 변수에 할당하면, 확보된 메모리 공간, 변수에는 참조 값이 저장된다.
- 원시 값으 가지는 변수를 다른 변수에 할당하면, 원본의 원시 값이 복사되어 전달된다. (값에 의한 전달), 객체를 가리키는 변수를 다른 변수에 할당하면 참조 값이 복사되어 전달된다. (참조에 의한 전달)
const o = {}; //const를 이용하여 선언한 변수는 재할당 금지!
o.a = 1; //하지만 const 키워드를 사용하여 선언한 변수에 할당한 '객체'는 변경 가능!
//만약 const 키워드로 '변수'가 할당되었으면 원시 값 변경 불가능하였겠지.
console.log(o) //{a:1}
메모리 공간의 주소가 변경되는 이유는, 변수에 할당된 원시 값이 변경 불가능한 값이기 때문이다. 값의 이러한 특성을 바로바로~~
불변성!
을 가지고 있다고 한다.불변성을 가지고 있는 원시 값을 할당한 변수는 재할당 이외의 변수의 값을 변경할 수 있는 방법이 없다!!
마음대로,, 예상치 못하게 값이 바뀌게 되면, 상태변경을 추적하기 어려워지니까 이러한 특성이 만들어진 것이다.
원시 값을 저장하기 위하여는 확보해야 하는 메모리 공간의 크기를 결정해야 한다. 이를 위하여 원시 타입별로 메모리 공간의 크기가 미리 정해져있다.
그러나 문자열을 다룰 때 주의해야 할 점이 있다. 숫자 값은 1이나, 100000나 동일한 8바이트가 필요한데, 문자열은 몇 개의 문자로 이루어졌느냐에 따라서 필요한 메모리 공간의 크기가 결정된다
옛날에 C언어를 공부하였을 때에는, 문자열 처리를 문자의 배열로 처리하였다. 그런데 자바스크립트는 개발자의 편의를 위하여 원시 타입인 문자열 타입을 제공한다.
자바스크립트의 문자열은 원시 타입이며, 변경 불가능한 값이 된다.
그래서 이 친구도 새로운 것을 할당하고 싶다면, 새로운 단어를 메모리에 생성하고, 식별자는 새로운 메모리 주소를 가리키도록 변경된다.
var str = 'string';
//문자열은 유사 배열 객체로 다룰 수 있다. 그래서 각 문자에 접근이 가능하다!
//하지만 문자열은 원시 값이므로 변경이 불가능하다. (에러 발생은 안함)
str[0] = 'S';
console.log(str); //string
위와 같이 변경을 시도하여도 반영되지 않는다. 한번 생성된 문자열은 변경 불가능하기 때문이다. 변수에 새로운 문자열을 할당하는 것은 위에 언급하였던 것과 같이 메모리 공간을 새로 만들기 때문에 가능하다.
유사 배열 객체는 배열처럼 인덱스로 프로퍼티 값에 접근 가능하고, length
프로퍼티를 가지는 것을 말한다. 원시 값을 객체처럼 사용하면, 원시 값을 감싸는 래퍼 객체로 자동 변환된다. 그래서 원시 값인 문자열을 배열처럼 사용할 수 있는 것이다.
var score = 80;
//copy 변수에는 score값의 변수 80이 복사되어서 할당된다.
var copy = score;
여기서 score
변수와 copy
변수는 동일한 값 80을 가지게 된다. 그러나 score
변수와 copy
변수의 값 80은 다른 메모리 값에 저장된 별개의 값깅다. 그렇기 때문에 score
변수의 값을 변경시켜도, copy
의 값에는 아무런 변화가 없다.
그런데 실제 자바스크립트 엔진에는 이렇게 관리되고 있지 않을 수 있다. ECMAScript 사양에는 변수가 어떻게 메모리를 관리하는지 명확하게 정의되어 있지 않기 때문에 제조사에 따라서 바뀔 수도 있기 때문이다. 변수의 메모리 관리는 아래와 같이 관리될 수 있다.
사실, 엄격하게 표현하면 값에 의한 전달이라는 용어에 오해가 생길 수 있다.
변수와 같은 식별자는 값이 아니라 메모리 주소를 기억하고 있기 때문에, 값이 전달되는 것이 아니라 메모리 주소가 전달되기 때문에 오해를 줄여야 한다고 한 것!
식별자는 값이 아니라 메모리 주소를 기억하고 있고, 메모리 주소를 통하여 메모리 공간에 저장된 값에 접근할 수 있기 때문이다. 식별자는 메모리 주소에 붙인 이름이다.
그래서 결론적으로 하고 싶은 말이 뭐냐고... 정리하자면 다음과 같다.
두 변수의 원시 값은, 변수에 원시 값을 할당하든 시점이든, 두 변수 중 어느 하나의 변수에 값을 재할당하는 시점이든 서로 다른 메모리 공간에 저장된 별개의 값이 되어서 한쪽에서 재할당을 통하여 값을 변경해도 서로 간섭할 수 없는 존재가 되었다.
객체는 프로퍼티의 개수도 정해져 있지 않고, 동적으로 관리될 수 있다. 그래서 원시 값과 같이 확보해야 할 메모리 공간을 사전에 정해둘 수 없다. 그래서 객체를 관리하는 방식이 원시 값과 비교하여 복잡하고, 구현 방식도 다를 수 있다. 그래서 당연히 원시 값과는 다른 방식으로 동작하도록 설계되어 있다.
C++을 공부해본 입장에서, 객체를 관리할 때에는 사전에 클래스를 정의하였다. 그리고 그 클래스를 기반으로 인스턴스를 생성하여 만들어진 그대로 객체를 생성하곤 했다.
그래서 동적으로 프로퍼티를 삭제하거나, 추가할 수 없었다.
그러나 자바스크립트는 클래스 없이 객체를 생성할 수 있고, 동적으로 프로퍼티와 메서드를 추가할 수 있다. 편리하지만, 성능 면에서는 객체지향 프로그래밍 언어의 객체보다 객체 생성, 프로퍼티 접근에 비용이 많이 든다.
그래서 V8 자바스크립트 엔진에서는 동적 탐색
대신에, 히든 클래스
라는 방식을 사용한다. 히든 클래스는 고저된 객체 클래스와 유사하게 동작한다.
변경 가능하다
객체를 할당한 변수에는 생성된 객체가 실제로 저장된 메모리 공간의 주소가 저장되어 있다. 이를 참조 값이라고 한다. 객체는 변경 가능한 값이기 때문에, 객체를 할당한 변수는 재할당 없이 객체를 직접 변경할 수 있다. 재할당 없이 프로퍼티를 동적으로 추가할 수 있고, 프로퍼티 값을 갱신할 수 있으며, 프로퍼티 자체를 삭제할 수 있다. 그래서 객체를 할당한 변수의 참조 값은 변경되지 않는다.
여러 개의 식별자가 하나의 객체를 공유할 수 있다
var person = {
name : 'Lee'
};
//참조 값을 복사(얕은 복사)
var copy = person;
객체를 가리키는 변수를 다른 변수에 할당하면, 원본의 참조 값이 복사되어 전달된다. 이를 참조에 의한 전달이라고 한다.
copy에는 person의 참조 값을 복사하여 저자된다. 그래서 person과 copy가 저장된 메모리 주소는 다르지만, 동일한 참조 값을 가진다. 어느 한쪽에서 객체를 변경하면 서로 영향을 주고받을 수 있다.
값의 의한 전달과 참조에 의한 전달은 식별자가 기억하는 메모리 공간에 저장되어 있는 값을 복사하여 전달한다는 면에서는 동일하다. 다만, 식별자가 기억하는 메모리 공간에 변수에 저장되어 있는 값이 원시 값이냐, 참조 값이냐의 차이만 있을 뿐! 따라서 자바스크립트에는 참조에 의한 전달은 존재하지 않고, 값에 의한 전달만이 존재한다고 말할 수 있다.
그래서 자바스크립트에서는 공유에 의한 전달
이라는 표현을 사용하기도 한다. 이러한 주의점에 대해서 항상 생각해보자. (공식적인 용어는 아니라는 것을 주의!)
var person1 = {
name : 'Lee'
};
var person2 = {
name : 'Lee'
};
console.log(person1 === person2);
console.log(person1.name === person2.name);
===
연산자는 변수에 저장되어 있는 값을 타입 변환하지 않고 비교한다!
person1
과 person2
의 객체는 내용은 같지만, 다른 메모리에 저장된 별개의 객체이니까 첫번째 console값은 false
가 찍히고, 두번째 console은 값으로 평가되어 비교되니까 Lee
로 평가되어, true
로 평가된다.