Notice
Recent Posts
Recent Comments
Link
«   2025/05   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
Archives
Today
Total
관리 메뉴

heyday2024 님의 블로그

[3주차 JS 기초 문법] 데이터 타입, 메모리, 변수 선언과 데이터 할당, 얕은 복사와 깊은 복사, null/ undefined 본문

프론트엔드 부트캠프

[3주차 JS 기초 문법] 데이터 타입, 메모리, 변수 선언과 데이터 할당, 얕은 복사와 깊은 복사, null/ undefined

heyday2024 2024. 10. 15. 11:15

(이미지 출처 : https://velog.io/@imjkim49/자바스크립트-데이터-타입-정리 )

 

1. 데이터 타입 종류와 메모리

// (1) 데이터 타입의 종류(기본형과 참조형)
// 자바스크립트에서 값의 타입은 크게 기본형(Primitive Type)과 참조형(Reference Type)으로 구분

// 기본형: 기본형은 단일 값을 나타내며, 그 값이 **불변(immutable)**합니다. 즉, 한 번 할당된 값은 변경할 수 없습니다. 복사할 때도 값 자체가 복사되며, 값이 담긴 주소값이 복사됩니다.
// number: 숫자형
// string: 문자열
// boolean: 참/거짓
// null: 값이 없음을 나타냄
// undefined: 값이 정의되지 않음
// symbol: 고유하고 변경 불가능한 값 (ES6에서 도입)
// bigint: 큰 정수값을 표현 (ES11에서 도입)

let x = 10;
let y = x; // y에 x의 값이 복사됨
x = 20;

console.log(x); // 20
console.log(y); // 10 (복사된 값은 변하지 않음)

//  예시에서 y는 x의 값이 복사되었을 뿐, x가 나중에 변경되더라도 y는 영향을 받지 않습니다. 이처럼 기본형은 값 자체가 복사됩니다.

// 참조형: 참조형은 객체 형태로 값을 저장하며, 변경 가능한(mutable) 속성을 갖습니다.
// 참조형 데이터를 복사할 때는 값이 저장된 메모리 주소를 복사합니다.즉, 복사된 변수는 같은 객체를 참조하게 됩니다.
// Object: 객체
// Array: 배열
// Function: 함수
// Date: 날짜
// RegExp: 정규식
// Map, Set 등 (ES6에서 도입된 객체 타입)

let obj1 = { name: "Alice" };
let obj2 = obj1; // obj1이 참조하는 객체의 주소값이 obj2에 복사됨
obj1.name = "Bob";

console.log(obj1.name); // 'Bob'
console.log(obj2.name); // 'Bob' (두 변수는 같은 객체를 참조하고 있음)

// 위 코드에서 obj1과 obj2는 같은 객체를 참조하고 있기 때문에, 하나의 변수를 변경하면 다른 변수도 영향을 받습니다. 참조형은 값 자체가 아닌, 값이 저장된 주소값을 복사합니다.

// 💡 **[기본형과 참조형의 구분 기준]**
// 1. 복제의 방식:
//  기본형은 값이 담긴 주소값을 바로 복제
//  참조형은 값이 담긴 주소값들로 이루어진 묶음을 가리키는 주소값을 복제

// 2. 불변성의 여부:
//  기본형은 불변성을 띔
//  참조형은 불변성을 띄지 않음

//  기본형: 값을 변경할 수 없기 때문에 새로운 값을 할당하면 기존 값은 그대로 유지되고, 새로운 메모리 공간에 할당됩니다.
// 참조형: 객체의 속성 값을 직접 변경할 수 있습니다. 이는 같은 객체를 참조하는 다른 변수에 영향을 미칩니다.

// (2) 메모리와 데이터에 관한 배경지식

// 1. 메모리, 데이터
//     1. 비트
//         1. 컴퓨터가 이해할 수 있는 가장 작은 단위(0, 1)
//         2. 0과 1을 가지고 있는 **메모리를 구성하기 위한 작은 조각**과 같은 것
//         3. 이 작은 조각들이 모여서 **‘메모리’**가 만들어지는 것.
//     2. 바이트
//         1. 0과 1만 표현하는 비트를 모두 찾기는 부담되어서 만들어진 바이트
//         2. 1개 → 2개 → … → 8개(새로운 단위 : byte) => 8 bit = 1byte

//     3. 메모리(memo + ry) : byte 단위로 구성
//         1. 모든 데이터는 byte 단위의 식별자인 메모리 주소값을 통해서 서로 구분이 됩니다.

//         💡 **만일, 64비트(8바이트) 정수는 메모리에 어떻게 저장할 수 있을까요?**
//         ⇒ 64비트를 8개의 바이트로 분할하고, 각 바이트를 메모리에 저장해야 해요. 각 바이트는 8개의 비트를 갖기 떄문에 64비트 정수는 메모리에서 **8개의 연속된 바이트**에 저장된답니다.

//     4. java, c와 다른 javascript의 메모리 관리 방식(feat. 정수형)
//         1. 8을 저장하는 방법
//             1. JS : let a = 8(8byte)
//             2. JAVA
//                 1. byte a = 8(1byte)
//                 2. short a = 8(2byte)
//                 3. int a = 8(4byte)
//                 4. long a = 8(16byte)
//         2. java 또는 c언어가 초기에 등장했을 때 메모리 관리가 더욱 중요했어서 숫자 데이터 타입은 크기에 따라 다양하게 지정해줘야 할 만큼 개발자가 **handling 할 요소**들이 많았어요.
//          하지만 상대적으로 신 언어인 javascript는 이런 부분에서는 상당히 편리하죠. 메모리 이슈까지는 고민하지 않아도 되니까요 😎

//    + 식별자 vs변수
//     1. var testValue = 3
//     2. 변수 = 데이터(3)
//     3. 식별자 = 변수명(testValue)

 

2. 변수 선언과 데이터 할당

1. 프로그램에서 변수를 선언하면, 컴파일러나 interpreter이 해당 변수에 대한 메모리를 할당할 준비를 함.

2. 변수가 어떤 자료형인지에 따라 필요한 메모리 크기를 결정함(자바스크립트는 런타임에 결정함)

3. 선언된 변수에 대해 메모리 공간이 할당됨. --> 여거서의 메모리 공간이 아래 이미지의 데이터 영역에 해당됨.

4. 스택/ 힙 메모리에서 할당이 이뤄짐.(기본자료형은 스택에, 객체와 같은 참조 자료형은 힙에 저장되고 그 주소가 스택에 저장됨)

5. 기본 자료형은 스택에 값이 직접 저장되기 때문에 그 값을 바로 사용할 수 있음. // 참조 자료형은 힙에 저장된 데이터를 참조하는 포인터가 스택에 저장되는 방식임. 그래서 변수를 통해 힙에 저장된 그 객체에 접근할 때마다 스택에 저장된 주소를 참조하는 방식으로 실제 데이터를 가져옴.

6. 그래서 변수의 값을 변경할 떄, 기본 자료형은 스택에서 직접 그 값을 덮어쓰는 반면, 참조 자료형은 힙에 저장된 객체가 그대로 유지되며 스택의 참조가 변경됨.

7. 그렇다면, 그대로 남아있는 더 이상 참조되지 않는 객체는 어떻게 되는가??? JS는 메모리 관리를 자동으로 처리하는 가비지 컬렉션(Garbage Collection) 메커니즘을 사용, 더 이상 사용되지 않는 메모리(참조되지 않는 객체)를 해제하는 방식으로 메모리 누수 방지. 

 


<기본 자료형>

 

- 할당 예시: 

/** 선언과 할당을 풀어 쓴 방식 */
var str;
str = 'test!';

/** 선언과 할당을 붙여 쓴 방식 */
var str = 'test!';
 

 

 

 

 

Q. 값을 바로 변수에 대입하지 않는 이유(= 값을 데이터영역에 저장하지 않고, 바로 변수 영역에 함께 저장하지 않는 이유)

  1. 자유로운 데이터 변환
    1. 이미 입력한 문자열이 길어진다면?
    2. 숫자는 항상 8byte로 고정이지만, 문자는 고정이 아니에요(영문 : 1byte, 한글 : 2byte). 그래서, 이미 1003 주소에 할당된 데이터를 변환하려 할 때 훨씬 더 큰 데이터를 저장하려 한다면 → 1004 이후부터 저장되어있는 모든 데이터를 오른쪽으로 다~~~ 미뤄야 하겠죠..!? (모든 값들을 큰 데이터가 들어올 때마다 shift하게되면 비효율적)
  2. 메모리의 효율적 관리
    1. 똑같은 데이터를 여러번 저장해야 한다면?
    2. 1만개의 변수를 생성해서 모든 변수에 숫자 1을 할당하는 상황을 가정해 봅시다. 모든 변수를 별개로 인식한다고 한다면, 1만개의 변수 공간을 확보해야 해요.
      1. 바로 대입하는 경우, 숫자형은 8 바이트 고정이죠?
        1. 1만개 * 8byte = 8만 byte
      2. 현재 사용하는 방식: 변수 영역에 별도 저장 
        1. 변수 영역 : 2바이트 1만개 = ****2만바이트</aside>
        2. 변수 영역에 저장되는 데이터는 2바이트로 가정했어요!
        3. 데이터 영역 : 8바이트 1개 = 8바이트
        4. 총 : 2만 8바이트 --> 즉, 데이터 영역에 저장된 같은 데이터를 여러 변수가 참조하도록해서 메모리를 더욱 효율적으로 관리 가능.

  1. 메모리를 기준으로 다시한번 생각해보는 두 가지 주요 개념
    1. 변수 vs 상수
      1. 변수 : 변수 영역 메모리(식별자, 데이터 영역 메모리 주)를 변경할 수 있음
      2. 상수 : 변수 영역 메모리를 변경할 수 없음
    2. 불변하다 vs 불변하지 않다
      1. 불변하다 : 데이터 영역 메모리를 변경할 수 없음
      2. 불변하지 않다 : 데이터 영역 메모리를 변경할 수 있음
  2. 불변값과 불변성(with 기본형 데이터)
// a라는 변수가 abc에서 abcdef가 되는 과정을 통해 불변성을 유추해봅시다!

// 'abc'라는 값이 데이터영역의 @5002라는 주소에 들어갔다고 가정할게요.
var a = 'abc';

// 'def'라는 값이 @5002라는 주소에 추가되는 것이 아니죠!
// @5003에 별도로 'abcdef'라는 값이 생기고 a라는 변수는 @5002 -> @5003
// 즉, "변수 a는 불변하다." 라고 할 수 있습니다.
// 이 때, @5002는 더 이상 사용되지 않기 때문에 가비지컬렉터의 수거 대상이 됩니다.
a = a + 'def';

<참조형 자료형>

기본형 데이터와 달리 참조형 데이터는 변수 할당 시 객체의 변수 (property) 영역이 별도로 존재함.

 

  • 참조형 데이터가 불변하지 않다(가변하다)라고 하는 이유
    • 기본형 데이터는 새로운 변수 값으로 변경 시 데이터 영역에 변경된 값이 추가되고 그 주소가 바뀌었다. (즉, 원래 저장되었던 값 자체가 변하지는 않는다//대신 변수 영역의 주소가 변함 --> 불변)
    •  그러나 참조형 데이터는 값 변경 시,객체 별도의 데이터 영역(데이터가 저장된 곳을 가리키는 주소)이 바뀜. --> 가변

 

 

  • 정리:
    • 변수와 상수를 구분하는 기준은 변수 영역
      • 상수는 변수 영역이 변하지 않음
    • 참조형과 기본형을 구분하는 기준은 데이터 영역(불변성과 가변성)
      • 참조형은 데이터 영역이 가변함.
      • 기본형은 데이터 영역이 불변함.
  • 기본 자료형한 번의 참조를 통해 값에 바로 접근할 수 있습니다.
    • 변수 영역 → 데이터 영역(값)
  • 참조 자료형두 번의 참조를 거쳐 힙에 있는 데이터를 접근합니다.
    • 변수 영역 → 데이터 영역(힙 주소) → 힙(실제 데이터)

<중첩 객체>

- 중첩객체란, 객체 안에 또 다른 객체가 들어가는 것

- 객체는 배열, 함수 등을 모두 포함하는 상위개념이기 때문에 배열을 포함하는 객체도 중첩객체

 

var obj = {
	x: 3,
	arr: [3, 4, 5],
}

// obj.arr[1]의 탐색과정은 어떻게 될까요? 작성하신 표에서 한번 찾아가보세요!

obj, arr 영역이 따로 있음.

 

  • 참조 카운트란? 
    • 객체나 리소스에 몇 개의 참조가 있는지 카운트하여, 참조가 더 이상 없을 때 자동으로 해당 메모리를 해제하는 방식
    • 참조 카운트가 0인 객체는 더 이상 사용되지 않는다는 뜻이기 때문에, 가비지 컬렉터에 의해 메모리에서 제거됩니다.
      • 가비지컬렉터(GC, Garbage Collector): 더 이상 사용되지 않는 객체를 자동으로 메모리에서 제거하는 역할.
        • 자바스크립트 엔진에서 내부적으로 수행되며, 개발자는 가비지 컬렉션에 대한 직접적인 제어를 할 수 없습니다.

<변수 복사: 기본형 vs 참조형>

기본형인 a와 b: 복사된 새로운 식별자인 b가 다른 값을 선언받을 때 데이터 영역이 바뀌지 않고, 새로운 값인 15를 참조하는 그 주소만 b의 변수영역에서 바뀌기 때문에 a와 b는 각각 따로 선언받은 데이터의 주소를 참조함으로 다른 값을 갖는다. 반면, 참조형인 obj1과 obj2: obj 영역의 참조 주소가 바뀐다, 이 말은 즉, 데이터 영역이 바뀐다는 뜻이다. obj1과 obj 2가 참조하는 주소는 변수 영역에서 동일하고, obj 영역에서의 데이터를 참조하는 주소가 바뀜으로, obj1과 obj2의 값이 모두 바뀐다.

 

 

// 기본형 변수 복사의 결과는 다른 값!
a !== b;

// 참조형 변수 복사의 결과는 같은 값!(원하지 않았던 결과😭)
obj1 === obj2;

- 결국 이런 결과가 나옴...


그렇다면 어떻게 복사된 객체의 값이 원래의 객체와 서로 영향 받지 않게 할 수 있을까...?

객체 자체를 변경하면 그 객체를 위한 데이터 영역이 따로 생성되기 때문에 원래의 객체의 값이 변화되지 않음.


불변 객체

- 객체의 속성에 접근해서 그 값을 변경하면 가변이 성립됨.

- 반면, 객체 데이터 자체를 변경(새로운 데이터를 할당)하면, 기존 데이터는 변경되지 않음.-> 불변

 

// 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

위 코드는 newUser = user 부분에 의해 같은 데이터 영역 주소를 가리킨다. 그래서 newUser.name을 다른 값으로 바꾸면, 그 바뀐 값의 데이터 주소를 기존의 User도 같이 가리키게 된다. 즉, 두 객체는 일치연산자로 비교했을 때 같은 객체로 판단함. 

 

그렇다면, 어떻게 해야 기존의 객체가 불변하게 하고, 완벽한 복사를 할 수 있을까?

첫번째 시도...

// 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 👍

 위의 코드는 함수를 이용해 객체를 아예 새롭게 할당해서 반환한다. 앞서 말했듯이 객체를 새로 할당하면 그 객체만을 위한 데이터 영역이 생김으로 기존의 객체와 다른 새로운 객체로 만들어지기 때문에 두 객체는 설령 안에 내용 값이 같아도 같은 데이터 주소를 참조하지 않기 때문에 두 객체를 비교하는 일치연산자의 결과값은 false이다. 

 

즉, 위의 코드는 user1이 user2가 바뀌었다해도 전혀 영향이 없다.(불변 객체)

하지만, 이 방법은 최선이 아님!!!

 

---> 문제점: changeName 함수는 새로운 객체를 만들기 위해 변경할 필요가 없는 gender 프로퍼티를 하드코딩( 프로그래밍에서 고정된 값을 코드에 직접 입력)으로 입력함.

만약에 이러한 속성이 10개, 20개, ... 엄청 많았다면, 그 코드를 다 작성하기엔 너무 성가심...

 

그래서 제시된 얕은 복사 

 

두번째 시도... 얕은 복사

//이런 패턴은 어떨까요?
var copyObject = function (target) {
	var result = {};

	// for ~ in 구문을 이용하여, 객체의 모든 프로퍼티에 접근할 수 있습니다.
	// 하드코딩을 하지 않아도 괜찮아요.
	// 이 copyObject로 복사를 한 다음, 복사를 완료한 객체의 프로퍼티를 변경하면
	// 되겠죠!?
	for (var prop in target) {
		result[prop] = target[prop];
	}
	return result;
}

- 각각의 객체의 속성을 for 구문으로 자동으로 복사.

 

//위 패턴을 우리 예제에 적용해봅시다.
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);

- 확실히 더욱 간편하고, 속성의 개수가 아무리 많아도 for 구문으로 손쉽게 복사할 수 있음.

 

하지만!!!! 이 얕은 복사도 문제가 있음...

문제점: 중첩된 객체에 대해서는 완벽한 복사를 할 수 없음.... 예를 들어, 객체의 속성 안에 다른 객체가 들어가면 for 구문이 한 단계 더 들어가야 완벽한 복사가 가능함. 위의 예시는 for 구문이 depth 1 밖에 안되어서 아래의 예제에서는 완벽한 복사를 할 수 없음.

 

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

이런 식으로 객체 안에 객체까지 다 복사할 수가 없음. 왜냐하면, 중첩된 객체의경우 참조형 데이터가 저장된 프로퍼티를 복사할 때, 주소값만 복사하기 때문.

 

그러면 그냥 for 구문을 두번 사용해서 (depth2) 복사하면 되지 않나요??

맞음, ser.urls 프로퍼티도 불변 객체로 만들어야하는데 이를 위해 for 안에 for 구문을 넣어서 (depth 2) 복사를 하면 해결이 될 수 있음... 하지만!!!!

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);

