[JS] 스코프와 클로저

18178 단어 JavaScriptJavaScript

스코프(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

좋은 웹페이지 즐겨찾기