# Scope - 스코프 - 유효범위
스코프는 유효범위를 뜻하는 단어입니다.
자바스크립트는 특이하게 다른언어(c, java)처럼 블럭스코프가 아닌 함수스코프를 갖습니다
var a = 1;
function A() {
var b = 2;
}
console.log(a); //1
console.log(b); //b is not defined
b는 함수내에 선언했으므로 외부에서 접근하려고하면 에러입니다.
그리고, 이런 코드가 동작합니다.
if(true) {
var c = 3;
}
console.log(c); //3
이런.. 좋지않습니다.
그래서 ES6에는 블럭스코프를 지원하기위한 let, const 키워드가 등장했습니다.
위의 if문 예제의 var를 let이나 const로 변경하면 에러죠.
그리고 아래 예제를 보면 더욱 이해하기 쉽습니다(?)
let d = 4;
if(true) {
let d = 5;
console.log(d); //5
}
console.log(d); //4
함수얘기가 나왔으니, 실행컨텍스트와 호이스팅, 클로저를 함께보면
자바스크립트 작동을 이해하기 더 쉽습니다.
전역변수는 사실 잘 사용하지도 않고, 설명할것도없지만 버그가 생길 가능성이 큰
암묵적 전역변수만 아래에서 설명합니다.
function A() {
a = 1;
}
A();
console.log(a); //1
A 함수를 실행하지않으면 오류입니다.
하지만 실행하면 a는 전역변수가 됩니다. (글로벌객체의 프로퍼티)
그래서 let, const 키워드를 꼭 붙여서 사용해야합니다.
그래서 개인적으로
let a = 1,
b = 2,
c = 3;
위와 같은 코드를 선호하지않습니다.
실수로 콤마를 빼먹으면 에러도 뱉지않는 전역변수가 되고 버그를 유발하며, 쓸데없는 메모리를 낭비하게 됩니다.
# lexical scope (static scope), dynamic scope
렉시컬 스코프는 함수를 어디에서 선언하였는지에 따라 상위 스코프가 결정됩니다.
다이나믹 스코프는 반대로(?) 함수를 어디에서 호출하였는지에 따라 상위 스코프가 결정됩니다.
var a = 1;
function A() {
var a = 2;
B();
}
function B() {
console.log(a);
}
A(); //1
B(); //1
다이나믹 스코프였다면, 결과는 다를겁니다.
A(); //2
B(); //1
# 스코프체인
id(식별자)를 찾기 위해 필요한것.
스코프가 유효범위라고 했죠?
스코프체인은 식별자가 어느정도 유효범위를 가지는지 알게해줍니다.
예를들어
function a(){
var v1 = 1;
console.log(v2); //ref error - not defined
function b(){
console.log(v1); //1
var v2 = 2;
}
}
위의 코드에서 b라는 함수의 스코프에서는 변수 v1, v2를 참조할수있죠?
b의 스코프에는 v2변수 밖에 없는데, 어떻게 v1을 참조할까요?
바로 스코프체인때문입니다.
스코프내에 다른 스코프가 있다면, 그것을 스코프체인 프로퍼티에 저장해놓는거죠.
그래서 b함수 스코프에서 v1을 사용하면, v1을 b함수 스코프에서 탐색하고,
없으면 이제 상위 스코프를 찾게되는데, 그 외부 스코프에 대한정보를 스코프체인이 갖고 있습니다.
한마디로 스코프체인의 탐색은 해당하는 이름을 찾거나 외부 스코프의 참조가 null이 될때 탐색을 멈춥니다.
# 클로저를 설명할때 자주 쓰이는 예시들
클로저를 설명할때 자주 쓰이는 예시들이지만 스코프에 더 중점을 두고 생각해봐야하는 것 같다.
중요한 포인트는 클로저는 외부변수의 값을 기억하는게 아니라, 참조를 기억한다.
function iter() {
for(var i = 0; i < 5; i++) {
setTimeout(() => {
console.log('i', i);
}, 0);
}
}
iter();
위 코드를 보면 순서대로 0~4가 나와야할거같지만 그렇지않고 '5'가 5번 출력된다.
우선, 타이머 딜레이가 0이어도 setTimeout은 비동기함수이다. (게다가 타이머는 평균 4ms정도의 지연시간도 갖는다.)
console.log가 호출되면 i를 찾기위해 상위 스코프(iter함수의 스코프)에서 i를 찾는다.
그 시점은 for문이 이미 5번 실행되고 난 다음이다. (setTimeout은 단순히 태스크큐에 쌓여있다.)
그래서 iter 함수스코프의 변수 i의 값은 이미 5이다. 그 후에 차례차례 태스크큐에서 setTimeout을 꺼내 실행하므로 5만 5번 출력한다.
# 해결방법
이걸 해결하는방법은 여러가지다.
단순히 var키워드를 let으로 변경하는 방법.
function iter() {
for(let i = 0; i < 5; i++) {
setTimeout(() => {
console.log('i', i);
}, 0);
}
}
iter();
위의 예제는 예상한대로 0~4까지 콘솔에 찍힌다.
setTimeout이 실행되는 시점에 i변수를 상위 스코프에서 찾는데, for구문의 블록스코프에서 변수 i를 찾는다.
for구문 내에서 let으로 선언한 i변수는 각 for구문내에서 각각의 값을 기억하다가 우리가 기대한 값을 돌려주고 있다.
다시 말해서, 5개의 각 스코프에 i변수가 하나씩 있기때문이다.
function iter() {
let i = 0;
for(; i < 5; i++) {
setTimeout(() => {
console.log('i', i);
}, 0);
}
}
iter();
위의 예제는 5라는 숫자만 5개 찍힌다.
이유는 변수선언이 var일때의 설명과 같다.
function iter() {
for(var i = 0; i < 5; i++) {
(function(index) {
setTimeout(() => {
console.log('index', index);
}, 0);
})(i);
}
}
iter();
이 방법 또한, i변수를 함수의 인자로 넘겨버려서, 스코프마다 고유한 index라는 독립적인 자유변수를 생성했기때문에 원하는 결과를 얻을 수 있습니다.
IIFE로 만든 5개의 스코프에 각각 다른값인 index가 생긴겁니다.