기본형과 참조형으로 나뉘어진다. 기본형에는 앞에서 배운 내용들에 심볼이 추가 되어 있고, 참조형(Object)에는 배열과 함수 데이터 등이 있다.
기본형과 참조형의 구분 기준은 값의 저장방식 / 불변성 여부인데,
복제의 방식
불변성의 여부
여기에서 불변성을 띈다라는 말을 이해하기위해서는 메모리와 데이터의 대한 내용을 공부해야했다.
비트
바이트
메모리(memo + ry) : byte 단위로 구성
모든 데이터는 byte 단위의 식별자인 메모리 주소값을 통해서 서로 구분이 됩니다.
java, c와 다른 javascript의 메모리 관리 방식(feat. 정수형)
8을 저장하는 방법
JS : let a = 8(8byte)
JAVA
java 또는 c언어가 초기에 등장했을 때 숫자 데이터 타입은 크기에 따라 다양하게 지정해줘야 할 만큼 개발자가 handling 할 요소들이 많았다. 하지만 javascript는 이런 부분에서는 상당히 편리하죠. 메모리 이슈까지는 고민하지 않아도 된다.
/** 선언과 할당을 풀어 쓴 방식 */
var str;
str = 'test!';
/** 선언과 할당을 붙여 쓴 방식 */
var str = 'test!';
값을 바로 변수에 대입하지 않는 이유(=무조건 새로 만드는 이유)
// a라는 변수가 abc에서 abcdef가 되는 과정을 통해 불변성을 유추해봅시다!
// 'abc'라는 값이 데이터영역의 @5002라는 주소에 들어갔다고 가정할게요.
var a = 'abc';
// 'def'라는 값이 @5002라는 주소에 추가되는 것이 아니죠!
// @5003에 별도로 'abcdef'라는 값이 생기고 a라는 변수는 @5002 -> @5003
// 즉, "변수 a는 불변하다." 라고 할 수 있습니다.
// 이 때, @5002는 더 이상 사용되지 않기 때문에 가비지컬렉터의 수거 대상이 됩니다.
a = a + 'def';
// 참조형 데이터는 별도 저장공간(obj1을 위한 별도 공간)이 필요합니다!
var obj1 = {
a: 1,
b: 'bbb,
};
var obj1 = {
a: 1,
b: 'bbb',
};
// 데이터를 변경해봅시다.
obj1.a = 2;
변경할 값인 숫자 2를 데이터 영역에서 검색
없네요! 2를 새로 추가하고, 해당 주소(ex : @5003)를 obj1을 위한 별도 영역에 갈아껴주기
데이터 영역에 저장된 값은 여전히 계속 불변값이지만, obj1을 위한 별도 영역은 얼마든지 변경이 가능해요. 이것 때문에 참조형 데이터를 흔히, ‘불변하지 않다(=가변하다)’
중첩객체의 할당
자바스크립트에서 중첩객체란, 객체 안에 또 다른 객체가 들어가는 것을 말하는데,객체는 배열, 함수 등을 모두 포함하는 상위개념이기 때문에 배열을 포함하는 객체도 중첩객체
var obj = {
x: 3,
arr: [3, 4, 5],
}
obj.arr[1]의 탐색과정
참조 카운트가 0인 메모리 주소의 처리
참조카운트란
객체를 참조하는 변수나 다른 객체의 수를 나타내는 값, 참조 카운트가 0인 객체는 더 이상 사용되지 않으므로, 가비지 컬렉터에 의해 메모리에서 제거됨.
가비지컬렉터(GC, Garbage Collector)
더 이상 사용되지 않는 객체를 자동으로 메모리에서 제거하는 역할을 함. 자바스크립트는 가비지 컬렉션을 수행함으로써 개발자가 명시적으로 메모리 관리를 하지 않아도 되도록 지원. 자바스크립트 엔진에서 내부적으로 수행되며, 개발자는 가비지 컬렉션에 대한 직접적인 제어를 할 수 없다.
변수 복사의 비교
우리가 지금까지 작성했던 방법과 상당히 유사하다. 먼저 기본형 및 참조형 데이터의 선언과 할당을 해보고, 변수 복사까지 강의를 참고하여 역시 표를 작성했다.
// STEP01. 쭉 선언을 먼저 해볼께요.
var a = 10; //기본형
var obj1 = { c: 10, d: 'ddd' }; //참조형
// STEP02. 복사를 수행해볼께요.
var b = a; //기본형
var obj2 = obj1; //참조형
// STEP01. 쭉 선언을 먼저 해볼께요.
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: "Ara",
gender: "female",
};
//이름을 변경하는 함수, 'changeName'을 정의
//입력값: 변경대상 user 객체, 변경하고자 하는 이름
//출력값: 새로운 user 객체
//특징: 객체의 프로퍼티(속성)에 접근하여 이름을 변경했네요! -> 가변
var changeName = function (user, newName) {
var newUser = user;
newUser.name = newName;
return newUser;
};
//변경한 user 정보를 user2 변수에 할당하겠습니다
//가변하기 때문에 user2도 영향을 받게 될 거에요
var user2 = changeName(user, "twoara");
//결국 아래 로직은 skip하게 될 겁니다
if (user !== user2) {
console.log("유저 정보가 변경되었습니다.");
}
console.log(user.name, user2.name); // twoara twoara
console.log(user === user2); //true
이걸 개선하면,
//user 객체를 생성
var user = {
name: "Ara",
gender: "female",
};
//이름을 변경하는 함수 정의
//입력값: 변경대상 user 객체, 변경하고자 하는 이름
//출력값: 새로운 user 객체
//특징: 객체의 프로퍼티(속성)에 접근하는 것이 아니라 아예 새로운 객체를 반환->불변
var changeName = function (user, newName) {
return {
name: newName,
gender: user.gender,
};
};
//변경한 user 정보를 user2 변수에 할당하겠습니다
//불변이기때문에 user1은 영향이 없어요
var user2 = changeName(user, "twoara");
//결국 아래 로직은 수정되겠네요
if (user !== user2) {
console.log("유저 정보가 변경되었습니다.");
}
console.log(user.name, user2.name); // Joara twoara
console.log(user === user2); //false
changeName 함수는 새로운 객체를 만들기 위해 변경할 필요가 없는 gender 프로퍼티를 하드코딩으로 입력⇒ 만일 이러한 속성이 10개라면?
얕은 복사
의 방법을 제시할게요!//이런 패턴은 어떤가요?
var copyObject = function (target) {
var result = {};
// for ~ in 구문을 이용하여, 객체의 모든 프로퍼티에 접근 할 수 있습니다.
// 하드코딩을 하지 않아도 괜찮아요
// 이 copyObject로 복사를 한 다음, 복사를 완료한 객체의 프로퍼티를 변경하면 되겠죠?
for (var prop in target) {
result(prop) = target(prop);
}
return result;
};
예제에 적용하면,
//위 패턴을 우리 예제에 적용해봅시다
var user = {
name: "Joara",
gender: "female",
};
var user2 = copyObject(user);
user2.name = "twoara";
if (user !== user2) {
console.log("유저 정보가 변경되었습니다.");
}
console.log(user.name, user2.name); //Joara twoara
console.log(user === user2); //false
중첩된 객체에 대해서는 완벽한 복사를 할 수 없기 때문.이것이 얕은 복사의 한계.
얕은 복사 : 바로 아래 단계의 값만 복사(위의 예제)
문제점 : 중첩된 객체의 경우 참조형 데이터가 저장된 프로퍼티를 복사할 때, 주소값만 복사
깊은 복사 : 내부의 모든 값들을 하나하나 다 찾아서 모두 복사하는 방법
중첩된 객체에 대한 얕은 복사
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
결국 ser.urls프로퍼티도 불변 객체로 만들어야 한다.
중첩된 깊은 복사
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방법을 이용하는 것도 존재한다.
JSON을 이용한 깊은 복사는 다른 깊은 복사 방법에 비해 코드가 간결하고 쉽게 이해할 수 있다.
SON.stringify() 함수는 순환 참조(Recursive Reference)를 지원하지 않는다. 따라서 객체 안에 객체가 중첩되어 있는 경우, 이 방법으로는 복사할 수 없다.
==> 따라서 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
"없다"를 명시적으로 사용 할 때에는 사용하면 안된다!!!!!
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);
참고로 typeof null
이 object인 것은 자바스크립트 자체 버그!!
복사에 대해서는 많은 복습이 필요 할 것 같다.
그리고 undefined와 null의 차이도 확실히 기억하고 잘못쓰는 경우가 있지 않게 조심해야겠다 !