[JS] Closure with Return

colki·2021년 4월 16일
0
post-custom-banner

lodash 메서드 구현 예제를 풀어보면서
아직도 closure에 대한 개념이 부족하다는 것을 많이 깨달았다 😂
특히 return 문을 언제 어떻게 써야 하는 가에 대해 정리가 너무 안됐었는데 return으로 검색해도 원하는 답을 찾을 수 없었다.

그리고 왜 없는 지도 이제 알았다. 너-무 기본이라 그랬던 것이다.
정말 바보다..나는 바.. 보.. 

클로저는 실행컨텍스트가 열렸을 때, 자신을 감싸고 있는 lexical scope 내부에서 선언된 변수 또한 참조할 수 있는 현상이다.
비록 자신을 감싸고 있던 함수가 이미 실행되어 종료된 후라 할지라도 말이다.

클로저를 구현하기 위해, 함수를 실행하는 방법은 크게 2가지로 나뉘는데,
바로 함수를 실행하는 방법과 변수에 담아서 실행하는 방법이다.

💡 return사용설명서


😐 예제_basic

var outer = function () {
  var a = 1;
  var inner = function () {
    console.log(++a); // 2
  };
  inner();
};

outer();

outer()

위와 같이 바로 outer 함수를 실행할 경우,
inner() 실행문으로 인해 inner함수의 컨텍스트가 열리고,
변수 a를 참조할 수 있다.

만약 이때 아래와 같이 구조를 바꾼다면?
(return만 쓰면 되는 줄 알았을 때 이런 바보같은 실수를 했었다..)

var outer = function () {
  var a = 1;
  return function () {
    console.log(++a); // 아무것도 없었다고 한다.
  };
};

outer();

이렇게 작성한다면 익명함수 내에서 콘솔에는 a가 찍히지 않는다.
클로저는 함수 선언 기준으로 생성되고 상위스코프 값을 참조하는 건 맞지만,
이 경우에는 리턴하고 끝내라 했으니까 콘솔은 a의 변수를 뱉어낼 틈도 없다.
console.log(outer()); // ƒ () { console.log(++a); }
익명함수를 리턴할 뿐이다.

콘솔한테 왜 a 안 찍냐고 혼내면, 콘솔은 니가 그럴 시간을 줬냐?? 하면서 억울해할 수 밖에..



그렇다면 그놈의 return function은 언제 쓰는 것인가 ??!!!

함수를 변수에 담고 실행하려고 하니까!
즉 함수를 리턴해야 하니 return fucntion이어야지 ㅋㅋ 멍충아.


😬 예제 return_1

var outer2 = function () {
  var a = 1;
  var inner1 = function () {
    return ++a;
  };
  return inner1;
};

var outerCall = outer2();
console.log(outerCall()); // 2

var outerCall = outer2(); outerCall()

이번에는 outer2함수의 실행결과함수를 변수 outerCall에 할당하고
그 outerCall을 실행한 코드이다. 여기서 outerCall은 함수

outer2를 실행하면, 컨텍스트가 열리고 inner1함수를 리턴하면서 종료된다.
이때 생성된 inner1은 상위스코프에 있는 a를 기억한 채로 outerCall에 담긴다.

그리고 outerCall을 다시 실행해줄 때 비로소 기억하고 있던
상위 스코프의 a를 참조하여 ++a 를 뱉어내는 것이다.

그래서 이때는
return 함수표현식 + 함수표현식 내부 return ,
return 함수선언문 + 함수선언문 내부 return 의 콜라보가 되겠다.


😬 예제 return_2

var outer2 = function () {
  var a = 1;
  return function () {
    return ++a;
  };

};

var outerCall = outer2();
console.log(outerCall());

var outerCall = outer2(); outerCall()

이번에도 역시 outer2함수의 실행결과함수 를 변수 outerCall에 할당하고 그 outerCall을 실행한 코드이다. 그런데 앞의 예제보다 산뜻한 코드임을 알 수 있다.

