기본형 | 참조형 |
---|---|
- 할당이나 연산시 복제 - 값이 담긴 주솟값을 바로 복제 - 불변성 | - 할당이나 연산시 복제 - 값이 담긴 주솟값들로 이루어진 묶음을 가리키는 주솟값을 복제 |
컴퓨터는 모든 데이터를 0, 1로 바꿔 기억한다. 0 또는 1로 표한할수 있는 하나의 메모리 조각을 비트(bit)
라고 한다.
각 비트는 고유한 식별자를 통해 위치를 확인 할수 있다. 바이트 역시 시작하는 비트의 식별자로 위치를 파악 할수 있다.
1byte = 8bit
모든 데이터는 바이트 단위의 식별자 즉, 메모리 주솟값을 통해 서로 구분하고 연결할 수 있다.
변수 : 변할수 있는수 (변할수 있는 데이터)
식별자 : 어떤 데이터를 식별하는데 사용하는 이름, 즉 변수 명
var a;
위의 예제를 말로 풀자면 "변할수 있는 데이터를 만든다. 이 데이터의 식별자는 a로 한다". 이다.
결국 변수란 변경 가능한 데이터가 담길수 있는 공간 또는 그릇이다.
1.
var a; // 변수 a 선언
a = 'abc'; // 변수 a에 데이터 'abc'할당
2.
var a = "abc" // 변수 선언과 할당을 한문장으로 표현
위와 같은 선언과 할당방법은 ?
1. 메모리에서 비어 있는 공간을 확보하고 그 공간의 이름을 a
라고 이름을 설정
2. 이름 a
를 가진 주소를 검색해 그곳에 문자열 abc
를 할당 한다.
그런데 실제로 해당 위치에 abc
를 직접 저장 하지 않는다. 데이터 저장을 위해 별도의 메모리 공간을 다시확보해 문자열 abc
를 저장하고, 그 주소를 변수 영역에 저장하는 식으로 이루어 진다.
직접 값을 대입하지 않고 번거롭계 한단계를 더 거치는 이유는?
데이터 변환을 자유롭게 할 수 있게 함과 동시에 메모리를 더욱 효율적으로 관리하기 위한 결과 이다.
var a = 5;
var b = 5;
b = 7;
기본형 데이터는 모두 불변값이다.
위의 예제를 보면 변수 a,b 는 숫자 5를 할당 한다. 만약 5라는 숫자가 없다면 데이터 공간을 하나 만들어 저장하고, 있으면 해당 데이터주솟값을 저장한다.
마지막 줄에 b는 다시 7을 할당하는데 5 자체를 7로 바꾸는 것이 아니라 기존에 저장했던 7을 찾아서 있으면 재활용 하고, 없으면 새로 만들어 b에 저장한다. 결국 5 와 7 모두 다른값으로 변경할수 없다.
이와 같이 한번 만들어진 값은 가비지 컬렉팅을 당하지 않는한 영원히 변하지 않는다.
참조형 데이터는 성질에 따라 변경할수도 불변값 일수도 있다.
var obj1 = {
a: 1,
b : 'abc'
};
기본형의 데이터와의 차이는 객체의 변수 영역이 별도로 존재 한다는 점이다.
var obj1 = {
a: 1,
b : 'abc'
};
obj.a = 2;
obj1
이 바라보고 있는 주소 @5001
은 변하지 않았았다. 즉 새로운 객체가 만들어 진 것이 아니라 기존의 객체 내부의 값만 바뀐것이다.
가비지 컬렉터 (garbage collector, GC)
어떤 데이터에 대해 자신의 주소를 참조하는 변수의 개수를 참조 카운트라고 한다. 참조 카운트가 있다면 1 이지만 없다면 0 이 된다. 참조 카운트가 0인 메모리 주소는 가비지 컬렉터의 수거 대상이 된다.
가비지 컬렉터는 메모리 사용량이 포화 상태에 임박할 때마다 자동으로 수거 대상들을 수거 한다. 수거된 메모리는 다시 새로운 값을 할당 할 수 있는 빈 공간이 된다.
var a = 10;
var b = a;
var obj1 = {c:10, d:'ddd'};
var obj2 = obj1
불변 객체가 왜 필요 할까?
값으로 전달받은 객체에 변경을 가하더라도 원본 객체는 변하지 않아야 하는 경우가 생길수 있다. 이럴때 불변객체가 필요 하다.
var user = {
name:'ga young',
age: 27
};
var changeName = (user, newName) =>{
var newUser = user;
newUser.name = newName;
return newUser
};
var user2 = changeName(user, 'Olive');
console.log(user.name, user2.name); // Olive Olive
console.log(user === user2); // true
위의 예제는 가변성으로 인한 문제를 보여준다.
서로 다른 객체를 바라보게 만들면 이러한 문제를 해결할수 있다.
var user = {
name:'ga young',
age: 27
};
var changeName = (user, newName) =>{
return {
name: newName,
age: user.age
}
};
var user2 = changeName(user, 'Olive');
console.log(user.name, user2.name); // ga young Olive
console.log(user === user2); // false
changeName
함수가 새로운 객체를 반환하도록 수정했다. 이제 user와 user2는 서로 다른 객체 이므로 안전하게 변경 전과 후를 비교할 수 있다.
다만 아직 변경할 필요가 없는 기존 객체의 프로퍼티를 하드 코딩으로 입력했다. 이런 식으로는 대상 객체에 정보가 많을수록 사용자가 입력하는 수고가 늘어 날수 있다.
var copyObject = (target){
var result = {};
for(var prop in target){
result[prop] = target[prop];
}
}
var user = {
name:'ga young',
age: 27
};
var user2 = copyObject(user);
user2.name = 'Olive';
console.log(user.name, user2.name); // ga young Olive
console.log(user === user2); // false
copyoObject
함수를 통해 간단하게 객체를 복사하고 내용을 수정할수 있었다. 하지만 협업하는 모든 개발자들이 user객체 내부의 변경이 필요할 때는 무조건 copyoObject
함수를 사용하기로 합의 하고 그 규칙을 지킨다는 전제하에서는 user객체가 곧 불변 객체라고 볼 수 있다.
개발자의 신뢰에만 의존하는것 보다는 모두가 그 규칙을 따르지 않고는 프로퍼티 변경을 할 수 없게끔 시스템적으로 제약을 거는 편이 안전하다.
얕은복사는 바로 아래 단계의 값만 복사 하는 방법이고 , 깊은 복사는 내부의 모든 값들을 하나하나 찾아서 전부 복사하는 방법이다.
var copyObjectDeep = (target){
var result = {};
if(typeof target === 'object' && target !== null){
for(var prop in target){
result[prop] = target[prop];
}
} else {
result = target;
}
return result;
}
target이 객체인 경우 내부 프로퍼티들을 순회하며 copyObjectDeep
함수를 재귀적으로 호출하고, 객체가 아닌 경우 target을 그대로 지정 하게끔했다. 이 함수를 사용해 객체를 복사한 다음에는 원본과 사본이 서로 완전히 다른 객체를 참조하게 되어 어느 쪽의 프로퍼티를 변경하더라도 다른 쪽에 영향을 주지 않는다.
var obj = {
a: 1,
b: {
c: null,
d: [1,2]
}
}
var obj2 = copyObjectDeep(obj);
obj2.a = 3;
obj2.b.c = 4;
obj.b.d[1] = 3;
console.log(obj) // {a: 1, b: {c: null, d:[1,3]}}
console.log(obj2) // {a: 3, b: {c: 4, d:[1,2]}}
둘다 '없음' 을 나타내지만 미세한 차이점이 있다.
자바스크립트 엔진은 사용자가 어떤 값을 지정할 것이라고 예상되는 상황임에도 실제로는 그렇게 하지 않았을 때 undefined를 반환 한다. 다음 과 같은 경우가 이에 해당 한다.
1. 값을 대입하지 않은 변수, 즉 데이터 영역의 메모리 주소를 지정하지 않은 식별자에 접근 할때
2. 객체 내부의 존재하지 않는 프로퍼티에 접근하려고 할 때
3. return 문이 없거나 호출되지 않는 함수의 실행 결과
var a;
console.log(a) // undefined -> 1번예시
var obj = {a: 1};
console.log(obj.a) // 1
console.log(obj.b) // undefined -> 2번 예시
console.log(b) // 1 -> b is not defined
var foo = (){};
var c = foo() //return값이 없으면 undefined를 반환하는 것으로 간주
console.log(c) // undefiend -> 3번 예시
같은 의미를 가진 null이라는 값을 이용해 비어 있음을 명시적으로 나타내면 된다.
추가로 null은 한가지 주의할 점이 있는데 type of null
이 object라는 점이다. 이는 자바스크립트 자체 버그 이다. 따라서 어떤 변수의 값이 null인지 여부를 판별하기위해서는 typeof대신 다른 방식으로 접근 해야 한다.
var n = null;
console.log(typeof n); // object;
console.log(n == undefined); // true
console.log(n == null); // true
console.log(n === undefined); // false
console.log(n === null); // true
동등 연산자 (==
) 이 아닌 더 정확히 비교하는 연산자인 일치 연산자(===
) 를 이용해 null값을 판단 한다.
참고
해당 포스팅은 코어 자바스크립트 책의 내용을 공부, 정리한 내용입니다.