[JS] 스코프와 클로저

황준승·2021년 9월 2일
0

스코프(Scope)

정의

(Scope, 유효 범위)는 자바스크립트를 포함한 모든 프로그래밍 언어의 기본적인 개념으로 확실한 이해가 필요하다.

변수는 전역 또는 코드 블록이나 함수 내에 선언하며 코드 블록이나 함수는 중첩될 수 있다.
이때 식별자는 자신이 어디에서 선언되었는지에 의해 자신이 유효한 범위를 갖는다.

예제)

let variable = 'global';

function func(){
    let variable = 'local';
    console.log(variable);
}

func();   // 출력: local
console.log(variable); // 출력: global

위 예제에서 전역에 선언된 variable 변수는 어디서는 참조할 수 있다. 하지만 func()함수 내에서 선언된 변수 variable은 내부에서만 참조할 수 있고 외부에서는 참조할 수 없다. 이러한 규칙을 스코프라고 한다.

이러한 스코프는 변수의 보안성을 높여주고 또 변수의 이름의 중복을 허용해준다.

함수 스코프

정의

  • 함수 레벨 스코프란 함수 코드 블록 내에 선언된 변수는 함수 코드 블록 내에서만 유효하고 함수 외부에서는 유효하지 않다.
var x = 0;

// 블록 단위는 전역변수로 친다. 
{
  var x = 1;
  console.log(x); // 1
}

console.log(x);   // 1

function func(){
    // 함수 내에서만 유효
    var x = 2;
    console.log(x); // 2
}

func();
console.log(x); // 1

자바스크립트는 기본적으로 함수 레벨 스코프를 따른다.

블록 스코프

정의

ES6부터 새로 생긴 const,let을 통해 블록 스코프 구현이 가능해졌다.

블록 레벨 스코프란 {} 블록 내에 선언된 변수는 {} 블록 내에서만 유효하고 함수 외부에서는 유효하지 않다.

let x = 0;

// 블록 단위는 전역변수로 친다. 
{
  let x = 1;
  console.log(x); // 1
}

console.log(x);   // 0

function func(){
    // 함수 내에서만 유효
    let x = 2;
    console.log(x); // 2
}

func();
console.log(x); // 1

렉시컬 스코프(Lexical Scope)

자바스크립트 포함 대부분의 프로그래밍 언어들이 렉시컬 스코프 규칙을 따르고 있다.

렉시컬 스코프는 다른 말로는 정적 스코프라고도 하며, 아래 코드를 통해 정적 스코프가 어떻게 구성되고 동작하는지 살펴보자.

  • 정적 스코프
var x = 'global'

function foo(){
	var x = 'local';
}

function bar(){
	console.log(x);
}

foo(); // 출력 : global
bar(); // 출력 : global

자바스크립트의 전역 스코프의 실행 과정은 아래 그림과 같다.

즉, foo()함수 안에 bar함수를 실행 시킬 때 bar함수 내에 x 변수를 찾지 못하였으므로 해당 x 변수를 전역 스코프에서 찾는다.

그 이유는 bar함수가 선언된 곳이 전역 스코프이기 때문이다.

스코프 체인(Scope Chain)

자바스크립트 엔진은 식별자를 찾을 때 일단 자신이 속한 스코프에서 찾고 그 스코프에서 식별자가 없으면 상위 스코프에서 다시 찾아 나간다. 이 현상을 스코프 체인이라고 한다.

스코프 체인은 쉽게 말해서 Identifiers(식별자)를 찾는 일련의 과정이라 할 수 있다.

예제)

const globalColor = 'red';

function foo(){
    const fooColor = 'blue';

    function bar(){
        const barColor = 'yellow';

        console.log(barColor);
        console.log(fooColor);
        console.log(globalColor);

    }

    bar();
}

foo();

// 출력
// yellow
// blue
// red

아래 그림을 통해 쉽게 이해할 수 있을 거 같다.

클로저(Closer)

Q. 우리는 스코프 체인을 통해서 하위스코프에서 상위 스코프로 식별자를 찾아나서는 작업을 할 수 있다. 그렇다면 반대로 상위 스코프에서 하위 스코프로 식별자를 찾아나서는 작업은 할 수 없을까??

A. 클로저를 통해 가능하다.

정의

클로저의 개념은 현대 프로그래밍에서 다음과 같이 해석된다.

클로저 = 함수 + 함수를 둘러싼 환경(렉시컬 스코프)

이 수식을 좀 더 풀어서 이야기를 해보면 다음과 같다.

클로저 = 함수가 생성될 때 그 함수의 렉시컬 환경을 포섭(clsure)하여 실행될 때 이용한다.

솔직히 이 말만 보고는 잘 이해가 가지 않는다....

잘못된 예시

function foo(){
	const color = 'blue';
  	function bar(){
    	console.log(color);
    }
  	bar();
}
foo();

코드 설명 : 앞서 스코프체인 코드와 유사하게 코드를 짜보았다.

함수환경
foo()global enviroment
bar()foo enviroment

Q.foo()함수를 통해 foo 스코프의 color 변수를 참조 했으니 이를 클로저라고 할 수 없을까??

__A. NO - bar는 단순히 foo 안에 정의되고 실행되었을 뿐, foo 밖으로 나오지 않았기 때문에 클로저라고 하지 않는다.

옳은 예시

const color = 'red';
function func() {
    const color = 'blue'; // 2
    function clouser() {
        console.log(color); // 1
    }
    return clouser;
}

const newClouser = func(); // 3
newClouser(); // 4

// blue 출력

코드 설명
1. func 함수 안에 지역 변수 color와 clouser함수를 선언한다.
2. clouser는 func의 리턴값으로 foo의 environment를 저장하였다.
3. clouser를 global 환경에서 newClouser라는 이름으로 데려왔다.
4. global에서 newClouser(=clouser)를 호출했다.
5. clouser은 자신의 스코프에서 color를 찾는다.
6. 없으므로 자신의 outer environment인 func함수의 scope를 찾아본다. => blue 출력

추가로 공부해나갈 사항

대표적으로 클로저하면 나오는 예시가 하나 있다.

function count() {
    var i;
    for (i = 1; i < 10; i += 1) {
        setTimeout(function timer() {
            console.log(i);
        }, i*100);
    }
}
count();
// 출력
// 5
// 5
// 5
// 5
// 5

위의 코드를 보면 아시겠지만 클로저를 잘못 사용하여 출력값이 이상하게 나온 것을 알 수 있다. 이 코드를 보면서 자바스크립트의 이벤트 루프와 콜스택 그리고 비동기 에 대해 좀 더 자세히 공부한 뒤에 다시 살펴봐도 좋을 거 같다는 생각을 한 번 해보았다.

참고 자료

(meetup.toast.com) https://meetup.toast.com/posts/86
(poiemaweb.com) https://poiemaweb.com/js-scope
(유투브 우리밋)
스코프 : https://www.youtube.com/watch?v=PEhJe_yai1Q&t=535s
클로저 : https://www.youtube.com/watch?v=-XfvJ5ShS-g

(tyle.io) 스코프 체인이란? https://tyle.io/blog/54

profile
다른 사람들이 이해하기 쉽게 기록하고 공유하자!!

0개의 댓글