(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
자바스크립트 포함 대부분의 프로그래밍 언어들이 렉시컬 스코프 규칙을 따르고 있다.
렉시컬 스코프는 다른 말로는 정적 스코프라고도 하며, 아래 코드를 통해 정적 스코프가 어떻게 구성되고 동작하는지 살펴보자.
var x = 'global'
function foo(){
var x = 'local';
}
function bar(){
console.log(x);
}
foo(); // 출력 : global
bar(); // 출력 : global
자바스크립트의 전역 스코프의 실행 과정은 아래 그림과 같다.
즉, foo()함수 안에 bar함수를 실행 시킬 때 bar함수 내에 x 변수를 찾지 못하였으므로 해당 x 변수를 전역 스코프에서 찾는다.
그 이유는 bar함수가 선언된 곳이 전역 스코프이기 때문이다.
자바스크립트 엔진은 식별자를 찾을 때 일단 자신이 속한 스코프에서 찾고 그 스코프에서 식별자가 없으면 상위 스코프에서 다시 찾아 나간다. 이 현상을 스코프 체인이라고 한다.
스코프 체인은 쉽게 말해서 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
아래 그림을 통해 쉽게 이해할 수 있을 거 같다.
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