[javascript] Closures

dev stefanCho·2021년 12월 7일
0

javascript

목록 보기
21/26

Closure란

It makes it possible for a function to have "private" variables. A closure is a function having access to the parent scope, even after the parent function has closed. - W3School

  • closure는 function을 return한다.
    (return하는 inner function은 outer function 내부에서 declare 되어야한다 반드시!!)
  • local binding된 특정 scope를 참조할 수 있는 특징(feature)을 closure라고 부른다.

Closure의 Scope Chain

모든 closure는 아래 3개의 scope를 갖고 있다.

  1. Local Scope (자신의 Scope)
  2. Outer Functions Scope (감싸고 있는 외부의 모든 function들)
  3. Global Scope

여기서 중요한 점은 모든 outer scope(global scope포함)에 접근할 수 있다는 것이다.
아래 예시는 Mdn에서 가져왔다.

// global scope
var e = 10;
function sum(a){
  return function(b){
    return function(c){
      // outer functions scope
      return function(d){
        // local scope
        return a + b + c + d + e;
      }
    }
  }
}

console.log(sum(1)(2)(3)(4)); // log 20

// You can also write without anonymous functions:

// global scope
var e = 10;
function sum(a){
  return function sum2(b){
    return function sum3(c){
      // outer functions scope
      return function sum4(d){
        // local scope
        return a + b + c + d + e;
      }
    }
  }
}

var sum2 = sum(1);
var sum3 = sum2(2);
var sum4 = sum3(3);
var result = sum4(4);
console.log(result) //log 20

아래처럼 outer scope(callMe) 함수의 변수(count)값을 업데이트 할 수가 있다.

const callMe = () => {
  let count = 0;
  return () => {
    if (count > 0) {
      return 'not first';
    } else {
      count++;
      return 'first time';
    }
  }
}

const init = callMe();
init() // first time
init() // not first
init() // not first

garbage collection이 되지 않는다.

call stack에서 함수가 제거되고 나면 heap memory에 남은 변수들이 저장된다. garbage collecting을 할때, 이 변수들은 closure로 보기때문에, 제거 되지 않는다. 따라서 closure로 만들어진 변수들은 다시 접근할 수 있는 것이다.

const myInfo = (name) => (age) => (location) => console.log(`${name} / ${age} / ${location}`);
const callMyName = myInfo('cho');

const callMyAge = callMyName(20); 

Callback에서의 Closure 변수 위치

closure가 된 변수는 callback 이후에도 변수 위치와 상관없이 접근할 수 있다.

function callback1() {
  const hi = 'hi there';
  setTimeout(function() {
    console.log(hi)
  }, 5000)
}

function callback2() {
  setTimeout(function() {
    console.log(hello)
  }, 5000)
  const hello = 'hello world';
}

callback1() // hi there
callback2() // hello world

callback1 을 통해서 Web API로 callback되지만, hi라는 변수에 접근할 수 있음을 알 수 있다.
callback2 를 통해서 closure라면 변수 선언 위치는 상관없음을 알 수 있다.

Memory efficient가 좋다.

function을 계속해서 생성하는 경우에 closure scope를 사용하여, 재생산에 의한 비효율을 막을 수 있다.

// Bad..
function createAllUser(index) {
  const bigArray = Array.from({ length : 1000}, (v, i) => ({ id: i}))
  console.log('create all user!');
    return bigArray[index];
}
console.log(createAllUser(1)); // create all user, {id: 1}
console.log(createAllUser(20)); // create all user, {id: 20}
console.log(createAllUser(100)); // create all user, {id: 100}



// Good!!
function createAllUser() {
  const bigArray = Array.from({ length : 1000}, (v, i) => ({ id: i}))
  console.log('create all user!');
  return function(index) {
    return bigArray[index];
  }
}

const users = createAllUser(); // create all user!
console.log(users(1)); // { id: 1 }
console.log(users(20)); // { id: 20 }
console.log(users(100)); // { id: 100 }

closure를 사용하지 않는경우, 함수 호출마다 bigArray를 생성하게 된다. 반면 closure를 사용한 경우는 closure 생성시에만 bigArray를 생성하고, 그 후에는 closure내에 있는 bigArray를 재사용하게 된다.

Encapsulation 된다.

내 API를 필요에 따라 숨길 수 있다.

const makeNuclearButton = () => {
  let timeWithoutDestruction = 0;
  const passTime = () => timeWithoutDestruction++;
  const totalPeaceTime = () => timeWithoutDestruction;
  const launch = () => {
    timeWithoutDestruction = -1;
    return '💥';
  }
  setInterval(passTime, 1000);
  return {
    totalPeaceTime,
  }
}

const ohno = makeNuclearButton();
ohno.passTime() // ohno에는 return object에는 launch가 없다.

Example

원하는 결과
index 0 value is a
index 1 value is b
index 2 value is c
index 3 value is d

const arr = ['a', 'b', 'c', 'd'];

// 모든 결과가 index 4 value is undefined로 나온다. (같은 global variable i를 참조하기 때문에)
for (var i = 0; i < arr.length; i++) {
  setTimeout(() => {
    console.log(`index ${i} value is ${arr[i]}`)
  })
}

// let을 사용하면 block scope가 생기므로, 원하는 결과를 얻는다.
for (let i = 0; i < arr.length; i++) {
  setTimeout(() => {
    console.log(`index ${i} value is ${arr[i]}`)
  })
}

// IIFE을 사용하면, i가 function의 local 변수로 저장되기 때문에, 원하는 결과를 얻는다. (IIFE가 closure 환경을 만드는 것이다.)
for (var i = 0; i < arr.length; i++) {
  (function (closureI) {
    setTimeout(() => {
      console.log(`index ${closureI} value is ${arr[closureI]}`)
    })
  })(i)
}
profile
Front-end Developer

0개의 댓글