7장 스코프
스코프는 변수와 상수, 매개변수가 언제 어디서 정의되는지 결정한다.
가시성(visibility) 이라고도 불리는 스코프는 프로그램의 현재 실행 중인 부분, 즉 실행 컨텍스트에서 현재 보이고 접근할 수 있는 식별자들을 말한다. 반면 존재한다는 말은 그 식별자가 메모리가 할당된(예약된) 무언가를 가리키고 있다는 뜻이다.
정적 스코프와 동적 스코프
자바스크립트의 스코프는 정적이다. 소스 코드만 봐도 변수가 스코프에 있는지 판단할 수 있다는 뜻이다. 다만, 소스 코드만 봐도 즉시 스코프를 분명히 알 수 있다는 뜻은 아니다.
정적 스코프는 어떤 변수가 함수 스코프 안에 있는지 함수를 정의할 때 알 수 있다는 뜻이다. 호출할 때 알 수 있는 것은 아니다.
const x = 3;
function f(){
console.log(x);
console.log(y);
}
// 새 스코프
{
const y = 5;
f();
}
변수 x는 함수 f를 정의할 때 존재하지만, y는 그렇지 않다. y는 다른 스코프에 존재한다. 다른 스코프에서 y를 선언하고 그 스코프에서 f를 호출하더라도, f를 호출하면 x는 그 바디 안의 스코프에 있지만 y는 그렇지 않다. 이것이 정적 스코프이다. 함수 f는 자신이 정의될 때 접근할 수 있었던 식별자에게 여전히 접근할 수 있지만, 호출할 때 스코프에 있는 식별자에 접근할 수는 없다.
자바스크립트의 정적 스코프는 전역 스코프와 블록 스코프, 함수 스코프에 적용된다.
전역 스코프
스코프는 계층적이며 트리의 맨 아래에는 바탕이 되는 무언가가 있어야 한다. 즉, 프로그램을 시작할 때 암시적으로 주어지는 스코프가 필요하다. 이 스코프를 전역 스코프라고 한다. 자바스크립트 프로그램을 시작할 때, 즉 어떤 함수도 호출하지 않았을 때 실행 흐름은 전역 스코프에 있다. 바꿔 말해, 전역 스코프에서 선언한 것은 무엇이든 프로그램의 모든 스코프에서 볼 수 있다.
블록 스코프
let과 const는 식별자를 블록 스코프에서 선언한다. 블록 스코프는 그 블록의 스코프에서만 보이는 식별자를 의미한다.
console.log('before block');
{
console.log('inside block');
const x = 3;
console.log(x); // 3
}
console.log(`outside block; x=${x}`); // ReferenceError: x는 정의되지 않았습니다.
{
// 외부 블록
let x = {color: "blue"};
let y = x; // y와 x는 같은 객체를 가리킨다.
let z = 3;
{
// 내부 블록
let x = 5; // 바깥의 x는 가려졌다.
console.log(x); // 5
console.log(y.color); // "blue"; y가 가리키는, 외부 스코프 x가 가리키는 객체는
// 스코프 안에 있다.
y.color = "red";
console.log(z); // 3
}
console.log(x.color); // "red"; 객체는 내부 스코프에서 수정됐다.
console.log(y.color); // "red"; x와 y는 같은 객체를 가리킨다.
console.log(z); // 3
}
* 외부 스코프에 있는 같은 이름의 변수에 그늘이 진 듯 만든다는 의미에서 변수 숨김을 변수 섀도우라고 부르기도 한다. 변수를 숨기면 그 변수는 해당 이름으로는 절대 접근할 수 없다.
스코프의 계층적인 성격 때문에 어떤 변수가 스코프에 있는지 확인하는 스코프 체인이란 개념이 생겼다. 현재 스코프 체인에 있는 모든 변수는 스코프에 있는 것이며, 숨겨지지 않았다면 접근할 수 있다.
함수가 특정 스코프에 접근할 수 있도록 의도적으로 그 스코프에서 정의하는 경우가 많다. 이런 것을 보통 클로저라고 부른다. 스코프를 함수 주변으로 좁히는 것이라고 생각해도 된다.
let globalFunc; // 정의되지 않은 전역 함수
{
let blockVar = 'a'; // 블록 스코프에 있는 변수
globalFunc = function(){
console.log(blockVar);
}
}
globalFunc(); // 'a'
globalFunc을 호출하면, 이 함수는 스코프에서 빠져나왔음에도 불구하고 blockVar에 접근할 수 있다. 일반적으로 스코프에서 빠져나가면 해당 스코프에서 선언한 변수는 메모리에서 제거해도 안전하다. 하지만 여기서는 스코프 안에서 함수를 정의했고, 해당 함수는 스코프 밖에서도 참조할 수 있으므로 자바스크립트는 스코프를 계속 유지한다.
즉, 스코프 안에서 함수를 정의하면 해당 스코프는 더 오래 유지된다. 또 일반적으로는 접근할 수 없는 것에 접근할 수 있는 효과도 있다.
let f; // 정의되지 않은 함수
{
let o = { note: 'Safe' };
f = function(){ return o; }
}
let oRef = f();
oRef.note = "Not so safe after all";
일반적으로는 스코프 바깥쪽에 있는 것들에는 접근할 수 없다. 함수를 정의해 클로저를 만들면 접근할 수 없었던 것들에 접근할 방법이 생긴다.
즉시 호출하는 함수 표현식 (IIFE)
IIFE는 함수를 선언하고 즉시 실행한다. 스코프와 클로저에 대해 충분히 이해했으니 왜 IIFE를 사용해야 하는지 이해할 수 있을 것이다.
(function(){
// IIFE 바디
})()
함수 표현식으로 익명 함수를 만들고 그 함수를 즉시 호출한다. 이의 장점은 내부에 있는 것들이 모두 자신만의 스코프를 가지지만, IIFE 자체는 함수이므로 그 스코프 밖으로 무언가를 내보낼 수 있다는 것이다.
const message = (function(){
const secret = "I'm a secret!";
return `The secret is ${secret.length} characters long.`;
})();
console.log(message);
변수 secret은 IIFE의 스코프 안에서 안전하게 보호되며 외부에서 접근할 수 없다. IIFE는 함수이므로 무엇이든 반환할 수 있다.
함수 스코프와 호이스팅
ES6에서 let을 도입하기 전에는 var를 써서 변수를 선언했고, 이렇게 선언된 변수들은 함수스코프라 불리는 스코프를 가졌다.
// let을 사용하는 경우
x; // ReferenceError: x는 정의되지 않았다.
let x = 3; // 에러가 일어나서 실행이 멈췄으므로 여기에는 결코 도달할 수 없다.
// var를 사용하는 경우
x; // undeinfed
var x = 3;
x; // 3
변수를 선언하지도 않았는데 그 변수에 접근할 수 있다는 건 이해할 수 없는 일이다. var로 선언한 변수는 끌어올린다는 뜻의 호이스팅이라는 매커니즘을 따른다. 자바스크립트는 함수나 전역 스코프 전체를 살펴보고 var로 선언한 변수를 맨위로 끌어올린다.
* 선언만 끌어올려진다, 할당은 끌어올려지지 않는다.
var x; // 선언이 끌어올려진다.
x; // undefined
x = 3;
x; // 3
함수 호이스팅
var로 선언된 변수와 마찬가지로, 함수 선언도 스코프 맨 위로 끌어올려진다. 따라서 함수를 선언하기 전에 호출할 수 있다.
f(); // 'f'
function f(){
console.log('f');
}
변수에 할당된 함수 표현식은 끌어올려지지 않는다. 이들은 변수의 스코프 규칙을 그대로 따른다.
f(); // ReferrenceError : f는 정의되지 않았다.
let f = function(){
console.log("f');
}
이 글은 이선 브라운. n.d. Learning JavaScript. n.p.: 한빛미디어(주).를 참고하여 정리한 글입니다.
'0 > javascript' 카테고리의 다른 글
Learning JavaScript 요약 정리 (4) 12장 ~ 14장 (이터레이터와 제너레이터, 비동기 프로그래밍) (0) | 2019.05.01 |
---|---|
Learning JavaScript 요약 정리 (3) 8장~10장 (배열, 객체지향 프로그래밍, 맵과 셋) (0) | 2019.04.29 |
Learning JavaScript 요약 정리 (1) (2장 ~ 6장) (0) | 2019.04.27 |
JavaScript 표준의 선구자들 : CommonJS와 AMD (0) | 2019.04.26 |
외부 API를 사용해서 데이터를 끌어오는 방법 (AJAX, Fetch API & Async/Await) (0) | 2019.04.02 |