데일리
[23.10.18] 원시 값과 객체
paikpaik
2023. 10. 19. 14:34
글의 목적
- 이 파트를 정리할까 말까 고민하다가 참조와 복사를 설명하기 위해서는 이 부분부터 다루는것이 좋다고 판단되어서 글을 쓰게 됨.
본론
1. 원시 값과 객체의 비교
- js는 총 7가지의 데이터 타입(number, string, boolean, null, undefined, symbol, object)이 있고 크게 원시 타입과 객체 타입으로 구분할 수 있다.
- 원시 타입과 객체 타입으로 구분하는 이유는 3가지의 중요한 차이점이 있기 때문이다.
- 원시 값은 변경 불가능한 값이고 객체 값은 변경 가능한 값이다.
- 원시 값을 변수, 즉 확보된 메모리 공간에 할당하면 실제 값이 저장되지만, 객체 값은 참조 값이 저장된다.
- 원시 값을 갖는 변수를 다른 변수에 할당하면 원시 값이 복사되어 전달되지만(pass by value, 값에 의한 전달), 객체를 가리키는 변수를 다른 변수에 할당하면 객체의 참조 값이 복사되어 전달된다.(pass by reference, 참조에 의한 전달)
1.1 원시 값
- 값과 변수를 구분해야 한다. 변수는 값을 저장하기 위한 공간, 또는 공간을 구분하기 위해 붙인 이름이고 값은 그 변수에 저장된 데이터로서 표현식이 평가된 산물이다.
- 원시 값은 읽기 전용 값으로서 변경 불가능한 값이다.
- 변경 불가능 하다는 것은 즉, 변수가 아니라 값에 대한 진술이고 변수 값을 변경할 수 없다는 말은 아니다.
- 변수 값은 언제든지 재할당이 가능하고 재할당이 불가능한 상수, const 또한 변수이다.
- 변수를 재할당하면 원시값은 변경되지 않고 새로운 메모리 주소에 데이터만 바뀌어 할당된다.
let data = "number";
data = "string";
console.log(data); // string
// But, data 는 number를 가리키지만 않는것이지 메모리에 저장되어 있다.
// 이는 상태 추적을 가능하게 하고 데이터의 신뢰성을 보장한다.
1.1.1 유사배열객체 문자열
- 문자열은 이터러블로서 배열과 유사하게 각 문자에 접근할 수 있다.
- 이로 인해서 재밌는 함수형 프로그래밍을 가능하게 한다. (github - myfx)
- 이터러블 프로토콜을 따르는 문자열에 한 문자에 값을 변경하려고 해도 원시값인 문자열은 변경되지 않는다. 에러도 발생하지 않는다.
let test = "test";
console.log(test[0]); // t
console.log(test.length); // 4
test[0] = "T";
console.log(test); // test
1.1.2 값에 의한 전달
- 값에 의한 전달은 사실 정확한 말이 아니고 메모리 주소에 의한 전달이 정확하다.
- 결국 메모리 주소가 계속 바뀔뿐이지 원시 값은 영향이 없다는 말이다.
let friend = 10; // 메모리 주소 : 1 = 10 (원시 값)
let best = friend; // 메모리 주소 : 2 = 10 (원시 값)
console.log(friend, best); // 10 10
// console.log(주소1, 주소2)
friend = 100; // 메모리 주소 : 3 = 100 (원시 값)
console.log(friend, best); // 100 10
// console.log(주소3, 주소2)
- c++의 포인터 처럼 js의 메모리 주소를 확인하면 참 좋겠지만 현재로서는 js에 C++에 접근해서 코드를 수정하지 않는이상 어렵다.
1.2 객체
- 객체는 프로퍼티의 수가 정해져 있지 않고 언제든지 동적으로 추가 삭제가 가능하다.
- 즉, 객체는 메모리 공간을 사전에 잡아두기가 어렵다.
- 그리고 js는 싱글 스레드 기반이기 때문에 객체의 메모리를 원시 값처럼 담아두면 메모리 효율과 성능에 문제가 생긴다. 그렇기 때문에 참조 값을 두고 계속 수정하는 것이다.
- 객체는 참조 타입의 값 즉, 객체는 변경 가능한 값이다.
- 이 말이 조금은 어렵거나 무슨차이인거냐 라고 생각이 들면 원시 값은 계속해서 집의 주소가 바뀌기 때문에 집의 인테리어가 보장이 되지만 참조 값은 집 주소는 그대로 있고 내부 인테리어만 계속 바꾸는거라고 생각하면 딱 맞다. 인테리어가 보장되지 않는다.
const person = { // 메모리 주소 : 1 참조 주소 1 name: "test"
name: "test",
};
person.name = "best"; // 메모리 주소 : 1 참조 주소 1 name: "best"
person.age = 8; // 메모리 주소 : 1 참조 주소 1 name: "best", age : 8
console.log(person); // { name: 'best', age: 8 }
1.2.1 얕은복사와 깊은복사
- 객체는 여러개의 식별자가 하나의 객체를 공유할 수 있다.
- 참조 값을 복사하는 것은 얕은복사이다.
- 얕은 복사는 참조 공간을 복사하는 것이고 깊은 복사는 주소자체를 복사하는 것이다.
const lodash = require("lodash");
const mother = { // 메모리 주소 : 1, 참조 주소 : 1
son: {
you: 1,
},
};
const copy = { ...mother }; // 얕은복사, 메모리 주소 : 2, 참조 주소 : 1
console.log(copy); // { son: { you: 1 } }
console.log(copy === mother); // false (메모리주소2, 참조주소1) !== (메모리주소1, 참조주소1)
console.log(copy.son === mother.son); // true (참조주소1 === 참조주소1)
const deepCopy = lodash.cloneDeep(mother); // 깊은복사, 메모리 주소 : 3, 참조 주소 : 2
console.log(deepCopy); // { son: { you: 1 } }
console.log(deepCopy === mother); // false (메모리주소3, 참조주소1) !== (메모리주소1, 참조주소1)
console.log(deepCopy.son === mother.son); // false (참조주소2) !== (참조주소1)
1.2.2 참조에 의한 전달
- 사실 참조에 의한 전달이라는 말은 js에서는 정확하게 들어맞지 않는다.
- 정확하게 말하면 값에 의한 전달만 가능하고 그 값이 원시값이냐 참조값이냐의 차이가 있을 뿐이다.
- 아래 코드가 이젠 이해가 될것이고 여러개의 식별자가 하나의 객체를 공유한다는 말도 이해가 될 것이다.
const person = { // 메모리 주소 : 1, 참조 주소 : 1
name: "test",
};
const copy = person; // 얕은복사, 메모리 주소 : 2, 참조 주소 : 1
console.log(copy === person); // true
copy.name = "share"; // 참조 주소 1을 변경
person.address = "참조공간"; // 참조 주소 1을 변경
console.log(person); // { name: 'share', address: '참조공간' }
console.log(copy); // { name: 'share', address: '참조공간' }
1.2.3 객체의 비교
- 객체는 변수와 다르게 값이 같아도 다른 객체이다. 단, 객체의 참조 값을 비교하면 같은 값이다.
const var1 = "test";
const var2 = "test";
const test1 = {
name: "test",
};
const test2 = {
name: "test",
};
console.log(var1 === var2); // true
console.log(test1 === test2); // false
console.log(test1.name === test2.name); // true
결론
- 애매하게 이해하고 있었던 얕은복사와 깊은복사에 대해서 정확하게 파악이 되는 시간이었다.