클로저

GoGoDev·2022년 10월 17일
1

프로그래머스

목록 보기
3/22

클로저를 알기 전에 실행 컨텍스트에 대해 알아야한다.

실행 컨텍스트 💬

실행 컨텍스트는 실행 가능한 코드가 실행되기 위해 필요한 환경이다.
자바스크립트엔진은 스크립트를 실행하다가 함수를 만나게되면 실행컨텍스트를 생성한다. 이 실행컨텍스트에 함수가 실행될 때의 환경을 저장하게 됩니다. 이렇게 만들어진 실행컨텍스트는 자신만의 스코프(유효범위)를 갖게된다.

실행 가능한 코드

  • 전역 코드: 전역 영역에 존재하는 코드
  • Eval 코드: eval 함수로 실행되는 코드
  • 함수 코드: 함수 내에 존재하는 코드

실행 컨텍스트가 생성되는 시점

  • 전역 공간 -> 익명 함수 실행
  • eval 함수 사용 시 (사용 잘 안함)
  • 함수 사용 시
  • {} 코드 블럭 사용 시

실행컨텍스트 예시

// ------------------------ (1)
var a = 1;
function outer() {
   function inner() {
      console.log(a);
      var a = 3;
      // ------------------ (2)
   }
   inner(); // ------------ (3)
   console.log(a);
   // --------------------- (4)
}
outer(); // --------------- (5)
console.log(a);
// ------------------------ (6)


실행 컨택스트는 자바스크립트 엔진에서 돌아가기 위해 스택에 순차적으로 쌓인다.

렉시컬 스코프 (정적 스코프) 📋

렉시컬 스코프는 코드를 작성한 순간 스코프가 정해진다.
JavaScript는 정적 스코프(static scope)를 채택하고 있다.

예시 코드

var myName = "guno"; // 전역 변수로 선언
function log() {
  console.log(myName); // 전역 스코프에 있는 myName과 연결되어 있음!
}

function wrapper() {
  myName = "byun"; // 전역 스코프에 있는 myName을 변경함
  log(); // byun (전역 스코프에 있는 변경된 myName을 가져온다)
}

log(); // guno
wrapper(); // byun

var myName = "guno";
function log() {
  console.log(myName);
}

function wrapper() {
  var myName = "byun"; // 함수 내 지역 스코프로 myName을 선언 -> 전역 스코프의 myName에 영향을 주지 않는다.
  log(); // guno (전역 스코프에 있는 myName은 변하지 않으므로 유지)
}

log(); // guno
wrapper(); // guno

클로저 ❌

  • 클로저는 함수와 함수가 선언된 어휘적(정적) 환경의 조합(관계)이다 -MDN-
  • 함수 안에 함수를 선언한 환경에서의 관계를 의미한다.
  • 해당 함수안에 함수를 선언한 환경은 내부 함수에서 외부 함수로 지역변수를 접근할 수 있지만 외부 함수의 실행이 끝나고 외부 함수가 소멸된 이후에도 내부 함수가 외부 함수의 변수에 접근할 수 있는 것을 의미한다.

클로저란 함수가 선언된 환경의 스코프를 기억하여 함수가 스코프 밖에서 실행될 때에도 기억한 스코프에 접근할 수 있게 만드는 문법

function helloName(name){
	const hello = "hello, "; // 지역 스코프라서 함수가 종료되면 메모리에서 사라진다.
    
    return function () {
    	console.log(hello + name);
    }
}

const guno = helloName("guno");

guno(); // 실행 시점에 hello 변수에 접근 가능하다
// 결과값 "hello guno"

외부에서 접근이 불가능한 영역을 closure를 통해 접근할 수 있다.

클로저 사용 이유

1. 정보의 은닉화

  • 클로저를 이용하여 내부 변수와 함수를 숨길 수 있다. (Private Method)

2. 전역 변수의 사용 억제

  • 클로저를 통해 데이터와 메소드를 묶어 모듈화에 유리하다

3. 버그를 잘 수정하기 위해

3번 예시

function counting() {
  let i = 0;
  for (i = 0; i < 5; i++) {
    setTimeout(function () {
      console.log(i);
    }, i * 1000);
  }
}
// 결과값 setTimeout의 2번째 인자 i는 변하지만 1번째 인자 익명함수의 i는 변하지 않는다
함수 안의 변수는 "실행"될 때 값이 결정된다 (JS는 동기적이기 때문에 순차적으로 실행된다)
5 
5 
5 
5 
5
  • setTimeout의 대기 시간이 끝나 콜백 함수가 실행되는 시점에는 for문 루프가 종료되어 i가 5가 되어 5만 출력된다.
  • setTimeout과 for문을 합쳤을 때, for 반복문 실행이 안되고 for문이 종료된 i값이 출력된다. (반복문과 비동기 코드의 문제)

클로저를 이용해 해결해 보자

IIFE를 사용

function counting() {
  let i = 0
  for (i = 0; i < 5; i++) { 
    (function (number) {
      setTimeout(function () {
        console.log(number);
      }, number * 1000);
    })(i);
  }
}

let을 for문 안에 넣는다

function counting() {
  for (let i = 0; i < 5; i++) { // let은 블록 수준 스코프이기 때문에 매 루프마다 클로저가 생성된다
    setTimeout(function () {
      console.log(i);
    }, i * 1000);
  }
}

function을 기준으로 스코프가 정해진다. 함수의 유효 범위는 어디서 정의됐느냐에 따라 달라진다.

다른 클로저 예제

function addFuc(init) {
  function add(num) {
    return init + num;
  }
  return add;
}

let add1 = addFuc(1);
console.log(add1(1)); // 2 <----- (1)
console.log(add1(2)); // 3

let add2 = addFuc(2);
console.log(add2(1)); // 3
console.log(add2(2)); // 4

1번에서 add1은 add 함수에서 온 것이다.
지역 스코프에서 num은 1이 된다. init은 add 함수에 없어 scope chain을 통해 부모 함수의 init을 클로저를 통해서 접근할 수 있다.
고로 지역 스코프의 num은 1 부모 함수의 init은 1로 둘을 더해 2라는 결과값을 도출할 수 있다.

add 함수가 만들어지는 시점에서 그의 부모 함수 addFuc가 가지고 있는 유효범위, 변수들을 add함수가 동봉해서 가지고 있다 언제든지 호출하면 거기에 접근할 수 있다.

더 공부해야하는 내용

  • 이벤트 루프
  • 호출 스택

출처

실행 컨텍스트1
실행 컨텍스트2(poiemaweb)
setTimeout, 실행 컨텍스트의 관계
스코프
클로저

profile
🐣차근차근 무럭무럭🐣

0개의 댓글