Scope & Closure

anotherhoon·2022년 5월 8일
0

CS

목록 보기
3/5

참고 : https://www.youtube.com/watch?v=PVYjfrgZhtU

1. 스코프

1-1 스코프란?

변수 이름, 함수 이름, 클래스 이름과 같은 식별자가 본인이 선언된 위치에 따라 다른 코드에서 자신이 참조될 수 있을지 없을지 결정되는 것

코드 전체는 전역과 지역으로 구분할 수 있다.
어떠한 변수가 전역에서 선언된다면 해당 변수는 전역 스코프를 갖게 된다.
어떠한 변수가 지역에서 선언된다면 해당 변수는 지역 스코프를 갖게 된다.
어떠한 코드가 실행이 될때 참조하려는 변수가 전역에는 있는데 지역에는 없고 또 어떻게 보니까 지역에는 있는데 전역에도 있으면 이 코드는 어디의 변수를 참조해야 할까. 이것을 알기위해 스코프 체인을 알아봐야한다.

1-2 스코프 체인

함수는 전역에서 정의가 될 수도 있지만 어떠한 함수 내부 몸체에서 정의될 수도 있다.
이를 함수의 중첩이라고 한다. 함수내부에서 정의된 함수를 중첩함수라고 하고 중첩함수를 포함하는 함수를 외부 함수라고 한다. 이처럼 함수가 중첩이 된다면 각각 함수의 지역 스코프도 중첩이 될 수 있다. 이는 스코프가 함수의 중첩에 의해 계층적인 구조를 가질 수 있다는 것을 의미한다.

var x = "나는 전역 x야";
function outer() {
	var y = "나는 outer함수의 지역 y야";
    console.log(x); // 나는 전역 x야
    console.log(y); // 나는 outer함수의 지역 y야
    
    function inner() {
    var x = "나는 inner함수의 지역 x야";
    console.log(x); // 나는 inner함수의 지역 x야
    console.log(y); // 나는 outer함수의 지역 y야
    }
    inner();
}
outer();
console.log(x); // 나는 전역 x야
console.log(y); // ReferenceError ..

전역 스코프
x나는 전역 x 야

|outer|funtion object|

outer 지역 스코프
y나는 outer 함수의 지역 y 야
innerfuntion object

inner 지역 스코프
x나는 inner 함수의 지역 x야

변수를 참조할 때 자바스크립트 엔진은 스코프 체인을 통해 변수를 참조한다.
현재 함수가 어떤 변수를 참조하려고 하는데 내 스코프 안에 원하는 변수가 없다면 하나 위로 올라간다(상위로) 그런데 하나 위로 올라갔는데도 내가 원하는 변수가 없다면 그러면 다시 위로 올라간다. 그렇게 올라가다보면 최상위 스코프인 전역스코프까지 올라가게되는데 전역스코프에도 내가 원하는 변수가 없다면 ReferenceError를 반환한다.

스코프체인에서 변수를 참조 할 때 무조건 위로만 올라간다. 하위스코프에서 상위스코프에 있는 변수를 참조할 수 있는 이유가 바로 스코프 체인의 반방향성 때문이다.

1-3 스코프 종류

스코프 레벨에는 블록 레벨 스코프와 함수 레벨 스코프가 있다.

블록 레벨 스코프 (if문, for문, 함수 ...)

함수 레벨 스코프 (only 함수)
자바스크립트에서는 var키워드로 선언된 변수는 오로지 함수 코드 블록만을 지역 스코프로 인정한다.

상위 스코프가 결정되는 시점을 기준으로 스코프는 동적 스코프와 정적 스코프로 나뉜다.

함수가 호출되는 시점에 결정되는 스코프를 동적 스코프라고 한다.

프로그램 런타임 도중에 실행 컨텍스트나 호출 컨텍스트에 의해서 스코프가 결정되는 것을 의미한다.

함수자 정의되는 시점에 결정되는
함수가 정의되는 시점에 딱 상위 스코프가 결정되는 것을 정적 스코프, 렉시컬 스코프라고 한다.

