클로저

Vorhandenheit ·2021년 7월 20일
0

JS/Node 

목록 보기
8/63

클로저

'단순하게 생각하면 함수 안에 어떤 값이 없을 때 다른데 가서 찾는 것입니다'
하지만 이런 이해만 가지고는 클로저를 사용할수 없습니다.

클로저의 장점인 캡슐화, private, 이런 말을 설명할 수 없기 때문입니다.

https://www.youtube.com/watch?v=SpHiBkjuwQM&ab_channel=%EA%B0%80%EC%9E%A5%EC%89%AC%EC%9A%B4%EC%9B%B9%EA%B0%9C%EB%B0%9CwithBoaz

실행되기 이전에 이미 환경을 구성한다는 걸 생각을 못했는데, 보고 단번에 이해됬습니다.!

변수에 참조하고있는 익명함수를 할당,
외부에서 참조 불가능하게끔 하고싶음, 그렇게 하기위해서 특수한 스킬 사용 그게 클로저

클로저의 배경

위에 그림에서 실행 컨텍스트라는 내용이 나옵니다. 먼저는 이 실행 컨텍스트에 대해서 알아야합니다.

1. 실행컨텍스트(Execution Context)란?

실행컨텍스트에는 대표적으로 두가지 타입Global Execution contextFunction Execution context가 있습니다.

  • Globacl Execution context
    자바스크립트 엔진이 실행될 때 가장 먼저 생성되는 실행 컨텍스트입니다. 전역 컨텍스트라고하며, 생성되는 모든 전역 변수, 함수는 여기 window 객체에 저장됩니다.

  • Function Execution Context
    함수가 호출되면 함수를 위한 실행 컨텍스트가 생성됩니다.

2. 실행 컨텍스트 단계

모든 실행 컨턱세트는 create단계와 execution 단계를 통해 생성됩니다
우리가 화면에 코드를 작성하는 경우는 create phase 단계입니다. 변수를 선언한다고 했을 경우, 뒤에서는 많은 일들이 벌어지고 있습니다.

(1) Create Phase

생성 단계에서 자바스크립트 엔진은

  • argumnets 객체를 생성 (다른말로 활성객체)

  • this 객체를 생성

  • 변수에는 undefined를 할당하고, 함수 선언문은 실제로 메모리에 할당합니다

  • LexicalEnvironment, VariableEnvironment 컴포넌트 생성

A. Lexical Environment 란

Lexical Environment는 ECMAScript 코드의 lexical 중첩 구조 상에서 특정 변수와 함수에 대한 인식자(Identifier)들을 정의하는데 사용되는 명세 유형입니다. Lexical Environment는 Environment Record와 outer Lexical Environment에 대한 가능한한 null 참조로 구성되어 있다.

뭐라는거야... 대충 변수 선언을 했을 경우, 해당 변수의 scope, 지금있는 위치 등 모든 정보를 가지고 있습니다.
이 Lexical Environment는 3가지 컴포넌트를 가지고 있습니다.

  • Environment Record : 컨텍스트 내부의 식별자 정보를 수집합니다. (함수나 변수 선언을 찾습니다)
    - Declarative environment record : 변수와 함수선언을 저장합니다
    - Object environment record : global binding object를 저장합니다.

  • Reference to the outher environment : 현재 실행 컨텍스트의 외부 lexical environment에 대해 접근하는 걸 의미합니다. (scope와 관계되어있습니다)

  • this binding : this의 값을 결정하거나 설정합니다.

Declarative 에는 GetBindingValue라는 메소드가 있습니다.이 메소드는 식별자가 바인딩할께 없거나, uninitialize를 바인딩하면 ReferenceError를 throw합니다.

위에서 Lexical Environment와 Variable Environment가 나옵니다. 이 두개는 하나의 차이점이 있는데 lexical 은 let과 const 바인딩을 저장하는데 사용되고, variable은 var 바인딩을 저장하는데 사용됩니다.
함수를 선언하면 선언 => 초기화 => 할당 단계로 구분이 되는데 var은 선언과 초기화를 같이 해버립니다. 이를 hosting이라고 합니다. 할당 단계는 뒤에 나오는 Execution phase입니다.

