[JS] Closure with Encapsulation

colki·2021년 5월 8일
0

Udemy_JavaScript: The Advanced JavaScript Concepts (2021) 강의를 바탕으로 메모한 내용입니다.



😁 Clousure의 장점에 대해서 알아보자

- Memory Efficient
- Encapsulation

1. Memory Efficient 메모리 효율성 ↑

Every time we run this function we create this memory

아래 예제처럼 함수를 연속적으로 호출하는 상황을 가정해보자.

function heavyDuty(index) {
  const bigArray = new Array(1000).fill('a');
  console.log('created');
  return bigArray[index];
}

heavyDuty(55); 
// 함수 호출
// created  함수 생성
  
heavyDuty(55);
// 이전 함수 제거
// 함수 호출
// created  함수 생성
  
heavyDuty(55);
// 이전 함수 제거
// 함수 호출
// created  함수 생성

재호출시 새로운 생성을 위해 이전 것을 없애고 또 생성하고 또 없애고 생성하고..를 반복하는 상황을 개선하기 위해서 closure를 사용할 수 있다.

Closure only create it once and just have it in memory there so we can just constantly access.
한 번 생성하고 나면, 추가로 만들어낼 필요없이 생성한 값에 접근이 가능하다.

다음 항목에서 위 예제를 리팩토링하면서 자세히 살펴보자.

2. Encapsulation 정보 은닉

It's hiding of information that is unnecessary to be seen by the outside world or manipulated.

anybody access to your API to your special functions or variables
( some data should just not be directly exposed )


function heavyDuty(index) {
  const bigArray = new Array(1000).fill('a');
  console.log('created'); // created 1번만 출력.
  
  return function (index) {
    return bigArray[index];
  }
}

const getHeavyDuty = heavyDuty();

getHeavyDuty(55); // a
getHeavyDuty(70); // a
getHeavyDuty(100); // a

