값(value) vs 참조(reference)

fromzoo·2020년 12월 10일
0

값에 의한 전달이 일어나는 5가지 데이터타입을 갖고 있음
(boolean, null, undefined, string, number)
=> 원시타입이라고 부름

참조에 의한 전달이 일어나는 3가지 데이터타입
(Array, Function, Object)
=> 이것은 크게 보자면 객체라고 볼 수 있다.

원시타입

원시타입 변수에 할당 -> 변수 = 원시타입을 가진 변수

var x = 10;
var y = 'abc'
var z = null

메모리상의 변수들 모습

VariablesValues
x10
y'abc'
znull

이 변수들을 다른 변수에 = 이라는 키워드를 이용하여 할당할때,
우리는 새로운 변수에 값을 복사하게 된다.

이변수들은 값에 의해 복사된다.

var x = 10;
var y = 'abc'

var a = x;
var b = y;
console.log(x,y,a,b) // 10, 'abc', 10, 'abc'

a와 x는 둘 다 값이 10
b와 y는 현재 둘 다 'abc'

값은 같지만 이들은 분리되어 있다.
값들이 복사되었기 때문

같은값은 가진 변수 하나를 바꾸더라도 다른 변수에는 아무런 영향이 없다.

각 변수들이 아무런 관계가 없다고 생각해도 무방

객체(Objects)

원시타입이 아닌 값이 할당된 변수들은
그 값으로 향하는 참조를 갖게 된다.

참조는 메모리에서의 객체 위치를 가리키고 있다. 변수는 실제로 값을 가지고 있지 않는다.

객체는 우리의 컴퓨터 메모리 어딘가에 생성됨 우리가 arr = [ ] 를 작성했을때, 우리는 메모리 내부에 배열을 만든것이다. 변수 arr이 갖는 것은 그 배열이 위치한 주소

아래와 같은 그림을 볼 때는 address가 원시타입과 같이 값에 의한 전달을 하는 새로운 종류의 데이터타입이라고 가정해보자. address는 참조에 의한 전달을 하는 위치를 가리키게 될 것 이다. 문자열이 ' ' 또는 " " 사이에 표기되듯 address< > 사이에 표기될 것이다.

참조타입의 변수를 할당하고 사용할때 우리가 쓰고 보는 것은 다음과 같은 코드이다.

1) var arr = [];
2) arr.push(1);

메모리 안에서 1과 2의 표기는 다음과 같다.

VariablesValuesAddressedObjects
arr<#001>#001[ ]
VariablesValuesAddressedObjects
arr<#001>#001[1]

arr이 가진 변수의 값은 정적임은 인지해야한다.

변수의 값이 바뀌는 것이 아닌 메모리 속의 배열 값만 바뀌는 것이다.
우리가 무언가 하려고 arr을 사용할때, 값의 push같은 일을할때, 자바스크립트 엔진은 메모리속의 arr의 위치로 간다. 그리고 거기에 저장된 정보를 이용하여 작업을 수행한다.

참조로 할당하기

객체와 같은 참조타입의 값이 =과 같은 키워드를 이용하여 다른 변수로 복사될때, 그 값의 주소는 실제로 복사된다. 마치 원시타입의 할당처럼 객체는 값 대신 참조로 복사된다

var reference = [1];
var refCopy = reference
VariablesValuesAddressedObjects
reference<#001>[1]
refCopy<#001>

각 변수는 이제 같은 배열로 향하는 레퍼런스를 갖는다. = 우리가 referencerefCopy를 건드려보면 다음과 같은 결과를 얻게 됨을 의미한다.

referece.push(2);
console.log(reference,refCopy) // [1,2], [1,2]

우리는 2를 메모리 속에 있는 배열에 push했다. 우리가 referencerefCopy를 사용할때 우리는 같은 배열을 가리키게된다.

참조 재할당하기

참조 값을 재할당하는 것은 오래된 참조를 대체한다.

var obj = { first: 'reference' }

메모리 상태

VariablesValuesAddressedObjects
obj<#234>#234{first: 'reference'}

우리가 두번째 줄을 입력한다면,

var obj = { first: 'reference' };
obj = { second : 'ref2' };

obj안에 저장됐던 주소값은 변경된다.
첫번째 객체는 아직 메모리 상에 표기가 되긴한다. 다음과 같이