outer2 함수 내부에 익명함수function () {} 구조로 들어있다.
내부함수에서는 return ++a로 외부 변수값을 뱉어내고 있는 건 똑같은데,

이 내부함수를 실행하거나 return 하는 부분이 따로 없다.
대신 익명함수 자체를 리턴하게 만들어져 있다.

이렇게 하는 경우에도 역시 클로저가 형성된다.
단 익명함수의 인자에 a를 넣어줄 필요는 없다.

바로 return 익명함수 + 익명함수 내부 return 의 콜라보이다.


💡 적용예제 _.flatten


_.flatten을 구현할 때 바보같은 실수의 정체성을 알게 되었다.
그 전까지는 3가지 방법을 돌려가며...썼더랬지.

위에서 설명한 예제1 토대로 다르게 구현해보면 다음과 같다.

😐 예제_basic

_.flatten = function (nestedArray) {
  const flattedArray = [];

  function flatArray (nestedArray, flattedItems) {
    _.each(nestedArray, function (item) {
      if (Array.isArray(item)) {
        flatArray(item, flattedItems);
      } else {
        flattedItems.push(item);
      }
    });
  }

  flatArray(nestedArray, flattedArray);

  return flattedArray;
}
  
  _.flatten(nestedArray);

함수를 리턴하는 것이 아니기 때문에 안에서 flateArray를 실행해준 것.

예제 1번 같은 상황으로 풀어보고 싶어서 위와 같이 만들었지만, 사실
함수를 중첩하지 않고 간결하게 할 수도 있다.

_.flatten = function (nestedArray, flattedArray) {
  flattedArray = flattedArray || [];

  _.each(nestedArray, function (item) {
    if (Array.isArray(item)) {
      return _.flatten(item, flattedArray);
    } else {
      flattedArray.push(item);
    }
  });

  return flattedArray;
}
  
_.flatten(nestedArray);

💡 "PREVENT" 배열 초기화


내가 flatten을 하면서 제일 애먹었던 부분이 재귀를 돌릴 때마다
flattenArray 가 []로 리셋되는 것이었는데, 이것은 함수를 호출할 때 인자로 추가해주면 해결된다.

1번 방법에서는 바깥 함수의 인자를 추가하는 등의 수정을 하고 싶지 않아서 내부에 flatArray라는 함수를 만들어서 전달하는 방식으로 작성했다.

2번 방법처럼 한다면 재귀를 돌릴때 _.flatten 의 두번째 파라미터로 전달하면 되고,
상단에 flattedArray = flattedArray || [];로 선언해주면 된다.

재귀돌릴 당시의 배열로 받을 경우= flattedArray 초기값으로 빈배열로 선언할 경우|| [];
구분지어 주면 원하는 대로 배열을 셋팅할 수 있다.

_.flatten = function (nestedArray, flattedArray = []) {

  _.each(nestedArray, function (item) {
    if (Array.isArray(item)) {
      return _.flatten(item, flattedArray);
    } else {
      flattedArray.push(item);
    }
  });

  return flattedArray;
}
  
_.flatten(nestedArray);

또는 처음부터 파라미터에 flattedArray = []default 해줄 수도 있다.
특정 값이 아니라면 기본값 []가 내부에 선언될 것이다.
(호이스팅을 떠올리면 인자도 내부에서 값으로 사용된다.)

즉 이부분을 정리하면

① 함수 내부 ⏩ if문

_.flatten = function (nestedArray, flattedArray) {

 if (!flattedAray) {
 	flattedArray = []
 }

② 함수내부 ⏩ or, || 연산자 이용

_.flatten = function (nestedArray, flattedArray) {

 flattedArray = flattedArray || [];

1, 2 경우에는 let const 등을 사용하지 않아도 된다.

③ 함수 파라미터 ⏩ default -> array = []

_.flatten = function (nestedArray, flattedArray = [] ) {
profile
매일 성장하는 프론트엔드 개발자
post-custom-banner

0개의 댓글