문제점: 위의 예시와 달리 객체 안에 객체 안에 객체 안에 객체.........와 같이 여러 중첩 객체의 속성을 갖으면, 계속해서 for 구문으로 하나 하나 복사하는 것이 번거로움.

 

그래서 마지막 시도... 깊은복사!!!

 

객체의 프로퍼티 중 기본형 데이터는 그대로 복사 + 참조형 데이터는 다시 그 내부의 프로퍼티를 복사하는 전략!!!

이를 위해 재귀적 수행을 함.

 

recursive function을 사용!!!(함수나 알고리즘이 자기 자신을 호출하여 반복적으로 실행)

재귀 함수 기본 구조:

  • 기본 사례(Base Case): 재귀 호출을 멈출 조건. 이 조건이 충족되면 함수는 더 이상 자기 자신을 호출하지 않고 종료됨.
  • 재귀 호출(Recursive Call): 함수가 자기 자신을 호출하는 부분. 매 호출마다 문제의 크기를 줄여서 결국 기본 사례에 도달하도록 함.
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;
}

 

 

- 들어오는 객체 속성이 object(객체)이고 null(내용 x)이 아닐 때, 즉, 참조형일 때, 각각의 값을 복사하는데, 이때 자신의 함수를 계속 호출하여 위의 조건인 object(객체)이고 null(내용 x)이 아닐 때까지 실행함. 결국 중첩된 객체도 한 꺼풀 한 꺼풀 복사되며 까지면, 값자체는 기본형의 형태가 되기 때문에 객체가 사라질 때까지 계속 진행함.

 

