스코프
스코프의 정의
You Don't Know JS 에 따르면 스코프란,
특정 장소에 변수를 저장하고 변수를 찾는데 필요한 규칙을 '스코프(scope)' 라 정의한다.
즉 말그대로 자바스크립트에서 변수를 찾을 때 스코프에 따라서 변수를 찾는다.
변수들은 자신들의 스코프내에서만 유효하다.
변수에 따른 스코프
var
함수 스코프 단위다.
즉, 함수내에서만 유효하다.
let,const
블록 스코프 단위다.
{} 내에서 유효
렉시컬 스코프 (Lexical Scope)
자바스크립트는 렉시컬 스코프 방식을 채용하고 있다.
렉시컬 스코프란 렉싱(Lexing) 타임에 정의되는 스코프를 말한다.
쉽게 말하면 코드가 적힌 순간에 정해지는 스코프다. 실행 순서와는 관계가 없다.
코드를 짤 때 변수와 스코프 블록을 어디서 작성하는 가에 기초해 정해진다.
예시
var name = 'zero';
function log(){
console.log(name); // zero 를 출력한다.
}
function wrapper() {
var name = 'nero';
log();
}
wrapper();
function 내에서 변수를 찾을 때, 해당 변수가 없으면 해당 function 밖으로 올라가면서 변수가 있는지 확인한다.
이를 scope chain 을 타고 올라간다고도 한다.
전역범위 까지 없으면 not defined error 가 뜬다.
위의 예시에서는 log 함수 내 zero 변수가 없기 때문에, 바깥쪽 스코프에서 zero 라는 변수를 찾아 출력한 것이다.
변수 뿐 아니라 function 선언도 스코프 적용을 받는다.
위 예에서도 wrapper 내에서 log 함수가 먼저 있는지 확인하고 없으면, 그 상위 scope 를 찾아서 호출한다.
즉시 실행 함수 (IIFE)
보통 스코프를 숨길 때, 변수나 함수선언문을 함수로 감싼다.
일반적으로 함수를 선언하고 호출하는 방식말고, 즉시 실행 함수를 이용할 수도 있다.
즉시 실행 함수란 아래와 같은 형태의 함수를 말한다. (줄여서 IIFE 다.)
(function foo() {
//
} () )
IIFE 가 같는 이점은 두가지가 있다,
- 함수를 둘러싼 스코프를 오염시키지 않는다.
- 함수를 이름으로 호출하지 않아도 된다.
첫번째는 무슨 말이냐면, 일반적인 방식으로 아래처럼 함수를 실행했다고 하자.
function foo() {
var a = 3;
console.log(a);
}
foo();
이럴 경우 , foo 라는 확인자 이름으로 둘러싸인 스코프를 오염시킨다.
즉 foo 가 있는 스코프에서는 foo 라는 이름을 쓸 수 없다.
IIFE 는 ( ) 로 묶이면서 함수 자신의 내부 스코프에 묶인다.
확인자 foo 는 바깥 스코프에서는 발견되지 않는다.
(... ) 로 함수를 감싸면 함수를 표현식으로 바꾸고, 마지막에 () 를 붙이면 함수를 실행할 수 있다.
IIFE 는 함수 표현식 으로 취급된다.
블록 스코프
블록 스코프는 { } 와 같은 임의의 코드 블록에 변수와 함수가 속하는 개념이다.
블록 스코프가 유용한 이유중 하나는 가비지 콜렉션과 관련이 있다.
function job(data) {
// data 를 가지고 뭔가 한다.
}
var bigData = { .. }:
job(bigData);
var btn = document.getElementById("btn");
btn.addEventListener("click", function click(e) {
console.log("button clicked");
});
위에서 click 함수는 bigData 가 전혀 필요 없다.
job() 이 실행 된 후, bigData 는 수거 될 수있지만, 자바스크립트 엔진은 bigData 를 그냥 남겨둔다.
왜냐면 저 click 함수가 아직 bigData 가 선언되어 있는 스코프 전체의 클로저를 가지고 있기 때문이이다.
아래와 같이 job() 함수 호출하는 부분을 블록 스코프로 감싸준다면 쉽게 해결된다.
{
var bigData = { .. }:
job(bigData);
}
let
let,const 는 es6 에 들어온 키워드이다.
let 은 선언된 변수를 둘러싼 아무 블록의 스코프에 붙인다. 그래서 흔히 let 은 블록 스코프를 가진다고 한다.
또한 let 은 호이스팅 효과를 받지 않는다.
let 과 반복문
다음과 같은 for문 반복문이 있다고 하자.
for(let i=0; i<10; i++) {
console.log(i);
}
let 은 i 를 for 반복문에 묶고, 반복문이 돌 때마다 변수를 다시 묶어서 다시 계산된 i 값이 제대로 들어가게 한다.
let 은 블록 스코프를 가지기 때문에 반복문의 주기마다 비동기나 콜백 함수에 반복문의 값을 넣어주고 싶을때 사용하기 좋다. 아래는 클로저의 특성을 이용한 예이기도 하다.
for(let i=0; i<10; i++) {
setTimeout(function() {
console.log(i);
}, i*1000);
}
위의 예에서는 1초마다 차례대로 0~9 까지 출력된다.
for 문내 i 는 블록 스코프를 가지게 되고, 각 for 문의 i는 for 루프가 종료되어도 i 를 참조하는 함수가 존재하기 때문에 계속 유지된다.
'Javascript' 카테고리의 다른 글
Typescript generic & util (0) | 2021.04.08 |
---|---|
return function (0) | 2021.03.24 |
[You don't know JS] Chapter2 - 값 (0) | 2021.02.25 |
[You don't know JS] Chapter1 - 타입 (0) | 2021.02.23 |
클로저 (0) | 2020.11.19 |