Dev

[JS] Call by reference가 없는 Javascript

hou27 2023. 4. 23. 13:27

잘못된 생각

최근까지 javascript는 함수 호출 시

참조형이면 넘길 때도 call by reference로 넘겨지고,

원시형이면 call by value로 넘겨지는 줄 알고 있었다.

 

그러다 최근 질문이 들어와서 답해주려다 자세히 알아보니,

잘못 알고 있었다는 것을 깨달았다.

 

Call by Value & Call by Reference

우선 흔히 알고 있는 두 가지 개념에 대해 살펴보자

 

Call by Value

호출자가 실제 매개 변수를 평가하고 그 값을 호출자에게 전달하는 관례
호출자에서 값 매개 변수의 수정 내용은 호출자에서 볼 수 없다.

 

 

Call by Reference

설명을 좀 줄이자면,

호출자(caller)가 함수에 전달한 실제 매개변수(actual parameter)가 변수일 경우, 컴파일러가 '해당 변수의 주소'를 호출된 함수(callee)에 전달한다. 호출된 함수 내에서 해당 매개 변수에 접근할 때, 실제 변수의 주소를 이용하여 값을 변경할 수 있게 된다.
함수 내에서 변경된 값이 호출자에서 전달한 변수에도 반영되는 결과를 가져오게 되는 것이다.

결론적으로 매개변수의 값이 아니라 주소가 전달되며, 이를 이용하여 함수 내에서 매개 변수의 값을 변경할 수 있다.

 

 

JS의 Call by reference?

간단히 말하면 JS에는 Call by reference가 존재하지 않는다.

 

이전까지는 초반에 언급했듯이

참조형이면 넘길 때도 call by reference로 넘겨지고,

원시형이면 callbyvalue로 넘겨지는 줄 알고 있었다.

 

그래서 js는 c와는 다르게

매개변수의 타입이 참조형이면, call by reference 방식으로 넘겨주게 되어

callee 내에서 원본을 컨트롤할 수 있는 것이라고 생각했다.

 

아래의 예시로 착각했던 이유를 덧붙이겠다.

const test = (obj) => {
  obj.a = 23;
};

let b = { a: 1 };
console.log(b);
test(b);
console.log(b);

위 코드는 함수 내에서 전달받은 객체의 값을 23으로 바꾸는 역할을 한다.

 

실제로 실행해보면,

함수 호출 전의 출력과 

호출 후의 출력이 변하여,

마치 call by reference에 의해 원본이 callee 내에서 수정된 것처럼 보인다.

 

사실 틀린 이해였다.

 

다른 예시를 이어서 보자.

const test = (obj) => {
  obj = { a: 23 };
};

let b = { a: 1 };
console.log(b);
test(b);
console.log(b);

얼핏 보면 이전 예시와 같은 동작을 하는 것처럼 보인다.

 

그러나 실행 결과를 확인해보면,

함수 실행 후에도 객체 b의 내용이 변하지 않은 것을 확인할 수 있었다.

 

그렇다면 지금 이게 무슨 상황인가?

 

??

우선 call by value였다면 첫번째 예시가 설명되지 않는다.

원본에 영향을 미쳤기 때문이다.

 

그런데 call by reference로도 설명이 되지 않는다.

두번째 예시에선 원본에 영향을 미치지 않았기 때문이다.

 

찾아보니 아래와 같은 개념이 있었다.

Call by Sharing

https://www.educative.io/answers/what-is-call-by-sharing

함수에 전달된 인수가 caller에서 볼 수 있는 방식으로 변형될 수 있지만,
인수로 전달된 특정 변수에 대한 엑세스는 제공되지 않는다.

이게 무슨 말인가?할 수 있다.

 

즉 참조값을 직접적으로 전달하는 것이 아닌,

그 주소에 대한 복사본을 만들어서 전달하는 방식인 것이다.

 

 

위에서 봤던 예시와 함께 다시 정리해보자

(예시에서 등장하는 메모리 주소는 임의의 값이다)

첫번째 예시

const test = (obj) => {
  obj.a = 23;
};

let b = { a: 1 };
console.log(b);
test(b);
console.log(b);

아래는 위 과정을 도식화한 것이다.

첫번째 예시는

넘겨받은 주소의 복사본을 통해

점 연산자 ( ' . ' )를 사용하여 원본 객체 프로퍼티에 접근하여

새로운 값을 할당해준 것을 알 수 있을 것이다.

 

 

 

두번째 예시

const test = (obj) => {
  obj = { a: 23 };
};

let b = { a: 1 };
console.log(b);
test(b);
console.log(b);

아래는 두번째 예시의 도식화 버전이다.

두번째 예시는

넘겨받은 주소의 복사본이 담겨있던 obj에 { a: 23 }이라는 새로운 값을 할당해준 것이기 때문에

다시 caller로 돌아왔을 때 0x01에는 여전히 { a: 1 }이 남아있는 것을 알 수 있을 것이다.

 

이렇게 Javascript는 call by sharing이라는 방식을 사용하고 있었으며,

이번 기회로 확실하게 이해하게 된 것 같아 보다 정확한 프로그래밍을 할 수 있을 것 같다.

 


참고자료

https://think0wise.tistory.com/65

call-by-value

call-by-reference

what-is-call-by-sharing