컴퓨터는 데이터를 저장하고 처리할 때, 모든 정보를 0과 1로 변환합니다. 이러한 0과 1을 표현할 수 있는 가장 작은 메모리 단위를 비트(Bit)라고 합니다. 메모리는 이러한 비트가 모여 이루어진 구조이며, 각 비트는 고유한 식별자(Unique Identifier)를 통해 위치를 확인할 수 있습니다.
비트는 단 하나의 값(0 또는 1)만을 표현할 수 있어 활용에 한계가 있습니다. 만약 데이터를 처리하거나 검색할 때 비트 단위로 작업을 한다면, 메모리 용량은 적게 차지할 수 있겠지만 효율이 매우 떨어질 것입니다.
하지만 비트를 지나치게 많이 묶으면, 효율성의 문제가 발생할 수 있습니다. 자주 사용하지 않는 데이터를 표현하기 위해 큰 공간을 남겨두는 것은 메모리 낭비로 이어질 수 있기 때문입니다.
변수는 변할 수 있는 수 입니다. 수학 용어를 차용했기 때문에 숫자를 의미하는 '수'가 붙었을 뿐, 값이 반드시 '숫자'여야 하는 것이 아닙니다.
영어 단어 variable은 원래 변할 수 있다라는 형용사이지만 컴퓨터 용어로 쓸 때는 변할 수 있는 '무언가'라는 명사로 확장시켰습니다.
여기서 무언가 = 데이터를 말합니다. 숫자도 데이터, 문자열도, 객체도, 배열 모두 데이터입니다. 식별자는 어떤 데이터를 식별하는 데 사용하는 이름, 즉 변수명입니다.
var a; // a라는 변수를 선언, 초기값은 undefined
a = 10; // a에 숫자 10을 저장
a = "Hello, World!"; // a에 문자열 저장
변수를 쉽게 이해하려면 데이터를 담는 그릇이라고 생각하면 됩니다.
이 그릇은 어떤 데이터든 담을 수 있으며, 필요에 따라 비우고 새 데이터를 담을 수도 있습니다.
변경 가능: 한 번 저장한 값이라도 언제든 다른 값으로 대체할 수 있습니다.
다양한 데이터 타입을 수용: 숫자, 문자열, 객체 등 다양한 데이터 타입을 저장할 수 있습니다.
식별자를 통해 접근: 변수의 이름, 즉 식별자를 통해 데이터를 저장하거나 불러올 수 있습니다.
var a; // 변수 a 선언
a = 'abc'; // 변수 a에 데이터 할당
var a = 'abc' // 변수 선언과 할당을 한 문장으로 표현
자바스크립트에서 변수 선언과 할당은 두 문장으로 나누거나 한 문장으로 작성하더라도, 엔진은 동일한 방식으로 처리합니다. 선언 과정에서는 메모리 공간을 확보하고 이름을 설정하며, 이후 할당 과정에서는 해당 주소에 값을 저장합니다.
왜 변수 영역에 값을 직접 대입하지 않고 굳이 번거롭게 한 단계를 더 거치는 걸까요?
자바스크립트는 변수와 데이터를 별도 저장해 데이터 변환의 자유와 메모리 효율성을 높입니다. 숫자는 고정 크기지만, 문자열은 가변적이므로 저장 공간을 미리 늘리거나 이동해야 할 수 있습니다. 이를 방지하려고 변수와 데이터를 분리해 메모리 사용을 최적화 할려고 해서 한 단계를 더 거칩니다.
변수와 상수는 변수 영역 메모리의 변경 가능성으로 구분됩니다. 변수는 재할당이 가능하지만, 상수는 불가능합니다. 반면, 불변성은 데이터 영역 메모리의 변경 가능성과 관련되며, 숫자, 문자열, boolean 등 기본형 데이터는 모두 불변값입니다.
var a = 'abc';
a = a + 'def';
var b = 5;
var c = 5;
b = 7;
var obj1 = {
a: 1,
b: 'bbb'
};
가상의 주소를 넣겠습니다. => (@0000)
변수 영역 확보: 변수 이름(obj1)에 해당하는 주소(@1002)가 할당됩니다.
데이터 그룹 저장: 객체 데이터를 저장하기 위해 별도의 주소(@5001)를 생성하고, 객체 내부 프로퍼티를 관리할 변수 영역(@7103, @7104)을 마련합니다.
프로퍼티 저장: 각 프로퍼티 이름(a, b)을 변수 영역에 지정하고, 값(숫자 1, 문자열 'bbb')을 데이터 영역(@5003, @5004)에 저장한 후 해당 주소를 프로퍼티 변수에 연결합니다.
기본형 데이터와 달리 참조형 데이터는 프로퍼티 변수 영역이 별도로 존재하며, 데이터 영역의 값은 불변입니다. 그러나 변수는 다른 값을 대입할 수 있어 참조형 데이터를 "가변값"으로 인식하게 됩니다.
참조형 데이터의 프로퍼티 재할당
var obj1 = {
a: 1,
b: 'bbb'
};
obj1.a = 2;
obj1의 a 프로퍼티에 숫자 2를 할당하려는 과정에서, 데이터 영역에 숫자 2가 없으면 빈 공간 @5005에 저장됩니다. 이 주소는 @7103에 저장되며, 객체 자체는 새로운 객체로 생성되지 않고 기존 객체(@5001) 내부의 값만 변경됩니다.
중첩된 참조형 데이터(객체)의 프로퍼티 할당
var obj = {
x: 3,
arr: [3, 4, 5]
};
변수 영역 확보: 변수 영역에 빈 공간 @1002를 확보하고 이름을 obj로 지정
객체 생성: 데이터 저장 공간 @5001에 객체를 저장하며, 객체의 프로퍼티를 저장할 변수 영역(@7103~?)을 생성
프로퍼티 지정: @7103에 이름 x, @7104에 이름 arr를 지정
숫자 저장: 데이터 영역에 숫자 3이 없으면 @5002에 저장하고, 그 주소를 @7103에 저장
배열 생성: 배열 데이터 그룹의 변수 영역(@8104~?)을 생성하여 주소 정보를 @5003 → @7104로 저장
배열 인덱스 설정: 배열 요소 3개를 위한 공간을 확보하고 인덱스(0, 1, 2)를 부여
숫자 저장:
인덱스 0: 숫자 3의 주소 @5002를 @8104에 저장
인덱스 1: 숫자 4가 없으면 @5004에 저장하고, 그 주소를 @8105에 저장
인덱스 2: 숫자 5가 없으면 @5005에 저장하고, 그 주소를 @8106에 저장
var a = 10;
var b = a;
var obj1 = {c: 10, d: 'ddd'};
var obj2 = obj1;
기본형 데이터: 변수 a는 빈 공간 @1001에 저장되며, 숫자 10은 데이터 영역 @5001에 저장
변수 b는 빈 공간 @1002에 생성되며, a의 값(@5001)을 복사해 저장
참조형 데이터: 변수 obj1은 빈 공간 @1003에 생성되며, 데이터 그룹은 @5002에 저장
그룹의 변수 영역 @7103~이 생성되어, c와 d가 각각 @5001(숫자 10)과 새로 생성된 문자열 주소에 연결
복사 과정: 변수 obj2는 빈 공간 @1004에 생성되며, obj1의 값(@5002)을 복사해 저장
기본형 데이터와 참조형 데이터 모두 복사 시 동일한 주소를 바라보게 됩니다(@5001, @5002).
하지만 데이터 할당 과정의 차이로, 참조형 데이터는 복사 후 객체 내부의 값을 공유하는 반면, 기본형 데이터는 독립적인 값을 가짐
변수 복사 이후 값 변경 결과 - 객체의 프로퍼티 변경 시
var a = 10;
var b = a;
var obj1 = {c: 10, d: 'ddd'};
var obj2 = obj1;
b = 15;
obj2.c = 20;
기본형 데이터: 숫자 15는 데이터 영역 @5004에 저장되고, 변수 b의 값(@1002)이 @5004로 변경
참조형 데이터: 숫자 20은 데이터 영역 @5005에 저장되고, obj2의 주소(@1004)가 가리키는 객체(@5002) 내부의 프로퍼티 c의 값이 @5005로 변경
변화 비교:기본형 데이터(a와 b): 변수 b의 값이 변경되면서 서로 다른 주소를 가리키게 됨
참조형 데이터(obj1과 obj2): 여전히 동일한 객체(@5002)를 참조, 객체 내부 프로퍼티만 변경됨
차이점:
기본형은 값이 복사되면서 별개의 주소를 바라봄
참조형은 객체의 주소를 복사하여 동일한 객체를 공유함
자바스크립트의 모든 데이터는 주소를 통해 참조되지만, 기본형은 값 복사가 한 번만 이루어지는 반면, 참조형은 추가 단계(객체 내부의 주소 참조)가 존재
이 차이를 이해하면 변수 복사 및 변경에 따른 동작 원리를 명확히 알 수 있다.
불변 객체와 가변 객체는 최근의 React, Vue.js, Angular 등에서 중요한 개념으로, 함수형 프로그래밍과 디자인 패턴에서도 핵심적인 역할을 합니다.
참조형 데이터의 가변성은 객체 내부의 프로퍼티를 변경할 때만 성립하며, 데이터 자체를 변경하려면 새로운 객체를 만들어야 합니다. 이를 통해 객체의 불변성을 유지할 수 있습니다. 불변성을 요구하는 경우, 새로운 객체를 만들어 재할당하거나 자동으로 새로운 객체를 생성하는 도구를 활용하여 관리할 수 있으며, 상황에 따라 불변 객체와 가변 객체를 구분해 사용할 수 있습니다.
var user = {
name: 'Jaenam',
gender: 'male'
};
var changeName = function(user, newName) {
var newUser = user;
newUser.name = newName;
return newUser;
};
var user2 = changeName(user, 'Jung');
if(user !== user2){
console.log('유저 정보가 변경되었습니다.');
}
console.log(user.name, user2.name);
console.log(user === user2);
user 객체의 name 프로퍼티를 변경한 후, user2 변수에 그 결과를 할당했지만, 두 변수는 동일한 객체를 참조하므로 서로 다른 객체가 아니게 됩니다.
두 변수의 name 프로퍼티가 모두 동일하고, 두 변수가 동일하다고 출력됩니다. 이는 객체가 가변적이어서 참조형 데이터의 변경이 다른 변수에 영향을 미친다는 문제를 나타냅니다.
객체의 가변성에 따른 문제점의 해결 방법
var user = {
name: 'Jaenam',
gender: 'male'
};
var changeName = funtion (user, newName) {
return {
name: newName,
gender: user.gender
};
};
var user2 = changeName(user, 'Jung');
if (user !== user2) {
console.log('유저 정보가 변경되었습니다.')
}
console.log(user.name, user2.name); // Jaenam Jung
console.log(user === user2); // false
user와 user2 객체는 서로 다른 객체이므로 안전하게 변경 전후를 비교할 수 있지만 changeName 함수에서 기존 객체의 프로퍼티를 하드코딩으로 입력하는 방식은 비효율적입니다. 객체의 프로퍼티가 많아질수록 입력해야 할 수고가 증가하기 때문에 모든 프로퍼티를 복사하는 방식을 사용하는 것이 더 효율적입니다.
얕은 복사는 바로 아래 단계의 값만 복사하는 방법이고,
깊은 복사는 내부 의 모든 값들을 하나하나 찾아서 전부 복사하는 방법입니다.
중첩된 객체에 대한 얕은 복사
var user = {
name: 'Jaenam',
urls: {
portfolio: 'http://github.com/abc',
blog: 'http://blog.com',
facebook: 'http://facebook.com/abc'
}
};
var user2 = copyObject(user);
user2.name = 'Jung';
console.log(user.name === user2.name);
user.urls.portfolio = 'http://portfolio.com';
console.log(user.urls.portfolio === user2.urls.portfolio);
user2.urls.blog = '';
console.log(user.urls.blog === user.urls.blog);
user2의 name 프로퍼티를 변경해도 원본 객체인 user의 값이 영향을 받지 않았습니다. 하지만 14번째 줄과 17번째 줄에서는 urls 프로퍼티 내부 값이 공유되므로, 원본과 사본 모두에 영향을 미쳤습니다. 이는 객체 내부에 중첩된 프로퍼티들이 참조를 공유하기 때문입니다. 이러한 문제를 방지하려면 user.urls 프로퍼티도 불변 객체로 만들어야 합니다.
중첩된 객체에 대한 깊은 복사
var user2 = copyObject(user);
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 === user.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;
};
copyObjectDeep 함수는 3번째 줄에서 객체인 경우 내부 프로퍼티를 순회하며 재귀적으로 호출해 복사하고 객체가 아닌 경우 8번째 줄에서 값을 그대로 복사합니다. 원본과 사본이 완전히 독립된 객체가 되어 한쪽의 변경이 다른 쪽에 영향을 주지 않게 됩니다.
자바스크립트에는 undefined와 null이라는 두 가지 "없음"을 나타내는 값이 있습니다. 이 중 undefined는 값이 지정되지 않은 경우 자바스크립트 엔진이 자동으로 부여합니다.
undefined와 배열
var arr1 = [];
arr1.length = 3;
console.log(arr1); // [empty x 3]
var arr2 = new Arrat(3);
console.log(arr2); // [empty x 3]
var arr3 = [undefined, undefined, undefined];
console.log(arr3); // [undefined, undefined, undefined]
1번째 줄에서 빈 배열을 만들고 크기를 3으로 설정하면, 3번째 줄에서 [empty x 3]이 출력됩니다. 배열에 빈 요소가 확보되었지만
값(심지어 undefined)도 할당되지 않았음을 뜻합니다.
5번째 줄에서는 new Array(3)를 사용해 배열을 생성했으며
결과는 [empty x 3]으로 동일합니다.
8번째 줄에서는 배열 리터럴을 사용해 각 요소에 undefined를 명시적으로 부여했으며 9번째 줄 출력 결과는 [undefined, undefined, undefined]로 앞의 두 결과와 다릅니다.
이처럼 비어있는 요소와 undefined를 할당한 요소는 출력 결과부터 다릅니다.
비어있는 요소는 순회와 관련된 많은 배열 메서드들의 순회 대상에서 제외됩니다.
빈 요소와 배열의 순회
var arr1 = [undefined, 1];
var arr2 = [];
arr[1] = 1;
arr1.forEarch(funtion (v, i) {console.log(v, i); }); // undefined 0/1 1
arr2.forEarch(funtion (v, i) {console.log(v, i); }); // 1 1
arr1.map(function (v, i) {return v + i; }); // [NaN, 2]
arr2.map(function (v, i) {return v + i; }); // [empty, 2]
arr1.filter(function (v) {retrun !v; }); // [undefined]
arr2.filter(function (v) {retrun !v; }); // []
arr1.reduce(function (p, c, i) {retrun p + c + i; }, ''); // undefined011
arr2.reduce(function (p, c, i) {retrun p + c + i; }, ''); // 11
arr1은 undefined를 명시적으로 할당한 배열이고, arr2는 빈 배열의 특정 인덱스에만 값을 할당한 배열입니다.
배열 메서드(forEach, map, filter, reduce 등)의 동작에서 차이가 발생합니다.
arr1은 모든 요소를 순회하며 결과를 출력합니다.
arr2는 빈 요소를 건너뛰고 처리하지 않습니다.
빈 요소와 명시적으로 할당된 undefined의 차이 때문입니다.
'값을 대입하지 않은 변수' 즉 데이터 영역의 메모리 주소를 지정하지 않은 식별자에는 자바스크립트가 직접 undefined를 할당합니다. TC39의 ECMAScript 명세서에서는 다음과 같이 설명하고 있습니다.
var 변수는 environmentRecord가 인스턴스화될 때 생성되면서 undefined로 초기화 됩니다.
TC39: 자바스크립트 언어의 표준을 정의하고 발전시키는
Ecma International의 기술 위원회인 "Technical Committee 39" 를 의미합니다.
TC39는 ECMAScript(자바스크립트의 표준 사양)의 제안, 논의, 개발을 담당하며, 언어의 새 기능과 업데이트를 정의하는 중요한 역할을 합니다.
Environment Record는 자바스크립트의 실행 컨텍스트(Execution Context) 내부에서 변수를 저장하고 관리하는 메커니즘 스코프와 관련된 데이터(변수, 함수, 매개변수 등)를 구조화하여 유지합니다.
자바스크립트 엔진은 코드 실행 중에 Environment Record를 사용해 변수의 선언, 할당, 조회를 처리합니다.
한편 ES6에서 등장한 let,const에 대해서는 undefined를 할당하지 않은 채로 초기화를 마치며 실제 변수가 평가되기 전까지는 해당 변수에 접근할 수 없습니다. 명세서에는 let과 const변수는 environmentRecord가 인스턴스화 될 때 생성되지만 실제 변수가 평가되기 전까지는 접근할 수 없다고 설명하고 있습니다.
메모리와 데이터
컴퓨터는 데이터를 0과 1의 비트로 저장하며, 효율적인 처리를 위해 비트를 묶어 사용
변수와 식별자
변수는 데이터를 저장하는 공간 식별자는 변수를 참조하기 위한 이름
변수 선언과 데이터 할당은 별도로 이루어지며 메모리 관리 효율성을 높이기 위해 변수와 데이터를 분리 저장
undefined와 null
undefined: 값이 없거나 할당되지 않은 상태를 의미하며 자바스크립트 엔진이 자동으로 부여하거나 사용자가 명시적으로 지정
null: 값이 없음을 명시적으로 표현하는 값으로 사용자가 의도적으로 "빈 상태"를 지정할 때 사용
불변값과 가변값
불변값: 기본형 데이터(number, string 등)는 변경 시 새로운 메모리 공간에 저장
가변값: 참조형 데이터(object, array 등)는 내부 값을 수정 가능하며 주소를 통해 참조
참조형 데이터의 특징
참조형 데이터는 변수에 주소를 저장하고, 데이터는 별도의 공간에 관리
중첩된 데이터 구조(예: 객체 안의 배열)는 각 요소가 개별 메모리 주소를 가짐
변수 복사
기본형 데이터는 값을 복사(독립적)
참조형 데이터는 주소를 복사(같은 객체를 공유)