💡 [기본형과 참조형의 구분 기준]
var str = 'test!';
변수를 선언하고 데이터를 할당해 주었다.
그렇다면 데이터는 어떻게 저장되는가.

주소 1002번에 식별자와 데이터의 주소값을 넣어주는 방식으로 저장된다.
어째서 주소 1002번에 식별자와 데이터 동시에 저장하지 않는가??
그 이유는 두가지가 있다.
var a = 'abc';
여기서 a라는 변수가 abc라는 값에서 abcdef가 되는 과정을 통해 불변성을 알 수 있다.
= 변수 a는 불변하다.
(이때 @5002번은 더 이상 사용되지 않아 가비지컬렉터의 수거 대상이 됨)
var obj1 = {
a: 1,
b: 'bbb'
};
// 데이터 번경
obj1.a = 2;
이렇게 참조형 데이터가 존재할 때 기본형 데이터와 다른 점이 있다.
객체의 변수(프로퍼티) 영역의 별도 존재가 있다는 것이다.

표를 보면 우선 1001번에 obj1 식별자와 obj1에 저장된 데이터의 주소값을 적어준다. 하지만 obj1은 참조형 데이터이기 때문에 안에 존재하는 a와 b의 주소값을 따로 만들어 준다.
나머지 할당 방법은 기본형 데이터와 같다.
그런데 여기서 obj1.a = 2로 데이터를 변경한다면

이렇게 a의 주소값이 변경된다. 이처럼 obj1의 데이터값은 불변이지만 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 👍
이렇게 changeName이라는 식별자의 함수를 정의해서 사용하는 것이다.
하지만 이또한, 완전한 해결책은 아니다.
//이런 패턴은 어떨까요?
var copyObject = function (target) {
var result = {};
// for ~ in 구문을 이용하여, 객체의 모든 프로퍼티에 접근할 수 있습니다.
// 하드코딩을 하지 않아도 괜찮아요.
// 이 copyObject로 복사를 한 다음, 복사를 완료한 객체의 프로퍼티를 변경하면
// 되겠죠!?
for (var prop in target) {
result[prop] = target[prop];
}
return result;
}
하지만 이 방법 역시 함수안에 함수가 존재하는 경우 안쪽에 있는 객체들이 제대로 복사되지 않는 문제점이 존재한다.
해결책 -> 깊은 복사
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;
}
<용어 정리>
실행 컨텍스트 : 실행할 코드에 제공할 환경 정보들을 모아놓은 객체.
호이스팅 : 선언된 변수를 위로 끌어올림.
실행 컨텍스트를 알기 위해 콜 스택을 알아보자

스택과 큐는 자료구조의 종류이다.
Stack은 후입선출(LIFO), Queue는 선입선출(FIFO)형태이다.
이제 본격적으로 실행 컨텍스트에 대해 알아보자.
실행 컨텍스트는 동일 환경에 있는 코드를 실행할 때 필요한 환경 정보들을 모아 컨텍스트를 구성하고 이것을 위에서 본 콜스택에 쌓아 올린다.
컨텍스트의 구성
// ---- 1번
var a = 1;
function outer() {
function inner() {
console.log(a); //undefined
var a = 3;
}
inner(); // ---- 2번
console.log(a);
}
outer(); // ---- 3번
console.log(a);
이 코드를 실행하면 코드실행 → 전역(in) → 전역(중단) + outer(in) → outer(중단) + inner(in) → inner(out) + outer(재개) → outer(out) + 전역(재개) → 전역(out) → 코드종료 이러한 순서로 실행된다.

스택의 구조로 그려보면 이렇게 된다.
VE는 컨텍스트 내의 식별자 정보(=record)를 갖고 있다.
LE와 VE는 호이스팅이라는 특징을 가지고 있다.