VariablesValuesAddressedObjects
obj<#678>#234{first: 'reference'}
#678{second : 'ref2'}

남아있는 객체를 가리키는 참조가 남아있지 않을때, 위의 표에 보이는 주소 값 #234이 보이는 것처럼, 자바스크립트 엔진은 가비지 컬렉션을 동작시킬 수 있다.

이것은 프로그래머가 모든 참조를 날리고 객체를 더이상 사용할 수 없게 된 뒤 자바스크립트 엔진은 그 주소로 가 사용되지 않는 객체를 메모리로부터 안전하게 지워버리는 것을 의미한다...

이경우에는 객체 {first:'reference}가 더이상 접근 불가능하고 가비지 컬렉션이 될 수 있다.?????????????

가비지컬렉션

== 와 ===

== 와 ===은 참조타입의 변수를 비교할때 사용됨

이 연산자들은 참조를 체크함
만일 변수가 item에 대한 참조를 갖고 있다면, 비교 결과는 true가 나올 것이다.

var arrRef = ['Hi!'];
var arrRef2 = arrRef;

console.log(arrRef === arrRef2) // true

만일 2개의 구분 가능한 객체들을 갖고 있고 그들의 프로퍼티가 동일한지 보고싶다면, 가장 쉬운 방법은 그 둘을 문자열로 바꾸고 비교하는 것이다.

동등연산자가 원시 타입을 비교할때는 그저 값이 같은지만 확인한다.

var arr1str = JSON.stringify(arr1);
var arr2str = JSON.stringify(arr2);

console.log(arr1str === arr2str) // true

또 다른 선택지로는 객체를 이용해 재귀적으로 반복을 도는 것이다. 각각의 프로퍼티가 동일한지 확인할 수 있다.

함수를 통한 파라미터의 전달

원시 값들을 함수로 전달할때 함수는 값들을 복사하여 파라미터로 전달한다.

이것은 = 연산자를 이용하는 것과 같다.

var hundred = 100;
var two = 2;

function multiply(x,y) {
	// PAUSE 
	return x * y
}
var twoHundred = multiply(hundred,two);

값을 multiply함수로 넘겼을때 변수 x는 그 값을 (100)을 갖게된다.

값은 우리가 =연산자를 써서 할당한 것처럼 복사된다.

또 인자가 넘겨진 hundred라는 변수에는 아무런 영향도 미치지 않는다. 위의 소스코드중 PAUSE 상태에서 메모리가 어떻게 구성되는지에 대한 스냅샷은 아래와 같다.

VariablesValuesAddressedObjects
hundred100#333function(x,y)
two2
multiply<#333>
x100
y2

순수함수

함수 중에 함수 바깥 스코프에 아무런 영향도 미치지 않는 함수를 순수함수라고 한다.

함수가 오직 원시값들만을 파라미터로 이용하고 주변 스코프에서 어떠한 함수도 이용하지 않는다면, 그 함수는 자연스레 순수함수가 된다.
당연히 바깥 스코프에는 아무런 영향을 끼칠 수 없다.

안에서 만들어진 모든 변수들은 함수에서 반환이 실행되는 즉시 가비지 컬렌션 처리가 된다.

객체를 받는 함수는 주변 스코프들의 상태를 변화시킬 수 있다. 만일 함수가 배열 참조값을 가진 변수를 받고 그 변수가 가리키는 배열에 push를 수행하면 그 주변 스코프에 존재하는 변수들과 그 참조와 그배열이 변하는 것을 볼 수 있다.

함수 리턴 후에 변화된 것들은 바깥 스코프에 여전히 남아있다. 이런 현상은 우리가 원하지 않는 방향으로 부작용을 줄 수 이다. 디버깅 하려고 해도 찾아내기도 힘들다.

Array.mapArray.filter를 포함한 많은 네이티브 배열 함수들은 그래서 순수 함수로 작성되어있다.(???) 배열 참조를 받아서 내부적으로 배열을 복사하고 원본 대신 복사된 배열로 작업한다. 그래서 원본도 건드리지않고 바깥 스코프에 영향도 미치지 않고 새로은 배열의 참조를 반환하게 된다.

여기서 순수함수와 순수함수가 아닌 것을 비교해보자

// 비순수함수
function changeAgeImpure(person) {
	person.age = 25;
	return person;
}

var alex = {
	name : 'Alex',
	age: 30
}

var changedAlex = changedAgeImprue(alex);

console.log(alex); // { name : 'Alex', age: 25 }
console.log(changedAlex) // { name : 'Alex', age: 25 }

비순수함수는 객체를 받아서 age 프로퍼티를 25로 바꾼다. 객체로 받아온값에 그대로 명령을 실행하기 때문에 이 함수는 alex 객체를 직접적으로 변화시킨다. 이 함수가 person 객체를 반환할때 이 함수는 받았던 객체 그대로를 반환한다.
alex와 alexChanged는 같은 참조를 가진다. person변수를 반환하고 그 참조를 다시 새로운 변수에 저장하는 것은 사실 쓸데없는 행동이다.

순수함수를 보도록하자.

function changeAgePure(person) {
	var newPerson = JSON.parse(JSON.stringify(person));
	newPersonObj.age = 25;
	return newPersonObj;
};

var alex = {
	name: 'Alex',
	age: 30
};

var alexChanged = changeAgePure(alex);

console.log(alex); // -> {name: 'Alex', age: 30}
console.log(alexChanged); // -> {name: 'Alex', age: 25}

이 함수에서 우리가 넘겨받은 객체를 문자열로 변화시키기 위하여 우리는 JSON.stringify함수를 사용한다. 그리고 JSON.parse함수를 이용하여 다시 객체로 만든다.

이러한 과정을 거치면서 새로운 객체를 만들고 그 결과값을 새로운 변수에 저장한다.

이와 같은 일을 하기 위한 다른 방법도 있다.
원본객체의 프로퍼티를 반복해서 새로운 객체에 할당하는 방법이다. 하지만 우리가 위에서 한 방법이 가장 간단하다. 새객체는 원본과 같은 프로퍼티들을 가진다. 하지만 메모리 상에서는 두 객체는 다른 주소값을 가지고 구분된다.

우리가 이 새로운 객체에서 age 프로퍼티를 변경할때 원본은 전혀 영향을 받지 않는다.
이함수는 순수하고, 바깥 스코프에 영향을 미치지 않는다. 인자로 받은객체도...

새롭게 만들어진 객체는 반환이 되어야한다. 그리고 새로운 변수에 저장되어야한다 그렇지 않으면 결과값은 가비지 컬렉션이 될 것이고 결과 객체는 어디에도 남지 않게 된다.

자가테스트

값(value) vs 참조(reference)는 코딩 인터뷰에서 많이 출제되곤 한다. 무엇이 로그에 남겨질지 추측해보자

function changeAgeAndReference(person) {
	person.age = 25;
	person = {
		name: 'John',
		age: 50
    };
	return person;
}

var personObj1 = {
	name: 'Alex',
	age: 30
};

var personObj2 = changeAgeAndReference(personObj1);

console.log(personObj1); // -> ?
console.log(personObj2); // -> ?

함수는 처음에 넘겨진 원본 객체의 프로퍼티 age를 변경한다.
그 후에 변수 값에 새로운 객체를 다시 할당한다. 그리고 그 객체를 반환한다. 두 로그의 결과는 다음과 같다.

console.log(personObj1); // -> {name: 'Alex', age: 25}
console.log(personObj2); // -> {name: 'John', age: 50}

함수의 파라미터로 할당되는 것들은 =연산자로 할당하는 것과 같다는것을 기억하자.

함수 속 변수 personpersonObj1로 향하는 참조를 갖고 있어서 처음에 전달 받은 객체에 직접 변화를 가한다.

우리가 person새로운 객체로 재할당 한 뒤에는 원본 객체에 더이상 영향을 미치지 않는다.

이 재할당은 바깥 스코프에 있는 personObj1가 가리키는 객체를 변경하지 않는다. person변수는 새로운 참조를 갖게된다. 왜냐하면 이 변수는 단순히 재할당 됐을 뿐이고 재할당은 personObj1에 아무런 영향도 미치지 않는다.

우리가 이 함수를 사용할때 유일한 차이점은 일단 함수가 끝나고 나면 함수 내부에 있던 person이 더이상 스코프 안에 있지 않다는 점이다.

🔍 출처 블로그
자바스크립트 개발자라면 알아야 할 33가지 개념 #3 값(value) vs 참조(reference) (번역)

profile
프론트엔드 주니어 개발자 🚀

0개의 댓글