//결과 확인
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.stringify() 함수를 사용하여 객체를 문자열로 변환한 후, 다시 JSON.parse() 함수를 사용하여 새로운 객체를 생성하기 때문에, 원본 객체와 복사본 객체가 서로 독립적으로 존재합니다. 따라서 복사본 객체를 수정해도 원본 객체에 영향을 미치지 않습니다.
  • JSON을 이용한 깊은 복사는 다른 깊은 복사 방법에 비해 코드가 간결하고 쉽게 이해할 수 있습니다.

단점:

  • JSON을 이용한 깊은 복사는 원본 객체가 가지고 있는 모든 정보를 복사하지 않습니다. 예를 들어, 함수나 undefined와 같은 속성 값은 복사되지 않습니다.
  • JSON.stringify() 함수는 순환 참조(Recursive Reference)를 지원하지 않습니다. 따라서 객체 안에 객체가 중첩되어 있는 경우, 이 방법으로는 복사할 수 없습니다.

따라서 JSON을 이용한 깊은 복사는 객체의 구조가 간단하고, 함수나 undefined와 같은 속성 값이 없는 경우에 적합한 방법입니다. 만약 객체의 구조가 복잡하거나 순환 참조가 있는 경우에는 다른 깊은 복사 방법을 고려해야 합니다.

 