자바스크립트는 렉시컬 스코프를 따르기 때문에 함수는 딱 바로 이렇게 태어나자 마자 상위 스코프가 결정되고 이후에 해당 함수에 의해 함수 객체가 생성이 되면 해당 함수 객체는 본인의 상위 스코프를 항상 알 수 있게 된다.
이렇게 해당 함수가 상위 스코프를 항상 알 수 있게 되는 이유는 자바스크립트에서 함수는 태어나면서 자신의 내부 슬롯에 상위 스코프의 참조를 저장하기 때문이다.

자바스크립트에서 함수는 태어나면 본인의 내부 슬롯에 상위 스코프에 대한 참조를 저장한다.

함수가 호출이 되면 자바스크립트 엔진에 의해 다음과 같은 흐름이 진행된다.

함수 호출

실행 컨텍스트 생성 → 실행 컨텍스트 스택에 push한다.

렉시컬 환경 생성(포함하는 식별자, 식별자에 바인딩 된 값, 상위 렉시컬 환경에 대한 참조)
렉시컬 환경이란 어떠한 코드가 어디서 실행이 되고 본인 주변에 어떤 코드들이 있는지 대체적인 정보를 담고 있는 환경이다.

코드의 실행이 끝나면 실행 컨텍스트 스택에서 해당 컨텍스트를 pop하여 제거한다.

클로져

중첩함수 inner가 이미 생명주기를 마감한 outer 함수
즉, 내부 함수의 지역 변수 x를 참조할 수 있다면 이때 inner를 클로져라고 한다.

const x = 1;

function outer(){
	const x = 10;
    const inner = function (){
    	console.log(x);
    };
    return inner;
}
const ella = outer();
ella(); //10

outer함수 종료전 실행 컨텍스트 렉시컬 환경의 모습을 간단하게 그려보았다.
outer 함수가 종료되는 모습을 살펴보자. outer함수의 생명주기가 끝났으니 실행컨텍스트에서 제거된다.
그리고 outer함수는 ella한테 inner 함수를 반환하면서 사라지기 때문에
지금부터 ella는 inner 함수 객체를 참조한다.

outer 함수는 실행 컨텍스트 스텍에서 완전히 제거되었다.
하지만 outer함수의 렉시컬 환경까지 손을 대지는 않았다.
ella는 inner함수 객체를 참조하고 있고 inner함수 객체는 본인의 내부 슬롯에 저장된 outer함수의 렉시컬 환경을 참조하기 때문에 가비지 컬렉션의 대상이 되지 않는다.
따라서 ella에 의해 inner함수를 다시 호출하면 outer함수 안에 있는 10을 값으로 가지고 있는 변수 x를 다시 참조할 수 있게 된다.
비록 inner함수의 객체 기준 내부 함수는 생명주기를 마감하고 실행컨텍스트스텍에서 전부 사라졌지만 내가 기억하고 있는 내부 슬롯에 저장된 상위 스코프에 의존하여 상위 스코프 내의 식별자를 참조할 수 있게 되는 것이다.

그리고 이것이 바로 클로져라는 개념이다.

중첩 함수

  • 상위 스코프의 식별자를 참조하고 있고
  • 본인의 외부 함수보다 더 오래 살아있다면

    클로져
    클로져는 본인의 상위 스코프에서 현재 참조하는 식별자만을 기억하는데 이렇게 클로져에 의해 참조된 변수를 자유변수(참조하는 상위함수의 변수)라고 한다.
    따라서 클로져, 닫혀 있다는 느낌이 드는 이 이름은 함수 본인이 기억하고 있는 자유변수에 의해 닫혀 있다, 갇혀있따, 즉 closed되어 있다. 라고 생각할 수 있을 것 같다. 이러한 클로져는 하나의 state가 의도치 않게 변경되지 않도록 state를 안전하게 은닉하고 또는 특정 함수에게만 state변경을 허용하기 위해 사용한다고 한다.

0개의 댓글