Example

let a = 20;
const b = 30;
var c;
function multiply(e, f) {
 var g = 20;
 return e * f * g;
}
c = multiply(20, 30);
GlobalExectionContext = { // 전역변수
  LexicalEnvironment: {
    EnvironmentRecord: {
      Type: "Object",
      // 인식자(Identifier) binding은 여기로 이동한다.
      a: < uninitialized >,
      b: < uninitialized >,
      multiply: < func >
    }
    outer: <null>,
    ThisBinding: <Global Object>
  },
  VariableEnvironment: {
    EnvironmentRecord: {
      Type: "Object",
      // 인식자(Identifier) binding은 여기로 이동한다.
      c: undefined,
    }
    outer: <null>,
    ThisBinding: <Global Object>
  }
}

위에 multiply(20, 30) 함수에 호출이 발생하면, 새로운 함수 실행 컨텍스트가 함수코드를 실행하기 위해 생성됩니다.

FunctionExectionContext = {
  LexicalEnvironment: {
    EnvironmentRecord: {
      Type: "Declarative",
      // 인식자(Identifier) binding은 여기로 이동한다.
      Arguments: {0: 20, 1: 30, length: 2},
    },
    outer: <GlobalLexicalEnvironment>,
    ThisBinding: <Global Object or undefined>,
  },
  VariableEnvironment: {
    EnvironmentRecord: {
      Type: "Declarative",
      // 인식자(Identifier) binding은 여기로 이동한다.
      g: 20
    },
    outer: <GlobalLexicalEnvironment>,
    ThisBinding: <Global Object or undefined>
  }
}

(2) Execution Phase

자바스크립트 파일이 실행이 되면 전역 컨텍스트가 콜 스택에 담깁니다.
이 단계에서 변수들에 값이 할당됩니다.

3. 클로저

클로저는 보통 함수가 실행되면 실행 컨텍스트가 생성되고, 다시 반환되면 함수의 생명주기가 끝나게되지만 클로저를 사용함으로 함수의 생명주기를 연장할 수 있습니다.

function outerFunc() {
    var x = 10;
    var innerFunc = function() { console.log(x); }
    return innerFunc;
}

var inner = outerFunc();
inner();

outerFunc의 실행 컨텍스트는 사라지지만, outerFunc 변수 객체는 여전히 남아있고, innerFunc의 스코프체인으로 참조됩니다. 이게 클로저입니다
어떻게 이게 가능하나면 자바스크립트 모든 함수는 자신의 상위 environmnet를 기억하고 있기 때문입니다. 함수를 선언하는 순간 메모리에 위치를 참조한다는 걸 기억해주세요!

클로저 예시

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

(1)outer 함수를 실행
(2)안에 inner함수가 실행
a가 2개 있지만, inner안에 a가 가장 가깝고 내부에 있기 떄문에 2를 출력


function outer() {
	var a= 0;
  
  function inner() {
  	a++;
  	return a
  } 
  return inner;
}
var func = out();
func(); // 1
func(); // 2
func(); // 3 

func()을 실행할 때마다 1씩 더해지고 있음을 확인할 수 있다.
그러나 결과는 a에 계속 1이 더해지고 있다. 왜냐하면 inner가 외부참조환경을 기억하고 outer스코프가 아직 사라지지않았기 떄문에


function outer() {
	var a = 0;
    function inner() {
  		a++;
  		return a;
  }
  return inner;
}
var func = outer();
var func2 = outer();
  
func // 1
func // 2
func2 // 1

func에 담긴 inner함수와 func2에 담긴 inner함수가 각각 다른 outer 함수의 스코프를 참조하고 있다.
func2를 이용해서 값을 똑같이 더하고 싶다면 IIFE(즉시실행함수) ()를 이용하면 되거나, 전역 스코프로 옮기면된다.(위험하니 하지말자)


참고

https://park-answer.netlify.app/2019-12-15-Closure/
https://ko.javascript.info/closure

profile
읽고 기록하고 고민하고 사용하고 개발하자!

0개의 댓글