// 원본 객체
const original = {
  name: "John",
  age: 30,
  address: {
    city: "New York",
    zipcode: 10001
  }
};

// JSON을 이용한 깊은 복사
const copy = JSON.parse(JSON.stringify(original));

// 복사된 객체 수정
copy.address.city = "Los Angeles";

// 결과
console.log(original.address.city); // "New York" (원본은 영향받지 않음)
console.log(copy.address.city);     // "Los Angeles"

Undefined 와 Null

둘 다 없음을 나타내지만, 그 목적이 미세하게 다름.

 

1. Undefined: 일반적으로는 자바스크립트 엔진에서 값이 있어야 할 것 같은데 없는 경우, 자동으로 부여

  • 변수에 값이 지정되지 않은 경우, 데이터 영역의 메모리 주소를 지정하지 않은 식별자에 접근할 때
  • .이나 []로 접근하려 할 때, 해당 데이터가 존재하지 않는 경우
  • return 문이 없거나 호출되지 않는 함수의 실행 결과
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. 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);

 

  • null과 undefined 모두 없다 라는 뜻이여서 동등 연산자에서는 두 값을 비교하면 true로 나옴
console.log(null == undefined);  // true
  • 하지만, 일치연산자는 type까지 같은지를 비교하기 때문에 두 값을 비교하면 false로 나옴.
console.log(null === undefined);  // false

 

 

드디어 데이터 타입 정리 끝!!!!