자바스크립트에서 값을 비교할 때, 동등 비교 연산자인 ==와 일치 비교 연산자인 ===을 사용하게 된다. 두 연산자 모두 비교한 피연산자 값이 일치하면 true값을 반환하고, 비교한 피연산자 값이 일치하지 않으면 false값을 반환하게 된다.
==비교 연산자 ==는 양쪽 값을 비교해서 같으면 true, 다르면 false를 반환하지만, 두 피연산자 값이 서로 다른 타입이더라도 자동으로 일부 피연산자의 타입의 형 변환을 통해 값을 비교한다.
console.log(100 == "100"); // true
console.log(1 == true); // true
console.log(null == undefined); // true
===비교 연산자와 달리, 피연산자 값의 타입이 다르면 타입을 변환하지 않고 있는 그대로 값을 비교하여 결과값을 리턴한다. 일치 연산자는 비교 연산자에 비해 엄격한 비교를 수행한다.
console.log(100 === 100); // true
console.log(100 == "100"); // false
console.log(1 == true); // false
console.log(null == undefined); // false
비교 연산자(==)의 경우 타입이 다르면 자동으로 변환해서 비교하기 때문에 원치 않은 결과값을 반환할 수 있어 권장하지 않는다.
얕은 복사(Shallow Copy)와 깊은 복사(Deep Copy)를 설명하기 전에 잠시 기본값(원시값)과 참조값에 대해서 설명하도록 하겠다.
기본 자료형을 의미한다. 우리가 흔히 알고 있는 자료형인, Number, String, Boolean, Null, Undefined 등이 여기에 해당한다. 변수에 기본값을 저장하게 되면 변수의 메모리 공간에 실제 데이터 값이 저장되고, 할당된 변수를 조작하려고 하면 저장된 실제 값이 조작된다.
여러 자료형으로 구성되는 메모리에 저장된 '객체'이다. 여기에 Object 또는 Symbol 등이 해당된다. 변수에 객체를 저장하면 독립적인 메모리 공간에 값을 저장하고, 변수에 저장된 메모리 공간의 참조(위치 값)를 저장하게 된다. 사실 할당된 변수를 조작하는 것은 객체 자체를 조작하는 것이 아닌, 해당 객체의 참조를 조작하는 것이다.
기본값을 복사할 때 그 값은 다른 독립적인 메모리 공간에 할당하기 때문에, 복사를 하고 값을 수정해도 기존의 값을 저장한 변수에는 영향이 없다. 이처럼 실제 값을 복사하는 것을 깊은 복사라고 한다. (자료형을 깊은 복사한 것)
const a = 'a';
let b = 'b';
b = 'c';
console.log(a); // 'a'
console.log(b); // 'c'
참조값을 복살 때 변수가 객체의 참조를 가리키고 있기 때문에 복사된 변수 또한 객체가 저장된 메모리 공간의 참조를 가리키고 있다. 그래서 복사를 하고 객체를 수정하면 두 변수는 똑같은 참조를 가리키고 있기 때문에 기존 객체를 저장한 변수에 영향을 끼치게 된다. 이처럼 객체의 참조값(주소값)을 복사하는 것을 얕은 복사라고 한다.
const a = {
num1: 1,
num2: 2,
};
let b = a;
b.num1 = 3;
console.log(a); // { num1: 3, num2: 2 }
console.log(b); // { num1: 3, num2: 2 }
얕은 복사란 객체를 복사할 때 기존 값과 복사된 값이 같은 참조를 가리키고 있는 것을 의미한다. 객체 안에 객체가 있을 경우 한 개의 객체라도 기존 변수의 객체를 참조하고 있다면 이를 얕은 복사라고 한다.
얕은 복사의 대표적인 방법 중 하나이다. start 부터 end 인덱스까지 기존 배열에서 추출하여 새로운 배열을 리턴하는 메소드이다. start와 end를 설정하지 않았다면, 기존 배열 전체를 얕은 복사한다.
const original = [ '1', 2, 'true', 4, "hi" ];
const copy = original.slice();
console.log(JSON.stringify(original) === JSON.stringify(copy)); // true
copy.push(10);
console.log(JSON.stringify(original) === JSON.stringify(copy)); // false
console.log(original); // [ 'a', 2, true, 4, "hi" ]
console.log(copy); // [ 'a', 2, true, "hi", 10 ]
기존 배열에는 영향을 끼치지 않아 깊은 복사로 보일 수 있지만, 기본값은 기본적으로 깊은 복사이다. slice() 메소드는 기본적으로 얕은 복사를 수행한다.
const original = [
[1, 1, 1, 1],
[0, 0, 0, 0],
[2, 2, 2, 2],
[3, 3, 3, 3],
];
const copy = original.slice();
console.log(JSON.stringify(original) === JSON.stringify(copy)); // true
// 복사된 배열에만 변경과 추가
copy[0][0] = 99;
copy[2].push(98);
console.log(JSON.stringify(original) === JSON.stringify(copy)); // true
console.log(original);
// [ [ 99, 1, 1, 1 ], [ 0, 0, 0, 0 ], [ 2, 2, 2, 2, 98 ], [ 3, 3, 3, 3 ] ]
console.log(copy);
// [ [ 99, 1, 1, 1 ], [ 0, 0, 0, 0 ], [ 2, 2, 2, 2, 98 ], [ 3, 3, 3, 3 ] ]
1차원 배열이 아닌 중첩 구조를 갖는 2차원 배열이면 얕은 복사를 수행한다.
Object.assign(생성할 객체, 복사할 객체) 메소드는 첫 번째 인자로 빈 객체를 넣어주고, 두 번째 인자로 복사할 객체를 넣어주면 된다.
const object = {
a: "a",
number: {
one: 1,
two: 2,
},
};
const copy = Object.assign({}, object);
copy.number.one = 3;
console.log(object === copy); // false
console.log(object.number.one === copy.number.one); // true
복사된 객체 copy 자체는 기존 object와 다른 객체지만 그 안에 들어가 있는 값은 기존 object안의 값과 같은 참조값을 가리키고 있다.
const object = {
a: "a",
number: {
one: 1,
two: 2,
},
};
const copy = {...object}
copy.number.one = 3;
console.log(object === copy); // false
console.log(object.number.one === copy.number.one); // true
깊은 복사된 객체는 객체 안에 객체가 있을 경우에도 원본과의 참조가 완전히 끊어진 객체를 의미한다.
JSON.stringify()는 객체를 json 문자열로 변환하는데 이 과정에서 원본 객체와의 참조가 모두 끊어진다. 객체를 json 문자열로 변환 후, JSON.parse()를 이용해 다시 원래 객체(자바스크립트 객체)로 만들어 준다. 이 방법은 가장 간단하고 쉽지만 다른 방법에 비해 느리며, 객체가 function일 경우, undefined로 처리한다는 단점이 있다.
const object = {
a: "a",
number: {
one: 1,
two: 2,
},
arr: [1, 2, [3, 4]],
};
const copy = JSON.parse(JSON.stringify(object));
copy.number.one = 3;
copy.arr[2].push(5);
console.log(object === copy); // false
console.log(object.number.one === copy.number.one); // false
console.log(object.arr === copy.arr); // false
console.log(object);
// { a: 'a', number: { one: 1, two: 2 }, arr: [ 1, 2, [ 3, 4 ] ] }
console.log(copy);
// { a: 'a', number: { one: 3, two: 2 }, arr: [ 1, 2, [ 3, 4, 5 ] ] }
const object = {
a: "a",
number: {
one: 1,
two: 2,
},
arr: [1, 2, [3, 4]],
};
function deepCopy(object) {
if (object === null || typeof object !== "object") {
return object;
}
// 객체인지 배열인지 판단
const copy = Array.isArray(object) ? [] : {};
for (let key of Object.keys(object)) {
copy[key] = deepCopy(object[key]);
}
return copy;
}
const copy = deepCopy(object);
copy.number.one = 3;
copy.arr[2].push(5);
console.log(object === copy); // false
console.log(object.number.one === copy.number.one); // false
console.log(object.arr === copy.arr); // false
console.log(object);
// { a: 'a', number: { one: 1, two: 2 }, arr: [ 1, 2, [ 3, 4 ] ] }
console.log(copy);
// { a: 'a', number: { one: 3, two: 2 }, arr: [ 1, 2, [ 3, 4, 5 ] ] }
const deepCopy = require("lodash.clonedeep")
const object = {
a: "a",
number: {
one: 1,
two: 2,
},
arr: [1, 2, [3, 4]],
};
const copy = deepCopy(object);
copy.number.one = 3;
copy.arr[2].push(5);
console.log(object === copy); // false
console.log(object.number.one === copy.number.one); // false
console.log(object.arr === copy.arr); // false
console.log(object);
// { a: 'a', number: { one: 1, two: 2 }, arr: [ 1, 2, [ 3, 4 ] ] }
console.log(copy);
// { a: 'a', number: { one: 3, two: 2 }, arr: [ 1, 2, [ 3, 4, 5 ] ] }