...
Call By Value
먼저 값에 의한 전달에 대한 특징부터 살펴보자.
- argument로 값이 넘어온다.
- 값이 넘어올 때 복사된 값이 넘어온다.
- caller(호출하는 녀석)가 인자를 복사해서 넘겨줬으므로 callee(호출당한 녀석)에서 해당 인자를 지지고 볶아도 caller는 영향을 받지 않는다.
예시)
let a = 1;
function addOne(b) { //callee
b = b + 1;
}
addOne(a); //caller
console.log(a); // 1
- a라는 변수를 인수로 넘겨주었다.
- 이때 1이라는 값은 복사되어 인자b에게 할당된다.
- a와 b의 값은 같지만 둘 다 다른 메모리 공간을 차지하게 되어 별개의 존재이기 때문에 함수 내부에서 b를 지지고 볶아도 a한테는 아무런 영향이 없다.
Call By Reference
이번에는 Call By Reference에 대한 특징을 살펴보자.
- arguments로 reference(값에 대한 참조 주소, 메모리 주소를 담고있는 변수)를 넘겨준다.
- reference를 넘기다 보니 해당 reference가 가리기는 값을 복사하지는 않는다.
- caller(호출하는 녀석)가 인자를 복사해서 넘기지 않았으므로 callee(호출당한 녀석)에서 해당 인자를 지지고 볶으면 caller는 영향을 받는다.
예시)
const me = {
name: 'Jimmy'
};
function changeName(person) { //callee
person.name = 'Joo';
}
console.log(me); // { name: 'Jimmy' }
changeName(me); //caller
console.log(me); // { name: 'Joo' }
- 매개변수 person에 인수로 넘겨진 me의 참조값이 전달되었다.
- 따라서 me와 person는 같은 참조값을 가지고 있다.
- changeName 함수 안에서 person.name을 바꾸어 주면 me.name도 변하는 것을 확인할 수 있다.
Call By Sharing
const me = {
name: 'Jimmy'
};
function changeName(person) { //callee
person = { name: 'Joo' };
}
console.log(me); // { name: 'Jimmy' }
changeName(me); //caller
console.log(me); // 정답은??
👉 정답은 뭘까?
음.. 함수에 참조타입을 인수로 넘겨주었고 함수 내에서 인자를 지지고 볶고(새로운 객체를 할당) 했으니까 함수 외부에 있는 객체(me)도 변했겠지?
참조타입은 Call By Reference이니까! 그렇다면 정답은 { name: 'Joo' }!
정답은 { name: 'Jimmy' }이다. 아무런 변화가 없다.
왜 그런걸까? 무엇이 잘못된걸까?
방금 위에 있던 예시 코드를 그림을 그려가며 따라가보면 왜 저런 결과가 나왔는지 이해하기 쉽다.
먼저
const me = { name: 'Jimmy'};
객체를 만들고 me라는 변수에 할당하였다. 이를 간단한 그림으로 표현하자면 아래와 같다.
(박스 위에 빨간색으로 적힌 16진수가 메모리 주소다.)
참조타입이기 때문에 me라는 변수는 객체의 참조값을 가지고 있다.
다만 일반적인 C언어의 &me 같은 레퍼런스 주소값에 바로 값이 담겨져 있는게 아닌, 변수 주소가 객체 값 주소를 참조하고 있는 형태이다.
다음으로 함수를 정의하고 함수를 호출하였다.
function changeName(person) { //callee
person = { name: 'Joo' };
}
changeName(me);
호출하고 인수가 넘어가는 부분부터 살펴보자.
인수로 me객체를 넘겨주었다. 이때 정확하게 넘고 짚어가야할 부분은 me를 인수로 넘겼지만 사실상 변숙밧이 아닌 참조값이 복사되어 넘어간다.
따라서 인수(매개변수)인 person은 복사된 참조값을 가지고있고 같은 객체를 가리키고 있다.
이것이 왜 call by referece로 작동하는 까닭이다.
이는 아래 그림과 같다.
마지막으로 함수 내부가 실행된다.
person = { name: 'Joo'}
위 코드가 실행되어 새로운 객체가 인수인 person한테 할당된다.
이를 아래 그림을 통해 살펴보자.
먼저 새로운 객체({name: 'Joo'})가 생성된다. 그 후에 person에게 할당된다.
이를 통해 person이 원래 가지고 있던 me의 참조값(0x111)이 새로운 객체의 참조값(0x222)로 바뀌게 된다.
즉, me와 person은 이제 다른 객체를 참조하고 있다.
위에서 설명했던 모든 특징을 종합하여 call by sharing이라 한다.
한 마디로 call by value도 아니고 call by reference도 아닌 것이다.
sharing이라 이름 지은 이유가 원본 객체로의 접근을 공유한다는 특징에서 따온 것이 아닐까 조심스럽게 생각해본다.
python, java, ruby, javascript 등의 언어에서 이러한 평가 전략을 사용한다.
특징을 정리하면 아래와 같다.
- argument로 전달되는 변수가 원시값일 경우 call by value처럼 평가되고, 아닐 경우는 parameter에 참조(메모리 주소)만 전달된다.
- callee의 parameter에는 caller에서 전달한 참조(메모리 주소)만 전달되므로 -> callee에서 동적 프로퍼티 할당을 시도하면 실제 객체의 프로퍼티 값은 바뀌지만 -> 재할당을 시도하면 원본 객체가 재할당되는 call by reference와는 다르게 우변의 값이 새로운 메모리 주소에 올라가고 좌변의 지역변수는 그 값을 가리키게 된다.
📍결론
결국, 변수가 가리키는 메모리 공간에 저장되어 있는 값을 복사하여 전달한다는 관점에서 바라볼 때
자바스크립트는 항상 값에 의한 전달(Call By Value)만 존재한다고 말할 수 있다.
값이 원시값이냐 참조값이냐의 차이만 있을 뿐이다.
원시 타입의 경우 우리가 알고 있듯이 값이 복사되어 전달되고
참조 타입의 경우 메모리 주소의 값(참조값)이 복사되어서 전달되기 때문에 두 경우 모두 동작 원리는 같다.
이를 Call by Sharing 이라고 부르기도 한다.
Reference
https://perfectacle.github.io/2017/10/30/js-014-call-by-value-vs-call-by-reference/
https://velog.io/@aiden/call-by-value-vs-call-by-reference-feat.-call-by-sharing
이 글이 좋으셨다면 구독 & 좋아요
여러분의 구독과 좋아요는
저자에게 큰 힘이 됩니다.