[javascript] call by value
2018-09-15

의문은 아래와 같은 코드에서 시작되었습니다.

function change(obj) {
  obj = { p1: 100 }
}
const o = { p1: 1 }
change(o)
console.log(o)

객체를 매개변수로 받는 change 함수에 새롭게 만든 o객체를 인자로 전달하였습니다. 그리고 change 함수에서 obj 변수에 새로운 객체를 할당하였습니다. 과연 o는 어떤 값이 출력될까요?

Call By Value

가끔 일하다보면, 정말 기초적인 부분에서 실수하는 경우가 있습니다. 최근에 저도 그랬습니다. 새로운 기술이나 도구 등을 익히는 것도 중요합니다만, 기본을 중요시하는 자세가 우선시해야 한다는걸 새삼 깨닫게 되었습니다. Call By...로 시작하는 함수 호출 방법도 기본중에 기본입니다. 언어에 따라서, 호출 방식에 따라서 인자가 평가되는 과정이 달라집니다. 언어를 익힐때 기본중에 기본이죠.

인자를 전달하는 방식에 따라 함수 호출 방법은 크게 두가지로 나눠집니다. Call By ValueCall By Reference입니다. 그러면, 자바스크립트는 어떤 방식으로 함수를 호출할까요? 바로 Call By Value입니다. 흔히들 잘못 알고 있는 사실이 한가지 있습니다. 바로 함수의 매개변수가 원시타입(Primitive Type)인 경우에는 Call By Value 고, 매개변수가 객체형태면 Call By Reference 로 동작한다는 것입니다. 이는 완전히 잘못된 사실입니다. 블로그를 검색해보면, 이렇게 잘못된 사실을 바탕으로 쓰여진 글을 쉽게 찾아볼 수 있습니다.

원시타입 (Primitive Type)

자바스크립트에는 5 가지 원시타입이 있습니다. boolean, number, string, null, undefined 이렇게 다섯가지 입니다. 함수의 인자로 원시타입의 값이 전달되면, 함수가 실행될때 전달받은 인자를 복사합니다. 인자가 복사되었기 때문에 전달된 인자와 전달 받은 인자는 전혀 다른 값(주소값이 다른)이 됩니다. 코드로 확인해 보겠습니다.

function change(pri) {
  pri = 10
}
const p = 1
change(p)
console.log(p) // p = 1

변수 p에 1 이 할당된 다음, change 함수의 인자로 p 가 전달되었습니다. change 함수 내에서는 매개변수 pri에 10 을 할당하여 값을 변경 시킵니다. change 함수가 호출된 이후 log 를 찍어보면, p 값은 change 함수가 호출되기 전과 동일하게 1이 됩니다. 즉, 처음 선언한 p와 change 함수로 전달한 인자는 별도의 값으로 존재한다는 뜻입니다.

재할당 (Reassigning of Reference Type)

주소값을 직접 찍어볼 방법이 없기 때문에(있으면 좀 알려주세요^^;;), === 연산자로 참조값이 동일한지 비교해보도록 하겠습니다.

let obj = { p1: 10 }
let objCopy = obj
console.log(obj === objCopy) // true

objCopy = { p2: 100 }
console.log(obj === objCopy) // false

obj 라는 객체를 선언합니다. 그리고 objCopy 에 obj 를 할당합니다. 좀더 자세히 알아보기 위해 임의의 주소값으로 표현해보겠습니다.

변수
obj <0x001>
objCopy <0x001>
주소 객체
0x001 { p1: 10 }

objCopy 는 obj 와 동일한 객체를 가리키고 있습니다. 그래서 === 연산자로 비교해보면 서로 같다(true)고 출력됩니다. 그 다음, objCopy 에 새로운 객체를 할당합니다. 그러면, 아래와 같이 바뀝니다.

변수
obj <0x001>
objCopy <0x002>
주소 객체
0x001 { p1: 10 }
0x002 { p2: 100 }