const getHeavyDuty = heavyDuty();
변수에 본체함수의 리턴값인 함수function (index) {를 담아서 getHeavyDuty(55);`로 호출했더니 정상적으로 bigArray의 요소 'a'가 출력되었다.

클로저를 이미 공부했던 나에게는 익숙한 상황이긴 한데..아직 클로저가 너무도 헷갈리는 나는
위 함수 내부의 function (index) {익명함수가 ()형태가 아니라 index라는 인자를 받는 부분이 많이 헷갈렸다.
상위스코프의 index를 클로저로 인해 기억하니까 안써도 되는 게 아니었나? 😮

알고 보니 그건 캡슐화를 하지 않았을 때, 함수를 직접 실행시킬 때 가능한 거였다!

익명함수의 매개변수를 ()로 두고,
1. 변수에 담기전에 본체 함수를 실행해서 index의 값을 받아오는 것과,
2. getHeavyDuty변수에 본체함수의 리턴값을 담고나서 getHeavyDuty를 실행할 때로 테스트해보았다.

  
/** 1. 캡슐화x  직접호출했을 때는 읽을 수 있음 **/
function heavyDuty(index) {
  const bigArray = new Array(1000).fill('a');
  
  console.log(index); // 1번째 콘솔: 55

  return function () { // ()
    console.log(index); // 2번째 콘솔: 55
    return bigArray[index];
  }
}

//const getHeavyDuty = heavyDuty();

heavyDuty(55); 
// 1번째 콘솔: 55
// 리턴값: 익명함수
heavyDuty(55)();
// 2번째 콘솔: 55
// 리턴값: a

-----------------------------------
  
/** 2. 캡슐화했을 때 index값 못 읽음 **/
function heavyDuty(index) {
  const bigArray = new Array(1000).fill('a');

  console.log(index); // 1번째 콘솔: undefined

  return function () { // ()
    console.log(index); // 2번째 콘솔: undefined
    return bigArray[index];
  }
}

const getHeavyDuty = heavyDuty();
// 1번째 콘솔: undefined
  
getHeavyDuty(55); 
// 2번째 콘솔: undefined
// 리턴값: a

캡슐화를 하지 않고 직접 호출할 때는 상위스코프 heavyDuty함수에서 index값이 콘솔에 출력됐지만, 캡슐화를 한 후에는 상위스코프에서 index값을 출력하지 못했다.

그렇다면 그 인자의 정보는 어디로 갔지?
바로 캡슐화한 그 변수에 함수의 렉시컬환경이 저장된다.

위에서는 익명함수를 getHeavyDuty 변수에 넣어 캡슐화했다.
그렇다면 getHeavyDuty에는 렉시컬스코프 heavyDuty함수의 변수환경 index 인자 정보와 bigArrray 변수 정보 가 저장된것이다.

즉 이때 캡슐화한 변수에는 단순히 값만 저장되는 것이 아니라 함수가 담겨있던 상위스코프의 Lexical Environment가 저장된다.

이는 지난번에 다룬 함수가 객체이기 때문에 단순히 동작이상으로 데이터를 저장하는 기능이 있다 했던 것과 일치하는 부분이다.

이렇듯 직접 호출하는게 아니라 return값 으로 뱉어내서 캡슐화했던 getHeavyDuty에는, 렉시컬 스코프 정보가 은닉되어 있기 때문에 따로 콘솔에서 index값을 조회할 수가 없었던 것이다. cooool! 😁


그래서 강의에서 리팩토링 했던 코드의 익명함수 매개변수에 index를 넣어줬던 것!

function heavyDuty(index) {
  const bigArray = new Array(1000).fill('a');
  console.log('created'); 
  
  return function (index) { // 여기 ****
    return bigArray[index];
  }
}

const getHeavyDuty = heavyDuty();

  

exercise 01

함수를 계속 호출(= 초기화)함에도 불구하고, 'first called' 가 한 번만 호출되도록 리팩토링해보기. (단, view 값은 초기화할 때마다 리턴해주어야 한다.)


문제.

let view;
function initialize() {
  view = '💨';
  console.log('first called')
}
  
initialize(); // first called
console.log(view); // 💨
  
initialize(); // first called
console.log(view); // 💨
.
.
.Colki Answer
  
function initialize() {
  let view;
  let wasCalled = false;

  return function() {
    if (!wasCalled) {
      wasCalled = true;
      view = '💨';
      console.log('first called')
    }

    return view;
  };
}

const result = initialize();
result();
// first called
// 💨
  
result();  
// 💨
result();  
// 💨
result();  
// 💨

exercise 02

면접 단골 문제
1초마다 콘솔에 console.log('I am at Index' + i)을 출력해야 한다.

ex)
I am at Index 1
I am at Index 2
I am at Index 3
I am at Index 4

( 2가지 솔루션이 있다! )


문제.
  
const array = [1, 2, 3, 4];

for (var i = 0; i < array.length; i++) {
  setTimeout(function () {
    console.log('I am at Index' + i);
  }, 3000);
} 

// I am at Index 4
// I am at Index 4
// I am at Index 4
// I am at Index 4Colki Answer
                                 
/* (1) var => let */

const array = [1, 2, 3, 4];

for (let i = 0; i < array.length; i++) {
  setTimeout(function () {
    console.log('I am at Index' + (i + 1));
  }, 3000);
}
                                 
// I am at Index 1
// I am at Index 2                       
// I am at Index 3          
// I am at Index 4   

니고이는 (i + 1) 이 아니라 array[i]을 넣었다.. !

/* (2) make new function */
const array = [1, 2, 3, 4];

for (var i = 0; i < array.length; i++) {
  countIndex(i + 1);
}

function countIndex(index) {
  setTimeout(function () {
    console.log('I am at Index' + index );
  }, 3000);
}

// I am at Index 1
// I am at Index 2                       
// I am at Index 3          
// I am at Index 4  
  
  
  

🔹 니고이 answer

the loop comes through here and I can do that by passing this immediately invoked function.

/* (2) IIFE */                                
const array = [1, 2, 3, 4];

for (var i = 0; i < array.length; i++) {
  (function (index) {
    setTimeout(function () {
      console.log('I am at Index' + array[index]);
    }, 3000);
  })(i);
}                              
  1. ( function ( ) { } )(i) <= for loop의 i가 IIFE의 인자로 들어가 IIFE를 즉시 실행시킴.
    그리고 이 i가 IIFE 함수안의 매개변수로 들어감
  1. ( function (index) { <= 여기로 쏙 들어오게 됨.
  1. console.log('I am at Index' + array[index]);
    콘솔에 'I am at Index 1' 가 출력됨.

위 과정이 반복된다.

😐 for loop + 즉시실행함수의 조합은 되는 걸로 알고 있었는데, 인자 들어가는 2개를 어떻게 써야 하는 지 제대로 알아두지 않았어서..
이 방법으로 푸는 게 잘 안됐다. 이번에는 제대로 기억해야지!

profile
매일 성장하는 프론트엔드 개발자

0개의 댓글