자바스크립트에서 값의 타입은 크게 기본형(Primitive Type)과 참조형(Reference Type)으로 구분됩니다. 기본형과 참조형의 구분 기준은 값의 저장 방식과, 불변성 여부입니다.
메모리, 데이터
메모리(memo + ry) : byte 단위로 구성
java, c와 다른 javascript의 메모리 관리 방식(feat. 정수형)
식별자, 변수
/** 선언과 할당을 풀어 쓴 방식 */
var str;
str = 'test!';
/** 선언과 할당을 붙여 쓴 방식 */
var str = 'test!';
값을 바로 변수에 대입하지 않는 이유(=무조건 새로 만드는 이유)
자유로운 데이터 변환
메모리의 효율적 관리
똑같은 데이터를 여러번 저장해야 한다면?
1만개의 변수를 생성해서 모든 변수에 숫자 1을 할당하는 상황을 가정해 봅시다. 모든 변수를 별개로 인식한다고 한다면, 1만개의 변수 공간을 확보해야 해요.
바로 대입하는 case) 숫자형은 8 바이트 고정이죠?
변수 영역에 별도 저장 case)
변수 영역 : 2바이트 1만개 = ****2만바이트
데이터 영역 : 8바이트 1개 = 8바이트
총 : 2만 8바이트
// a라는 변수가 abc에서 abcdef가 되는 과정을 통해 불변성을 유추해봅시다!
// 'abc'라는 값이 데이터영역의 @5002라는 주소에 들어갔다고 가정할게요.
var a = 'abc';
// 'def'라는 값이 @5002라는 주소에 추가되는 것이 아니죠!
// @5003에 별도로 'abcdef'라는 값이 생기고 a라는 변수는 @5002 -> @5003
// 즉, "변수 a는 불변하다." 라고 할 수 있습니다.
// 이 때, @5002는 더 이상 사용되지 않기 때문에 가비지컬렉터의 수거 대상이 됩니다.
a = a + 'def';
가변값과 가변성(with 참조형 데이터)
1. 참조형 데이터의 변수 할당 과정
```jsx
// 참조형 데이터는 별도 저장공간(obj1을 위한 별도 공간)이 필요합니다!
var obj1 = {
a: 1,
b: 'bbb,
};
```
2. 기본형 데이터의 변수 할당 과정과 차이점 : **객체의 변수(프로퍼티) 영역**의 별도 존재 여부
3. 참조형 데이터가 불변하지 않다(가변하다)라고 하는 이유
```jsx
var obj1 = {
a: 1,
b: 'bbb',
};
// 데이터를 변경해봅시다.
obj1.a = 2;
```
위에 작성해놓았던 표를 토대로 변경 테스트를 해보며 알아봅시다 😉
과정은 아래와 같아요
.
1. 변경할 값인 숫자 2를 데이터 영역에서 검색합니다.
2. 없네요! 2를 새로 추가하고, 해당 주소(ex : @5003)를 obj1을 위한 별도 영역에 갈아껴줍니다.
데이터 영역에 저장된 값은 여전히 계속 불변값이지만, obj1을 위한 별도 영역은 얼마든지 변경이 가능해요. 이것 때문에 참조형 데이터를 흔히, ‘불변하지 않다(=가변하다)’
라고 합니다.
중첩객체의 할당
자바스크립트에서 중첩객체란, 객체 안에 또 다른 객체가 들어가는 것을 말해요. 이번 주차 초반에 살펴보았듯이 객체는 배열, 함수 등을 모두 포함하는 상위개념이기 때문에 배열을 포함하는 객체도 중첩객체라고 할 수 있답니다.
참조 카운트가 0인 메모리 주소의 처리
참조카운트란 무엇일까요?
💡 **객체를 참조하는 변수나 다른 객체의 수를 나타내는 값**입니다. 참조 카운트가 0인 객체는 더 이상 사용되지 않으므로, **가비지 컬렉터**에 의해 메모리에서 제거됩니다.가비지컬렉터(GC, Garbage Collector)
💡 더 이상 사용되지 않는 객체를 자동으로 메모리에서 제거하는 역할을 합니다. 자바스크립트는 가비지 컬렉션을 수행함으로써 개발자가 명시적으로 메모리 관리를 하지 않아도 되도록 지원합니다. 자바스크립트 엔진에서 내부적으로 수행되며, 개발자는 가비지 컬렉션에 대한 직접적인 제어를 할 수 없습니다.// STEP01. 쭉 선언을 먼저 해볼께요.
var a = 10; //기본형
var obj1 = { c: 10, d: 'ddd' }; //참조형
// STEP02. 복사를 수행해볼께요.
var b = a; //기본형
var obj2 = obj1; //참조형
var a = 10; //기본형
var obj1 = { c: 10, d: 'ddd' }; //참조형
// STEP02. 복사를 수행해볼께요.
var b = a; //기본형
var obj2 = obj1; //참조형
b = 15;
obj2.c = 20;
기본형과 참조형의 변수 복사 시 주요한 절차의 차이점은 다음과 같아요!
영향 없음
obj1까지 변경이 됨
// 기본형 변수 복사의 결과는 다른 값!
a !== b;
// 참조형 변수 복사의 결과는 같은 값!(원하지 않았던 결과😭)
obj1 === obj2;
//기본형 데이터
var a = 10;
var b = a;
//참조형 데이터
var obj1 = { c: 10, d: 'ddd' };
var obj2 = obj1;
b = 15;
obj2 = { c: 20, d: 'ddd'};
obj2 변수는 참조형 데이터이고, 참조형 데이터의 값을 변경한 것임에도 불고하고 이전 케이스와는 다르게 obj1과는 바라보는 데이터 메모리 영역의 값이 달라졌습니다!
참조형 데이터가 ‘가변값’이라고 할 때의 ‘가변’은 참조형 데이터 자체를 변경할 경우가 아니라, 그 내부의 프로퍼티를 변경할 때 성립한다고 할 수 있겠네요 👍
불변 객체의 정의
우리는 앞선 과정에서, 가변하다
와 불변하다
의 개념을 배웠습니다. 다시 살짝 정리해서 객체를 예로 들면, 객체의 속성에 접근해서 값을 변경하면 가변이 성립했었죠. 반면, 객체 데이터 자체를 변경(새로운 데이터를 할당)하고자 한다면 기존 데이터는 변경되지 않습니다. 즉, 불변하다라고 볼 수 있습니다.
개념은 알겠는데, 그 개념이 왜 필요한지 알겠나요? 아래 예시를 통해서 “불변하다”. 혹은, ‘불변객체’의 개념이 왜 필요한지를 한번 알아보도록 합시다
불변 객체의 필요성
// user 객체를 생성
var user = {
name: 'wonjang',
gender: 'male',
};
// 이름을 변경하는 함수, 'changeName'을 정의
// 입력값 : 변경대상 user 객체, 변경하고자 하는 이름
// 출력값 : 새로운 user 객체
// 특징 : 객체의 프로퍼티(속성)에 접근해서 이름을 변경했네요! -> 가변
var changeName = function (user, newName) {
var newUser = user;
newUser.name = newName;
return newUser;
};
// 변경한 user정보를 user2 변수에 할당하겠습니다.
// 가변이기 때문에 user1도 영향을 받게 될거에요.
var user2 = changeName(user, 'twojang');
// 결국 아래 로직은 skip하게 될겁니다.
if (user !== user2) {
console.log('유저 정보가 변경되었습니다.');
}
console.log(user.name, user2.name); // twojang twojang
console.log(user === user2); // true
위의 예제를 아래와 같이 개선할 수 있어요!
// user 객체를 생성
var user = {
name: 'wonjang',
gender: 'male',
};
// 이름을 변경하는 함수 정의
// 입력값 : 변경대상 user 객체, 변경하고자 하는 이름
// 출력값 : 새로운 user 객체
// 특징 : 객체의 프로퍼티에 접근하는 것이 아니라, 아에 새로운 객체를 반환 -> 불변
var changeName = function (user, newName) {
return {
name: newName,
gender: user.gender,
};
};
// 변경한 user정보를 user2 변수에 할당하겠습니다.
// 불변이기 때문에 user1은 영향이 없어요!
var user2 = changeName(user, 'twojang');
// 결국 아래 로직이 수행되겠네요.
if (user !== user2) {
console.log('유저 정보가 변경되었습니다.');
}
console.log(user.name, user2.name); // wonjang twojang
console.log(user === user2); // false 👍
위 방법이 과연 최선일까요?
안타깝지만, 그렇진 않아요. 다음과 같은 문제점이 있기 때문입니다.
얕은 복사
의 방법을 제시할게요!더 나은 방법 : 얕은 복사
//이런 패턴은 어떨까요?
var copyObject = function (target) {
var result = {};
// for ~ in 구문을 이용하여, 객체의 모든 프로퍼티에 접근할 수 있습니다.
// 하드코딩을 하지 않아도 괜찮아요.
// 이 copyObject로 복사를 한 다음, 복사를 완료한 객체의 프로퍼티를 변경하면
// 되겠죠!?
for (var prop in target) {
result[prop] = target[prop];
}
return result;
}
//위 패턴을 우리 예제에 적용해봅시다.
var user = {
name: 'wonjang',
gender: 'male',
};
var user2 = copyObject(user);
user2.name = 'twojang';
if (user !== user2) {
console.log('유저 정보가 변경되었습니다.');
}
console.log(user.name, user2.name);
console.log(user === user2);
얕은 복사 vs 깊은 복사
✓ 이 패턴도 여전히 문제가 있을까요?
하하….. 네 ㅠㅠ 여전히 있습니다. 왜냐면, 중첩된 객체에 대해서는 완벽한 복사를 할 수 없기 때문이에요. 이것이 얕은 복사의 한계입니다.
var user = {
name: 'wonjang',
urls: {
portfolio: 'http://github.com/abc',
blog: 'http://blog.com',
facebook: 'http://facebook.com/abc',
}
};
var user2 = copyObject(user);
user2.name = 'twojang';
// 바로 아래 단계에 대해서는 불변성을 유지하기 때문에 값이 달라지죠.
console.log(user.name === user2.name); // false
// 더 깊은 단계에 대해서는 불변성을 유지하지 못하기 때문에 값이 같아요.
// 더 혼란스러워 지는거죠 ㅠㅠ
user.urls.portfolio = 'http://portfolio.com';
console.log(user.urls.portfolio === user2.urls.portfolio); // true
// 아래 예도 똑같아요.
user2.urls.blog = '';
console.log(user.urls.blog === user2.urls.blog); // true
var user = {
name: 'wonjang',
urls: {
portfolio: 'http://github.com/abc',
blog: 'http://blog.com',
facebook: 'http://facebook.com/abc',
}
};
// 1차 copy
var user2 = copyObject(user);
// 2차 copy -> 이렇게까지 해줘야만 해요..!!
user2.urls = copyObject(user.urls);
user.urls.portfolio = 'http://portfolio.com';
console.log(user.urls.portfolio === user2.urls.portfolio);
user2.urls.blog = '';
console.log(user.urls.blog === user2.urls.blog);
💡 재귀적으로 수행한다?
⇒함수나 알고리즘이 자기 자신을 호출하여 반복적으로 실행되는 것을 말합니다 😎
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;
obj2.b.d[1] = 3;
console.log(obj);
console.log(obj2);
마지막 방법! JSON(=**JavaScript Object Notation)을 이용하는 방법**도 존재합니다. 하지만 완벽한 방법은 아니에요. 간략히 장/단점을 정리해드리니, 내용만 참고해주세요 😉
장점:
단점:
따라서 JSON을 이용한 깊은 복사는 객체의 구조가 간단하고, 함수나 undefined와 같은 속성 값이 없는 경우에 적합한 방법입니다. 만약 객체의 구조가 복잡하거나 순환 참조가 있는 경우에는 다른 깊은 복사 방법을 고려해야 합니다.
둘 다 없음을 나타내는 값이에요. 하지만 미세하게 다르고, 그 목적 또한 다르답니다. 아래에서 한번 살펴보도록 할께요!
사용자(=개발자)가 직접 지정할 수도 있지만 일반적으로는 자바스크립트 엔진에서 값이 있어야 할 것 같은데 없는 경우, 자동으로 부여합니다. 다음 케이스를 생각해 주시면 될 것 같아요.
var a;
console.log(a); // (1) 값을 대입하지 않은 변수에 접근
var obj = { a: 1 };
console.log(obj.a); // 1
console.log(obj.b); // (2) 존재하지 않는 property에 접근
// console.log(b); // 오류 발생
var func = function() { };
var c = func(); // (3) 반환 값이 없는 function
console.log(c); // undefined
2가지 역할을 가진 undefined, 헷갈릴 만도 해요. 그리고 위험해요!
용도 : ‘없다’를 명시적으로 표현할 때
주의 : typeof null
typeof null이 object인 것은 유명한 javascript 자체 버그입니다. 조심해야겠죠?
var n = null;
console.log(typeof n); // object
//동등연산자(equality operator)
console.log(n == undefined); // true
console.log(n == null); // true
//일치연산자(identity operator)
console.log(n === undefined);
console.log(n === null);