reference type, Scope, Closer

Reference type

변수에는 원시 자료형(primitive type)과 참조 자료형(reference type)이 있다.

원시 자료형은 말 그대로 원시적, 단순하게 한 메모리에 변수라는 이름을 가진 값을 할당하는 것이므로 쉽게 이해할 수 있다.
참조 자료형은 왜 그런 이름이 붙었을까? 바로 reference, 주소가 변수에 값으로 들어가기 때문이다. 각 reference는 동적(dynamic)으로 크기가 변하는 특별한 보관함(heap)으로 이어진다.

내가 기억해야할 점은 아래 예시와 같다.

// 원시 자료형의 경우
let a = 0;
let b = a;
b = 1;
console.log(a); // 0

// 참조 자료형의 경우
let c = [1, 2, 3];
let d = a;
d[0] = 100;
console.log(c[0]); // [100, 2, 3]

reference data를 복사한다는 것은 주소를 복사하는 것이다.
원시 자료형은 단순히 값을 복사하는 것이므로 b가 다른 값으로 변해도 a 값은 변화가 없는 반면,
참조 자료형은 주소값을 복사하므로 d를 이용해 배열의 요소를 바꾸면 같은 주소를 가진 c의 요소도 바뀐다.

주소 참조라는 점을 이해하니 앞선 Array를 공부할 때 [] === []가 false인 이유를 알 수 있었다. 동일한 이유로 비어있지 않은 object, array 모두 같게 생기더라도 서로 다른 주소를 가지므로 엄격한 비교에서는 false를 반환한다. 해보니까 그냥 비교 (==)에도 false를 반환하기는 한다..

'참조하다' 라는 말에 익숙해지자. 바로 변수의 값을 읽는 것이 아니라, 주소 참조를 선행한 다음 그 주소에 도착해 데이터를 읽는 것!

// 객체의 주소를 복사하고, 주소 도착지의 객체에 영향을 미치지 않는 값을 재할당 했을 때
let x = {foo: 3};
let y = x;
y = 2;
console.log(x) // {foo: 3}
// 객체의 주소를 복사하고, 주소 도착지의 객체의 값을 바꾸도록 재할당 했을 때
let a = {foo: 3};
let b = a;
b.foo = 4;
console.log(x) // {foo: 4}



Scope

우물 같은 스코프.. 안쪽 스코프에서는 밝은 바깥을 내다볼 수 있지만 바깥 스코프에서는 안쪽이 어두워 보이지 않는다!
가장 바깥쪽의 scope는 Global scope(전역 스코프), 이외는 Local scope(지역 스코프)라고 하고, 지역스코프가 더 높은 우선순위를 가진다.

1. block scope

  • 반복문에 선언하는 i는 딱 그 block에서만 사용된다. block을 넘어서서 i를 묻는다면 Reference Error!

2. fucntion scope

  • function은
    화살표 함수는 block scope로 취급된다. function 키워드를 쓴 경우에만 function scope!

나의 level에서 선언하지 않은 변수가 바깥 스코프에서도 안보이면 ReferenceError가 발생한다.

❗️var, scope와 관련해 주의할 점

  • var는 block scope를 무시한다!(for문에서 var로 i를 선언하면 바깥에서도 i가 조회된다)
  • 재선언도 가능하다. 보통 재선언되면 그건 버근데.. 문제인걸 모르는 것
  • 전역변수를 var로 선언하면 브라우저에만 존재하는 window 객체를 덮어씌워서 내장 기능이 먹통이 될지도 모른다.

그러니까 그냥 쓰지말자!!!!!

let x = 10;
function outer () {
  let x = 20;
  function inner () {
    return x;
  }
  return inner();
}
let result = outer();
console.log(result); // 20

이렇게 scope가 위계적으로 겹치면 순차적인 스코프 체이닝이 일어난다.

let x = 10;
function outer () {
  let x = 20;
  function inner () {
    x = x + 10;
    return x;
  }
  inner();
}
outer();
let result = x;
console.log(result); // 10

resultouter()함수 실행과 무관하게 같은 레벨에서 10으로 할당되었으므로 10을 반환한다.

let x = 10;
function outer () {
  x = 20;
  function inner () {
    let x;
    x = x + 20;
    return x;
  }
  inner();
}
outer();
let result = x;

위와 마찬가지인 상황이지만, outer() 내에 x가 선언되지 않아 상위 스코프인 전역 스코프를 변경한다.
❗️참고로, inner() 함수에서 type이 정해지지 않은 채 선언된 x+ 20하면 undefined + 20으로 NaN을 반환한다.




Closer

  • 외부함수의 변수에 접근할 수 있는 내부함수라고 받아들이자.
  • 실제 정의는, 함수와 함수가 선언된 어휘적 환경의 조합이다.
const adder = function(x){
  return function(y){
    return x + y;
  }
}

이때 안쪽의 function(y)이 closer에 해당하고, 스코프를 이용해 변수의 접근 범위를 닫는 것에 핵심이 있다.

어떻게 closer를 사용할 것인가

1. 데이터 보존

const add5 = adder(5);

위 함수 adder에 이어서, add5adder(5)라고 선언하고 실행시키더라도 아직 y 변수가 남아있으므로 함수 자체로 남아있다. 따라서 외부 함수의 실행이 끝나도, 인자로 넘긴 5라는 값을 x에 계속 담은 채로 둘 수 있다.
즉, 특정 데이터를 보존(외부 함수에 해당)한 상태로 여러번 사용할 수 있다.


2. 클로저 모듈 패턴

const makeCounter = () => {
  let value = 0;
  
  return {
    increase: () => {
      value = value + 1
    },
    decrease: () => {
      value = value - 1
    },
    getValue: () => value
  }
}

const counter1 = makeCounter();

함수의 리턴값을 object로하는데, 그 속에 여러개의 내부 함수를 담는다.
value에 새로운 값을 할당하는 것은 불가능하지만 객체에 담긴 함수를 메소드로 활용하여 간접적으로 바꿀 수 있다.

생각해보면 외부 함수 단계에서는 value를 선언한 것 밖에 없다. 하지만 이 과정을 통해 value는 전역 변수가 아닌 스코프 내 접근이 제한된 변수가 되었다. 이를 캡슐화라고 하는데, 전역 변수로 선언하여 발생할 지도 모르는 side effect를 최소화할 수 있다.


3. 함수의 재활용, 모듈화

reference type은 변수에 주소를 저장한다고 공부했다. 두개의 다른 변수에 같은 함수를 할당하는 것은 같은 주소를 할당하는 것이므로, 값도 동일하게 바뀔 것이다.

하지만 위에서 만든 makeCounter()의 경우로 예를 들면,
const counter2 = makeCounter(); 이라고 동일한 함수를 할당하더라도
value서로 영향을 끼치지 않도록 자신만의 함수 스코프 내에 갇혀있기때문에 각자 독립적이면서도 같은 함수를 할당하며 각각의 값을 보존할 수 있다.

이렇게 재사용한 상태로 만들어졌다고 해서 모듈화라고 말한다.

좋은 웹페이지 즐겨찾기