objCopy 의 주소값이 변경되었습니다. objCopy 는 원래 0x001라는 주소값을 value 로 가지고 있었습니다. 그런데, 새로운 객체를 할당했더니 주소값이 변경되었습니다. 그 이유는 { p2: 100 }이라는 객체는 새로운 주소로 만들어집니다. 그리고 기존에 잇던 objCopy 라는 변수에 할당했으므로, 이제 objCopy 는 0x002 주소값을 가리키게 되었습니다. 이해를 쉽게 하기 위해 코드를 조금 변형시켜보겠습니다.

let obj = { p1: 10 } // obj 주소값 : 0x001
let objCopy = obj // objCopy 주소값 : 0x001
const newObj = { p2: 100 } // newObj 주소값 : 0x002
objCopy = newObj // objCopy 주소값 : 0x002

newObj 라는 새로운 객체가 objCopy 에 할당되었으니 objCopy 가 가리키는 주소값이 당연히 바뀌게 되는 것입니다. 애초에 객체타입(reference type)에 할당되는 값이 실제 객체가 아니라 객체를 가리키는 주소값이기 때문에 당연한 결과인지도 모르겠습니다.

인자를 함수내에서 변경(속성만 변경)하면 어떻게 될까?

function change(obj) {
  obj.p1 = 100
}
const o = { p1: 1 }
change(o)
console.log(o)
변수
o <0x001>
obj <0x001>
주소 객체
0x001 { p1: 1 }

처음 할당된 o 객체의 주소값은 0x001입니다. change 함수를 호출되면 인자 o 가 복사됩니다. 복사된 객체 obj 는 o 객체의 주소값을 갖는 새로운 변수가 됩니다(위의 표 참고). 즉, obj 와 o 두가지 변수가 동일한 객체를 바라보고 있다는 뜻입니다. 그래서 obj 객체의 속성을 변경하면 객체 o 의 속성도 변경됩니다.

그럼 이제 처음에 봤던 코드를 다시 한번 살펴보도록 합니다.

function change(obj) {
  obj = { p1: 100 }
}
const o = { p1: 1 }
change(o)
console.log(o)

log 에 찍히는 값은 바로 { p1: 1 }입니다. 위에서 보았던 재할당을 잘 기억하면서 위 코드의 주소값 변화를 살펴보겠습니다.

const o = { p1: 1 };

변수
o <0x001>
주소 객체
0x001 { p1: 1 }

change(o)호출, 인자(주소값)가 복사됩니다.

변수
o <0x001>
obj <0x001>
주소 객체
0x001 { p1: 1 }

change 함수 내, obj = { p1: 100 };

변수
o <0x001>
obj <0x002>
주소 객체
0x001 { p1: 1 }
0x002 { p1: 100 }

마지막으로 console.log(o)에서 { p1: 1 }가 출력됩니다. 즉, 객체가 바뀌지 않았습니다. 이유는 o 객체가 여전히 0x001을 가리키고 있기 때문입니다.

정리

자바스크립트에서 함수 호출시 call by value로 인자가 전달됩니다. call by value 는 함수가 호출되면 전달한 인자가 복사됨을 뜻합니다. 인자가 복사되기 때문에 원시타입(primitive type)의 경우에는 함수 바깥의 변수와 서로 별개로 동작합니다. 하지만, array 를 포함한 객체타입이 인자로 전달된 경우에는 변수에 할당되어 있는 객체의 주소값이 복사됩니다. 그래서 함수내에서 매개변수의 속성을 변경하면 함수 바깥에서도 동일하게 변경되는 것입니다. 그런데, 만약 함수내에서 매개변수가 재할당되었다면, 함수 바깥의 변수와 함수내 매개변수가 서로다른 객체를 바라보게 됩니다. 그래서 함수 내부에서 아무리 객체를 변형시키더라도 외부의 객체에 영향을 줄 수 없습니다.

참고자료