'Call by value'와 'Call by reference' (feet. 자바스크립트)

younoah·2022년 1월 19일
11

[My 자바스크립트]

목록 보기
16/17

'Call by value'와 'Call by reference' 란?

call by valuecall by reference란 변수나 객체등이 함수의 인자(argument)로 들어와 매개변수(parameter)로 전달될 때 어떤 방식으로 전달될 지를 결정하는 방식이다.

함수 호출 방법은 크게 2가지
  • Call by value(값에 의한 호출, 값의 복사)
  • Call by reference(참조에 의한 호출, 주소의 복사)

참조형에 따른 방식

일반적으로 기본형(원시형)을 매개변수로 넘길 때는 Call by value 방식으로 넘기고

참조형을 매개변수로 넘길 때는 Call by reference 방식으로 넘긴다.

기본형(원시형) : 숫자, 문자열, 불리언, null, undefiend, 심볼

참조형 : 객체, 배열, 함수, 날짜, 정규표현식


parameter, argument

인자(argument)는 어떤 함수가 호출될 때 전달되는 을 의미하고

매개변수(parameter)는 전달된 값을 받아들이는 변수를 의미한다.

function sum(a, b) {	// a, b는 매개변수(parameter)
  return a + b;
}
sum(10, 20);	// 10, 20은 인자(argument)

Call by value(값에 의한 호출, 값의 복사)

Call by value(값의 복사)는 말 그대로 복사된 값을 인자로 넘겨서 매개변수로 전달한다.

기본형(Primitive type)의 경우 call by value 방식으로 전달된다.

  • 장점 : 복사하여 처리하기 때문에 안전하다. 원래의 값이 보존이 된다.
  • 단점 : 복사를 하기 때문에 메모리가 사용량이 늘어난다.

Call by reference(참조에 의한 호출, 주소의 복사)

call by reference(주소값의 복사)는 실제 데이터가 존재하는 주소를 가리키는 주소값을 인자로 넘겨서 매개변수로 전달한다.

객체(ex. obj, array, function ...)는 call by reference 방식으로 전달된다.

  • 장점 : 복사하지 않고 직접 참조를 하기에 빠르다.
  • 단점 : 직접 참조를 하기에 원래 값이 영향을 받는다.(리스크)

자바스크립트는?

  • 원시타입의 경우 call by value로 동작한다.

  • 참조타입의 경우 call by reference로 동작한다.

JavaScript는 어떤 타입의 자료형(기본형, 참조형)이던지 항상 call by value를 사용한다.

참조형 타입이여도 참조형의 주소값을 복사해서 매개변수로 전달한다.


예제

function foo(obj2) {
  obj2= 10;
  console.log(obj2);	// 10
}
let obj1 = { a: 5, b: 8 };
foo(obj1);

console.log(obj1);	// { a: 5, b: 8 }

C, C++ 언어에서는?

포인터와 call by reference 개념을 가진 C, C++ 언어의 경우에는 2번째 줄의 obj2 = 10;에 의해서 obj1의 값이 10으로 바뀐다. 왜냐하면 완전한 주소값 참조로 연결되어 있기 때문이다.


자바스크립트에서는?

하지만 JS의 경우에는 객체의 프로퍼티 값이 아닌 객체 자체의 변경을 허용하지 않는다. 왜냐하면 겉보기에는 주소값을 참조하는 것 같지만 실제로는 주소값의 복사본을 만들어서 넘기기 때문이다.


동작 설명

먼저 foo(obj1);에서 obj1이 함수 foo()의 인자로 넘어가서 obj2라는 매개변수로 전달될 때, obj1이 가리키고 있는 주소값과 동일한 주소값을 가진 obj2가 복사되서 전달된다.


그 다음 함수 내부의 obj2 = 10;에서 obj2의 주소값이 10이 저장된 위치의 주소값으로 변경된다.


연습

case1

const f1 = obj => {
  obj = null;
}

let b1 = {'a': 1};
f1(b1);
console.log(b1); // { a: 1 }

b1이라는 객체를 null로 만드는 것 같지만 그러게 되지 않는다.
obj에는 주솟값의 복사본이 들어가게 되고,
우리가 원하는 b1 주솟값에 값이 할당된느 것이 아니라 또 다른 주솟값에 null이 할당된다.


case2

const f2 = array => {
  array = null;
}

let b2 = [];
let a2 = b2;
f2(b2);
console.log(b2); // []

배열도 case1과 마찬가지이다.


case3

const swap = (a, b) => {
  let tmp = a;
  a = b;
  b = tmp
}

let x = 1;
let y = 2;

swap(x, y);
console.log(x, y); // 1, 2

기본형을 넘겨도 마차가지이다. 스왑이 일어나지 않는다.


case4

const f = (a) => {
  a = 2;
}

let b = 11;
f(b);
console.log(b); // 11

case3과 비슷하다. 기본형을 넘겨도 복사된 주소를 갖고 있는 변수에서 값을 변경했기 때문에 출력은 11이다.


case5

const a = { 'a': 1 };
const b = (c) => {
  c.a = 2;
} 
b(a);
console.log(a) // { a: 2 }

객체의 속성은 변하게 된다.

참조형의 복사본인 객체를 전달했고 이 객체는 다른 주솟값을 갖지만 둘 다 똑같은 { 'a' : 1 }이라는 값을 바라보고 있기 때문이다.

이 상태에서 c의 속성이 변화하면 똑같은 값을 바라보고 있는 a의 속성도 변하게 된다.


case6

const foo = (b) => {
  b = b.a = 1;
  b.b = 2;
};

const a = {};
foo(a);
console.log(a); // { a: 1 }

2번째 줄 b = b.a = 1; 을 보자.

할당 연산자를 여러 개 연결한 경우, 평가는 우측부터 진행된다. (참고 - 할당연산자 체이닝)

b.a = 1 로 인해 변수 a가 가리키는 값에 { a: 1 } 가 할당된다.

그리고 다시 b에는 1이 할당된다.

3번째 줄 b.b = 2; 는 기본형에 프로퍼티를 할당했기 때문에 무시가 된다.

따라서 결과적으로 a는 { a: 1 } 이 된다.



profile
console.log(noah(🍕 , 🍺)); // true

0개의 댓글