우석
변경 가능한 데이터 구조를 가진 언어에서 불변성 유지하기
모든 동작은 읽기 / 쓰기 또는 둘 다로 분류할 수 있다.
/* 읽기 */
function find_item(cart, index) {
return cart[index];
}
/* 쓰기 */
function add_item(cart, item) {
return cart.push(item);
}
/* 둘 다 */
function drop_item_by_name(cart) {
return cart.shift();
}
- 모든 동작은 읽기 / 쓰기 또는 둘 다로 분류할 수 있다.
- 읽기는 데이터가 바뀌지 않기 때문에 다루기가 쉽다.
- 하지만 쓰기 작업이 추가될 경우 데이터를 바꾼다.
- 쓰기 작업으로 바뀐 값은 어디서 사용될지 모르기 때문에 바뀌지 않도록 원칙이 필요하다.
카피-온-라이트로 쓰기를 읽기로 바꿀 수 있다.
- 카피-온-라이트 3개의 작업으로 이루어진다.
- 복사본 만들기
- 복사본 변경하기(원하는 만큼)
- 복사본 리턴하기
- 카피-온-라이트를 적용하면 외부 데이터를 변경하지 않기 때문에 읽기 작업으로 분류할 수 있다.
- 즉, 복사를 통해 데이터의 불변성이 보장되어 쓰기 작업의 복잡성이 사라진다.
/* 쓰기 */
function add_item(cart, item) {
var new_cart = cart.slice()
new_cart.push(item)
return new_cart;
}
/* 둘 다 */
function drop_item_by_name(cart) {
var new_cart = cart.slice()
var first = new_cart.shift()
return {
first,
cart: new_cart
}
}
- 위 코드처럼 카피-온-라이트를 적용하면 데이터의 불변성이 보장되어 쓰기 작업을 읽기로 바꿀 수 있다.
객체에 대한 카피-온-라이트
var array = [1, 2, 3, 4, 5];
var name = "hello";
var obj = {
array,
name
}
var new_obj = Object.assign({}, obj)
- 위 코드는 obj를 복사해 new_obj를 생성하는 코드이다.
- 하지만 Object.assign()를 활용해 객체를 복사해도 중첩 객체의 경우 얕은 복사가 되어 두 객체에서 공유하게 된다.
- 위 코드에서는 obj, new_obj가 array 키에 대한 value로 동일한 배열 객체(array)를 가진다.
/* item 객체 형태 */
{
name: "name",
price: 1234,
}
function set_price_by_name(cart, name, price) {
var new_cart = cart.slice()
for (var i = 0; i < new_cart.length; i++) {
if (new_cart[i].name === name) {
var new_item = Object.assign({}, new_cart[i]);
new_item.price = price;
new_cart[i] = new_item;
}
}
}
- 위 코드에서 깊은 복사가 일어나는 부분은 cart의 배열 객체와 Object.assign()에 의해 복사되는 item 객체 뿐이다.
- 즉, price가 변경되지 않는 item의 경우 불변성이 보장되지 않고 구조적 공유를 하게 된다.
- 하지만 이 객체들은 set_price_by_name() 메서드에서 변경하는 데이터가 아니기 때문에 문제가 되지 않는다.
- 위 메서드에서 변경이 일어나는 객체의 경우 복사본을 만들기 때문에 공유된 값은 변경되지 않는다고 확신할 수 있다.
만혁
변경 가능한 데이터 구조를 가진 언어에서 불변셩 유지하기
동작을 읽기, 쓰기 또는 둘 다로 분류하기
- 읽기 : 데이터에서 정보를 가져온다. 데이터를 변경하지 않는다.
- 쓰기 : 데이터를 변경한다.
카피-온-라이트 원칙 세 단계
- 복사본 만들기
- 복사본 변경하기
- 복사본 리턴하기
function add_elelment_last(array, elem) {
const newArray = array.slice(); // 1. 복사본 만들기
newArray.push(elem); // 2. 복사본 변경하기
return newArray; // 3. 복사본 리턴하기
}
add_elelment_last
함수는 데이터를 변경하지 않고 정보를 리턴했기 때문에 읽기
다
카피-온-라이트는 쓰기를 읽기로 바꾼다.
쓰면서 읽기도 하는 함수를 분리하기
function firstElement(array) {
return array[0];
}
function dropFirst(array) {
const copy = array.slice();
copy.shift();
return copy;
}
값을 두 개 리턴하는 함수로 만들기
function shift(array) {
const copy = array.slice();
const first = copy.shift();
return {
first: first,
array: copy
};
}
function shift(array) {
return {
first: firstElement(array),
array: dropFirst(array)
};
}
연습 문제
카피-온-라이트로 변경
const mailingList = [];
function addContact(list, email) {
const copy = list.slice();
copy.push(email);
return copy;
}
function submitFormHandler(event) {
const from = event.target;
const email = form.elements['email'].value;
const added = addContact(mailingList, email)
mailingList = added;
}
.pop()
메서드를 카피-온-라이트로 변경
const a = [1,2,3,4];
const b = a.pop();
console.log(b); // 4출력
console.log(a); // [1,2,3] 출력
function lastElement(array) {
return array[array.length - 1]
}
function dropLast(array) {
const copy = array.slice();
copy.pop();
return copy;
}
function pop(array) {
return {
last: lastElement(array),
array: dropLast(array)
};
}
.push()
메서드를 카피-온-라이트로 변경
function push(array, elem) {
const copy = array.slice();
copy.push(elem);
return copy;
}
addContact()
리팩토링
function addContact(mailingList, email) {
return push(mailingList, email);
}
arraySet()
함수 만들기
a[15] = 2;
function arraySet(array, idx, value) {
const copy = array.slice();
copy[idx] = value;
return copy;
}
objectSet()
함수 만들기
o['price'] = 37;
function objectSet(object, key, value) {
const copy = Object.assign({}, object);
copy[key] = value;
return copy;
}
setPrice()
리팩토링
function setPrice(item, newPrice) {
const edited = objectSet(item, 'price', newPrice);
return edited;
}
setQuantity()
함수 만들기
function setQuantity(item, newQuantity) {
const edited = objectSet(item, 'quantity', newQuantity);
return edited;
}
delete
연산 만들기
const a = {x : 1};
deletea['x'];
a // {} 출력
function objectDelete(object, key) {
const copy = Object.assign({}, object);
delete copy[key]
return copy;
}
다음 중첩된 동작을 카피-온-라이트로 변경
// as-is
function setQuantityByName(cart, name, quantity) {
for(const i = 0; i < cart.length; i++) {
if(cart[i].name === name) {
cart[i].quantity = quantity;
}
}
}
// to-be
function setQuantityByName(cart, name, quantity) {
const copy = cart.slice();
for(const i = 0; i < copy.length; i++) {
if(copy[i].name === name) {
copy[i] = setQuantity(copy[i], quantity);
}
}
return copy;
}