JavaScript에서 자료형(type)이란 값(value)의 종류.
원시 자료형(primitive type)
42, 'string', true, undefined, null
[0, 1, 2] // 배열
{name: 'kimcoding', age: 45} // 객체
function sum (x, y) { return x + y } // 함수
⚡ 이처럼 원시 자료형과 참조 자료형의 특징은 서로 대비.
원시 자료형
을 변수에 할당하면 값 자체가 할당된다.
let num = 20;
변수 num
을 선언하면 컴퓨터는 num이라는 이름의 공간을 확보.
그리고 20이라는 원시 값을 그 공간에 저장.
이처럼 원시 자료형은 값 자체를 저장
여러 개의 값을 다룰 수 있는 참조 자료형
은 어떻게 값을 저장할까?
let arr = [0, 1, 2, 3];
배열의 요소 각각이 하나의 값이기 때문에 하나의 공간에 배열 자체를 저장하는 것은 불가능
JavaScript는 특별한 저장 공간에 참조 자료형을 저장한 후, 그 저장공간을 참조할 수 있는 주소값을 변수에 저장.
만약 어떤 변수에 저장되어 있는 원시 자료형
을 다른 변수에 할당하면 어떻게 될까?
let num = 20;
let copiedNum = num;
원시 자료형
은 값 자체가 복사된다.
num
과 변수 copiedNum
은 동일하게 20이라는 값을 가진다.참조 자료형
은 이와는 달리 주소값을 복사합니다.
let arr = [0, 1, 2, 3];
let copiedArr = arr;
참조 자료형
이 할당된 변수를 다른 변수에 할당하면, 이 두 변수는 같은 주소를 가리킨다.
원시 자료형
은 원본에 다른 값을 재할당해도 복사본에 영향을 미치지 않습니다. 참조 자료형
은 원본을 변경하면 복사본도 영향을 받는다.같은 주소를 참조하고 있기 때문
⚡ 한 번 생성된 원시 값은 변경할 수 없다.
let num = 20;
여기서 변수 num에 할당된 값을 숫자 20 대신, 다른 값으로 변경하고 싶으면 변수에 재할당을 하면 가능.
num = 30;
num
이라는 변수가 참조하던 공간에 들어 있던 20
이 30
으로 변경될 것 같지만, 메모리 내부에서는 이처럼 동작하지 않는다.30
이라는 원시 값을 저장하기 위한 새로운 공간을 확보한 뒤, 그 공간에 num이라는 이름을 붙이고 30
을 저장.원시 자료형
은 어떤 상황에서도 불변하는 읽기 전용 데이터.원시 자료형
이 높은 신뢰성을 가질 수 있는 요인.
- 남아 있는 값
20
은 어떻게 될까?
가비지 콜렉터(garbage collector)
: JavaScript 엔진은 이처럼 사용하지 않는 값을 자동으로 메모리에서 삭제.- 그러나 가비지 콜렉터가 어느 시점에 진행되는지는 예측할 수 없다.
참조 자료형
이 있는 저장공간(heap)의 주소값을 저장하고 있다.arr[3] = '3';
arr.push(4);
arr.shift();
console.log(arr); // [1, 2, '3', 4]
let str = 'code';
str = 'states';
str
에 다른 문자열을 재할당하면 새로운 공간을 확보하고 그 공간의 이름이 str
이 된다. ‘states’
를 저장.console.log(str[0]) // 's'
console.log(str[2]) // 'a'
원시 자료형
을 할당한 변수를 다른 변수에 할당하면 값 자체의 복사가 일어난다.let num = 5;
let copiedNum = num;
console.log(num); // 5
console.log(copiedNum); // 5
console.log(num === copiedNum); // true
...
copiedNum = 6;
console.log(num); // 5
console.log(copiedNum); // 6
console.log(num === copiedNum); // false
참조 자료형
은 임의의 저장공간에 값을 저장하고 그 저장공간을 참조하는 주소를 메모리에 저장하기 때문에 다른 변수에 할당할 경우 값 자체가 아닌 메모리에 저장되어 있는 주소가 복사된다.let arr = [0, 1, 2, 3];
let copiedArr = arr;
...
console.log(arr); // [0, 1, 2, 3]
console.log(copiedArr); // [0, 1, 2, 3]
console.log(arr === copiedArr) // true
copiedArr
에 push()
메서드를 사용하여 배열의 요소를 추가하면,arr
에도 동일하게 요소가 추가arr
이 참조하고 있던 주소가 copiedArr
로 복사되어, 두 변수가 같은 주소를 참조하고 있기 때문 copiedArr.push(4);
...
console.log(arr); // [0, 1, 2, 3, 4]
console.log(copiedArr); // [0, 1, 2, 3, 4]
console.log(arr === copiedArr) // true
⚡ 참조 자료형이 저장된 변수를 다른 변수에 할당할 경우, 두 변수는 같은 주소를 참조하고 있을 뿐 값 자체가 복사되었다고 볼 수 없다.
slice()
메서드는 어떤 배열의 begin 부터 end 까지(end 미포함)에 대한 얕은 복사본을 새로운 배열 객체로 반환.
const animals = ['ant', 'bison', 'camel', 'duck', 'elephant'];
console.log(animals.slice(2));//["camel", "duck", "elephant"]
console.log(animals.slice(2, 4));//["camel", "duck"]
console.log(animals.slice(2, 2));//[ ]
console.log(animals.slice(1, 5));//["bison", "camel", "duck", "elephant"]
console.log(animals.slice(-2));//["duck", "elephant"]
console.log(animals.slice(2, -1));//["camel", "duck"]
console.log(animals.slice());//["ant", "bison", "camel", "duck", "elephant"]
slice()
를 사용하면 원본 배열을 복사할 수 있다.let arr = [0, 1, 2, 3];
let copiedArr = arr.slice();
console.log(copiedArr); // [0, 1, 2, 3]
console.log(arr === copiedArr); // false
copiedArr.push(4);
console.log(copiedArr); // [0, 1, 2, 3, 4]
console.log(arr); // [0, 1, 2, 3]
spread syntax
는 ES6에서 새롭게 추가된 문법.let arr = [0, 1, 2, 3];
console.log(...arr); // 0 1 2 3
let num = [1, 2, 3];
let int = [1, 2, 3];
...
console.log(num === int) // false
let arr = [0, 1, 2, 3];
let copiedArr = [...arr];
console.log(copiedArr); // [0, 1, 2, 3]
console.log(arr === copiedArr); // false
copiedArr.push(4);
console.log(copiedArr); // [0, 1, 2, 3, 4]
console.log(arr); // [0, 1, 2, 3]
Object.assign()
메서드는 출처 객체들의 모든 열거 가능한 자체 속성을 복사해 대상 객체에 붙여넣습니다. 그 후 대상 객체를 반환
const target = { a: 1, b: 2 };
const source = { b: 4, c: 5 };
const returnedTarget = Object.assign(target, source);
console.log(target);//{ a: 1, b: 4, c: 5 }
console.log(returnedTarget === target);//true
spread syntax
는 배열뿐만 아니라 객체를 복사할 때도 사용할 수 있다.let obj = { firstName: "coding", lastName: "kim" };
let copiedObj = {...obj};
console.log(copiedObj) // { firstName: "coding", lastName: "kim" }
console.log(obj === copiedObj) // false
slice()
, Object.assign()
, spread syntax
를 사용해도 참조 자료형 내부에 참조 자료형이 중첩된 구조는 복사할 수 없다.console.log(users === copiedUsers); // false
users
와 copiedUsers
를 동치연산자 ===
로 확인해 보면 false가 반환.users
와 copiedUsers
의 0번째 요소를 각각 비교하면 true
가 반환.users[0]
과 copiedUsers[0]
는 여전히 같은 주소값을 참조하고 있기 때문.console.log(users[0] === copiedUsers[0]); // true
slice()
,Object.assign()
, spread syntax
등의 방법으로 참조 자료형을 복사하면, 중첩된 구조 중 한 단계까지만 복사한다.
- 이것을
얕은 복사(shallow copy)
라고 한다.
먼저 중첩된 참조 자료형을 JSON.stringify()
를 사용하여 문자열의 형태로 변환하고,
반환된 값에 다시 JSON.parse()
를 사용하면, 깊은 복사와 같은 결과물을 반환.
JSON.stringify()
JSON.parse()
const arr = [1, 2, [3, 4]];
const copiedArr = JSON.parse(JSON.stringify(arr));
...
console.log(arr); // [1, 2, [3, 4]]
console.log(copiedArr); // [1, 2, [3, 4]]
console.log(arr === copiedArr) // false
console.log(arr[2] === copiedArr[2]) // false
const lodash = require('lodash');
const arr = [1, 2, [3, 4]];
const copiedArr = lodash.cloneDeep(arr);
console.log(arr); // [1, 2, [3, 4]]
console.log(copiedArr); // [1, 2, [3, 4]]
console.log(arr === copiedArr) // false
console.log(arr[2] === copiedArr[2]) // false