Ch5 에서는 액션을 모두 계산으로 빼낼 수 없는 경우에 대해 어떻게 대처해야 하는지 소개하고 있다.
Ch5 요약
액션을 계산으로 뽑을 수 없을 경우 암묵적 입력과 출력을 되도록 인자와 리턴값으로 전환하자
계산도 분류가 가능하다. 같은 계산이더라도 무엇을 다루는지에 따라 어떤 엔티티를 다룬다면 특정 엔티티에 대한 동작 ( 자주 바뀌지 않음 ), 비즈니스 규칙 ( 자주 바뀔 수 있음 ), 배열 유틸리티 ( 자주 바뀌지 않음, 재사용성 높음 ) 으로 분류하자.
분류한 기준에 따라 계산을 분리하면 재사용성이 더 높아지고 구분된 그룹과 분리된 계층으로 구성할 수 있다.
엔티티에 대한 동작
function make_cart_item(name, price) { // 장바구니에 대한 동작
return {
name: name,
price: price
}
}
function add_item(cart, item) { // 제품에 대한 동작
return add_element_last(cart, item);
}
배열 유틸리티
function add_element_last(array, elem) {
var new_array = array.slice();
new_array.push(elem);
return new_array;
}
비즈니스 규칙
// 요구사항에 따라 자주 변할 수 있는 로직이다.
function gets_free_shipping(cart) {
return calc_total(cart) >= 20;
}
function calc_tax(amount) {
return amount * 0.10;
}
CH5 에서는 카피온라이트와 방어적 복사라는 용어를 다시 접하게 되었다. 결국 얕은 복사와 깊은 복사를 나타내는 용어이긴 한데 할당 연산자, Object.assign, spread, slice() 등을 사용시 어디까지가 얕은 복사고 깊은 복사인지 새로 정리할 수 있었다.
얕은 복사 (shallow copy, copy on write) : 객체 복사시 원본값과 복사된 값이 같은 참조 (메모리 주소) 를 가리킨다.
- 얕은 복사는 복사본의 속성이 만들어진 원본 객체와 같은 참조를 공유하는 복사이다.
- 중첩된 객체의 값이 아닌 최상위 속성만 복사한다.
- 복사본의 최상위 속성을 재할당해도 원본 객체에 영향을 끼치지 않는다.
- 복사본의 중첩 객체 속성을 재할당하면 원본 객체에 영향을 끼친다.
깊은 복사 (deep copy, depensive copy) : 복사된 객체가 다른 주소를 참조하여 내부의 값만 복사된다.
- 깊은 복사는 복사본의 속성이 복사본이 만들어진 원본 객체와 같은 참조를 공유하지 않는 복사이다.
할당 연산자 - 복사본을 생성하지 않고 참조만 할당한다.
Object.assign() - 객체 자체는 깊은 복사가 수행되나 2차원 이상은 얕은 복사가 수행된다. 메소드를 복사한다.
Array.slice() - 기본적으로 깊은 복사를 수행하며 2차원 이상은 얕은 복사를 수행한다. 메서드를 복사한다. ... 책에서는 얕게 복사한다고 소개되어 있다.
JSON.parse(JSON.stringify(obj)) - 깊은 복사를 수행한다. 메소드를 복사할 수 없다.
spread - assign() 처럼 복사한 객체 자체는 깊은 복사이나 내부의 객체는 얕은 복사가 수행된다.
structuredClone(obj) - 깊은 복사가 수행되며 메서드를 복사할 수 없다.
깊은 복사
- spread 👉 기본적으로 깊은 복사이나 2차원 이상에서 얕은 복사, 메소드 복사 가능
- JSON.parse(JSON.stringify(obj)) 👉 메소드 복사 불가능
- structuredClone(obj) 👉 메소드 복사 불가능
이미지 출처 : https://developer.mozilla.org/ko/docs/Glossary/Deep_copy
얕은 복사
- 할당 연산자
- Array.slice() 👉 기본적으로 깊은 복사이나 2차원 이상에서 얕은 복사, 메소드 복사 가능, 배열에 항목이 다른 배열이나 객체를 참조시 해당 배열, 객체는 복사되지 않는다.
- Object.assign() 👉 기본적으로 깊은 복사이나 2차원 이상에서 얕은 복사, 메소드 복사 가능
이미지 출처 : https://developer.mozilla.org/ko/docs/Glossary/Shallow_copy
깊은 복사와 얕은 복사가 미친듯이 헷갈리기 시작한다. 책과 mdn 에서 사용하는 깊은 복사와 얕은 복사가 서로 다른 대상을 가리키는 듯 이상하다. 일단 동작에 대해서만 위와 같은 표현으로 명시해두었다.
이미지 출처 : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice
mdn 을 보면 알 수 있듯이 slice() 는 배열의 일부를 얕은 복사를 하여 새로운 배열 객체로 반환한다고 나와 있다.
설명중 유용한 부분을 발견했다.
즉, slice() 는 요소의 얕은 복사본을 반환하는데 객체 참조가 아닐 경우 깊은 복사처럼 원본에 변경사항이 영향을 끼치지 않으나 객체 참조일 경우 원본 배열과 새 배열은 모두 동일한 객체를 참조한다.
할당 연산자
let obj = {
a: 1,
b: 2,
};
let copy = obj;
obj.a = 5;
console.log(copy.a);
// Result
// a = 5;
Object.assign()
let obj = {
a: 1,
b: {
c: 2,
},
}
let newObj = Object.assign({}, obj);
console.log(newObj); // { a: 1, b: { c: 2} }
obj.a = 10;
console.log(obj); // { a: 10, b: { c: 2} } 👉 기본적으로 깊은 복사
console.log(newObj); // { a: 1, b: { c: 2} } 👉 깊은 복사라서 다른 참조를 갖기에 a의 값이 다르다.
newObj.b.c = 30;
console.log(obj); // { a: 10, b: { c: 30} } 👉 2차원 이상은 얕은 복사
console.log(newObj); // { a: 20, b: { c: 30} } 👉 얕은 복사라 같은 참조를 갖기에 c 의 값이 같다.
slice()
const arr = ['a', 'b', ['c']];
const copy = arr.slice(); // 얕은 복사
copy[2].push('d'); // 원본인 arr 도 바뀐다.
slice() 는 2차원 이상에서 얕은 복사를 한다.
const arr = ['a', 'b', ['c']];
const copy = arr.slice();
copy.push("d") // slice 는 깊은복사를 수행하는데 2차원 이상에서 얕은 복사를 수행하는 것이었다.
slice() 는 기본적으로 깊은 복사를 수행한다.
const arr = ['a', 'b', ['c'], {exec: function exec () { return true}}];
const copy = arr.slice();
slice() 는 메서드도 복사를 한다.
JSON.parse(JSON.stringify(obj))
let obj = {
a: 1,
b: {
c: 2,
},
}
let newObj = JSON.parse(JSON.stringify(obj));
obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 2 } } 👉 깊은 복사 수행
let obj = {
name: 'scotch.io',
exec: function exec() {
return true;
},
}
let method1 = Object.assign({}, obj); // 메서드까지 복사
let method2 = JSON.parse(JSON.stringify(obj)); // 메서드를 복사할 수 없다.
console.log(method1); //Object.assign({}, obj)
/* result
{
exec: function exec() {
return true;
},
name: "scotch.io"
}
*/
console.log(method2); // JSON.parse(JSON.stringify(obj))
/* result
{
name: "scotch.io"
}
*/
spread
const array = [
"a",
"c",
"d", {
four: 4
},
];
const newArray = [...array];
newArray[3].four=5;
console.log(array);
깊은 복사가 수행되었고 2차원의 경우 얕은 복사가 수행되었다.
spread 는 JSON.parse(JSON.stringify(obj)) 와 달리 메서드도 복사가 된다.
structuredClone(obj)
const obj = [
"a",
"b",
{three: 3},
{method: function exec() {
return true;
}}
];
const copy = structuredClone(obj);
structuredClone(obj) 는 메서드가 복사되지 않는다.
const obj2 = [
"a",
"b",
{three: 3},
];
const copy = structuredClone(obj2);
copy.push("c");
copy[2].three = 4;
console.log(obj2);
깊은 복사가 정상적으로 수행되었다.
reference
Copying Objects in JavaScript
blog
지니의 기록 - 얕은복사, 깊은 복사
docs
mdn - structuredClone()