
var a; // 변수 a 선언
a = 'abc'; // 변수 a에 데이터 할당
var a = 'abc' // 변수 선언과 할당을 한 문장으로 표현
할당 과정에서 a라는 이름을 가진 주소를 검색해서 문자열 'abc'를 할당하면 될 것 같지만 실제로는 해당 위치에 문자열 'abc'를 직접 저장하지 않는다.
데이터를 저장하기 위한 별도의 메모리 공간을 다시 확보해서 문자열 'abc'를 저장하고, 그 주소를 변수 영역에 저장하는 방식으로 이뤄진다.
💡 변수 영역에서 값을 직접 대입하지 않고 한 단계를 더 거치는 이유?
- 데이터 변환을 자유롭게 할 수 있게 하고, 메모리를 효율적으로 관리하기 위함
- 미리 확보한 공간 내에서만 데이터 변환을 할 수 있다면 변환한 데이터를 다시
저장하기 위해서 확보된 공간을 변환된 데이터 크기에 맞게 늘리는 작업이 필요함
- 즉, 가변적인 문자열 데이터의 변환을 처리하려면 변수와 데이터를 별도의
공간에 나누어 저장하는 것이 좋음
- 기존 문자열에 변환이 발생하면 무조건 새로 만들어 별도의 공간에 저장함
var a = 'abc'; // 변수 a에 'abc' 데이터 주소 할당
a = a + 'def'; // 기존 'abc'에 'def'를 추가하는 것이 아니라,
// 새로운 문자열 'abcdef'를 만들어 그 주소를 변수 a에 저장
var b = 5; // 변수 b에 숫자 5 할당
// 데이터 영역에서 5 찾고, 없으면 데이터 공간 만들어 저장
var c = 5; // 이미 만들어놓은 값이 있으니 위의 주솟값 재활용
b = 7; // 기존에 저장된 5를 7로 바꾸는 것이 아니라,
//이미 만들어 놓은 7이 있다면 재활용하고 없으면 새로 만들어서 b에 저장
💡 불변성의 성질
변경은 새로 만드는 동작을 통해서만 이뤄지기 때문에 문자열 값, 숫자 값 모두 한 번 만든 값을 바꿀 수 없다.
💡 참조 카운트
어떤 데이터에 대해 자신의 주소를 참조하는 변수의 개수
💡 가비지 컬렉터(garbage collector, GC)
- 참조 카운트가 0인 메모리 주소는 가비지 컬렉터의 수거 대상이 된다.
- 가비지 컬렉터는 런타임 환경에 따라 특정 시점이나 메모리 사용량이 포화 상태에 임박할 때마다 자동으로 수거 대상들을 수거(collecting)한다.
- 수거된 메모리는 다시 새로운 값을 할당할 수 있는 빈 공간이 된다.
💡 왜 불변 객체가 필요할까?
값으로 전달받은 객체에 변경을 가하더라도 원본 객체는 변하지 않아야 하는 경우가 발생함
var user = {
name: 'Yuli',
gender: 'female'
};
var changeName = function (user, newName) {
var newUser = user;
newUser.name = newName;
return newUser;
};
var user2 = changeName(user, 'Kim');
if (user !== user2) {
console.log('유저 정보가 변경되었습니다.');
}
console.log(user.name, user2.name); // Kim Kim
console.log(user === user2); // true
var user = {
name: 'Yuli',
gender: 'female'
};
var chageName = function (user, newName) {
return {
name: newName,
gender: user.gender
};
};
var user2 = changeName(user, 'Kim');
if (user !== user2) {
console.log('유저 정보가 변경되었습니다.');
} // 유저 정보가 변경되었습니다.
console.log(user.name, user2.name); // Yuli Kim
console.log(user === user2); // false
💡 위 해결 방법의 미흡한 점
chageName 함수는 새로운 객체를 만들면서 변경할 필요가 없는 기존 객체의 프로퍼티(gender)를 하드코딩으로 입력했다. 이 방식은 대상 객체에 정보가 많거나 변경해야 할 정보가 많을수록 사용자가 입력하는 수고가 늘어날 것이다.
var copyObject = function (target) {
var result = {};
for (var prop in target) {
result[prop] = target[prop];
}
return result;
};
var user = {
name: 'Yuli',
gender: 'female'
};
var user2 = copyObject(user);
user2.name = 'Kim';
if (user !== user2) {
console.log('유저 정보가 변경되었습니다.');
} // 유저 정보가 변경되었습니다.
console.log(user.name, user2.name); // Yuli Kim
console.log(user === user2); // false
💡 위 해결 방법의 미흡한 점
협업하는 모든 개발자들이 user 객체 내부의 변경이 필요할 때는 무조건 copyObject 함수를 사용하기로 합의하고 그 규칙을 지킨다면 불변 객체이지만, 아예 프로퍼티 변경을 할 수 없게 시스템적으로 제약을 거는 편이 안전하다. 또한, 얕은 복사만을 수행하는 것이 가장 아쉬운 점이다.
var user = {
name: 'Yuli'
urls: {
portfolio: 'https://github.com/yyuli',
blog: 'https://velog.io/@yulikim'
}
};
var user2 = copyObject(user);
user2.name = 'Kim';
console.log(user.name === user2.name); // false
user.urls.portfolio = 'https://github.com/';
console.log(user.urls.portfolio === user2.urls.portfolio); // true
user2.urls.blog = '';
console.log(user.urls.blog === user2.urls.blog); //true
💡user 객체에 직접 속한 프로퍼티에 대해서는 복사해서 완전히 새로운 데이터가 만들어진 반면, 한 단계 더 들어간 urls의 내부 프로퍼티들은 기존 데이터를 그대로 참조
var user2 = copyObject(user);
user2.urls = copyObject(user.urls);
user.urls.portfolio = 'https://github.com/';
console.log(user.urls.portfolio === user2.urls.portfolio); // false
user2.urls.blog = '';
console.log(user.urls.blog === user2.urls.blog); // false
var copyObjectDeep = function(target) {
var result = {};
if (typeof target === 'object' && target !== null {
for (var prop in target) {
result[prop] = copyObjectDeep(target[prop]);
}
}else {
result = target;
}
return result;
};
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: { '0': 1, '1': 2 } } }
var obj = {
a: 1,
b: {
c: null,
d: [1, 2],
func1: function () { console.log(3); }
},
func2: function () { console.log(4); }
};
var obj2 = copyObjectviaJSON(obj);
obj2.a = 3;
obj2.b.c = 4;
obj.b.d[1] = 3;
console.log(obj); // { a: 1, b: { c: null, d: [ 1, 3 ], func1: f() }, func2: f() }
console.log(obj2); // { a: 3, b: { c: 4, d: [ 1, 2 ] } }
var a;
console.log(a); // (1) undefined. 값을 대입하지 않은 변수에 접근
var obj = { a: 1 };
console.log(obj.a); // 1
console.log(obj.b); // (2) 존재하지 않는 프로퍼티에 접근
console.log(b); // c.f) ReferenceError: b is not defined
var func = function() { };
var c = func(); // (3) 반환(return)값이 없으면 undefined를 반환한 것으로 간주
console.log(c); // undefined
💡 'undefined'의 의미 구분
- 사용자가 명시적으로 부여한 경우
- undefined가 '비어있음'을 의미하긴 하지만 하나의 값으로 동작하기 때문에 이때의 프로퍼티나 배열의 요소는 고유의 키값(프로퍼티 이름)이 실존하게 되고, 순회의 대상이 된다.
- 비어있는 요소에 접근하려고 할 때 반환되는 경우
- 해당 프로퍼티 내지 배열의 키값(인덱스) 자체가 존재하지 않음을 의미한다.
- 즉, 값으로써 어딘가에 할당된 undefined는 실존하는 데이터인 반면, 자바스크립트 엔진이 반환해주는 undefined 문자 그대로 값이 없음을 나타낸다.
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
💡 undefined와 null의 비교
동등 연산자(equally operator)(==)로 비교할 경우, null과 undefined가 서로 같다고 판단하기 때문에 정확히 판별하기 위해서는 일치 연산자(identity operator)(===)를 사용해야 한다.