스코프(Scope, 유효 범위)란, 간단히 말하면 특정 장소에 변수를 저장하고 나중에 그 변수를 찾기 위해 정의된 규칙입니다.
var foo = 0;
function myFunction() {
var foo = 100;
console.log(foo);
}
myFunction(); // (1)
console.log(foo); // (2)
(1)의 결과와 (2)의 결과는 각각 어떻게 될까요?
결과는 (1)은 100이 되고, (2)는 0이 됩니다.
예시에서 전역에 선언된 변수 foo는 어디에서든 참조할 수 있는 반면에 함수 myFunction 내에서 선언된 변수 foo는 함수 내부에서만 참조할 수 있고 외부에서는 참조할 수 없습니다.
이러한 규칙을 스코프라고 합니다.
만약 스코프가 없다면 같은 식별자로 쓰인 이름은 충돌을 일으키게 되어 제대로 동작하지 않을 것 입니다.
그럼 자바스크립트는 어떤 변수를 참조할지, 어떻게 변수를 식별하게 되는 걸까요?
1. LHS검색과 RHS검색
스코프에 대해 알아보기 전 LHS검색과 RHS검색에 대해 간단히 알아보겠습니다.
자바스크립트 엔진은 코드를 실행할 때 변수가 선언된 적이 있는지 스코프에 따라 검색하게 됩니다.
이때 엔진이 어떤 종류의 검색을 하느냐에 따라 결과가 달라지게 됩니다.
LHS검색(Left-Hand Side)은 말 그대로 변수가 대입 연산자의 왼쪽에 있을 때 수행하고,
RHS검색(Right-Hand Side)은 변수가 대입 연산자의 왼쪽에 있지 않을 때 수행합니다.
쉽게 말해, LHS검색은 변수 컨테이너를 찾는 것이고 RHS검색은 특정 변수의 값을 찾는 것입니다.
console.log(a);
위 구문에서 구문에서 a에 아무것도 대입하지 않기 때문에 a에 대한 참조는 RHS검색입니다.
즉, a의 값을 가져와 console.log()에 넘겨주게 됩니다.
a = 2;
위 구문에서 '= 2'를 대입 연산을 수행할 대상 변수를 찾아야 하기 때문에 a에 대한 참조는 LHS검색입니다.
console.log(myVariable); // Reference Error
RHS검색은 대상을 찾지 못했을 때 Reference Error가 발생합니다.
function foo() {
myVariable = 2;
}
foo();
console.log(myVariable); // 2
LHS검색은 대상을 찾지 못했을 때에 암시적으로 최상위 스코프에 같은 이름의 변수가 생성됩니다.
엄밀히 말하자면, 최상위 객체에 프로퍼티로 추가되는 것입니다!
'use strict';
function foo() {
myVariable = 2;
}
foo();
console.log(myVariable);
strict mode에서는 Reference Error 발생합니다!
2. 중첩 스코프
스코프는 특정 변수를 찾기 위해 정의된 규칙이라고 앞서 말씀드렸습니다.
일반적으로 프로그래밍을 하다 보면 고려해야 할 스코프는 여러 개가 될 것입니다.
스코프가 중첩된 경우에는 어떻게 동작하게 될까요?
var a = 10;
function foo(b) {
console.log(a + b);
// a에 어떻게 접근할 수 있을까?
}
foo(10); // 20
위의 예시에서는 함수 내에 존재하는 변수 a를 찾을 수 없습니다.
자바스크립트 엔진은 대상 변수를 현재 스코프 내에서 찾을 수 없으면 바깥의 스코프로 넘어가 대상 변수를 찾게 됩니다.
가장 바깥 스코프에 도달할 때까지 이 과정이 계속됩니다.
자바스크립트 엔진은 현재 스코프에서 변수를 찾기 시작하고, 찾을 수 없다면 한 단계씩 상위 스코프로 이동하며 대상 변수를 찾습니다.
최상위 스코프(글로벌 스코프)에 도달하면 찾았는지 못 찾았는지에 관계없이 검색을 멈춥니다.
이러한 과정을 스코프 체인(scope chain)이라고 합니다.
3. 스코프의 구분
자바스크립트에서 스코프를 구분해보면 어디에서든 참조할 수 있는 전역 스코프(Global Scope)와 블록 내 하위 스코프에서만 참조할 수 있는 지역 스코프(Local Scope)로 나눌 수 있습니다.
4. 자바스크립트 스코프의 특징
대부분의 C-family language는 블록 레벨 스코프(block-level scope)를 따르고 있습니다.
반면 자바스크립트는 함수 레벨 스코프(function-level scope)를 따르고 있습니다.
그리고 자바스크립트는 렉시컬 스코프(lexical scope)로 작동합니다.
단, ES6에서 추가된 let, const keyword를 사용하면 블록 레벨 스코프를 사용할 수 있습니다!
그렇다면 함수 레벨 스코프는 무엇이고, 블록 레벨 스코프는 무엇일까요?
함수 레벨 스코프란 함수의 코드 블록 내에서 선언된 변수는 함수 코드 블록 내에서만 유효하고 외부에서는 참조할 수 없다는 규칙입니다.
var x = 0;
console.log(x); // 0
{
var x = 1;
console.log(x); // 1
}
console.log(x); // 1
블록 레벨 스코프란 코드 블록 내에서만 유효한 스코프를 의미합니다.
// 자바스크립트는 기본적으로 function-level scope를 사용하지만
// let, const 키워드를 사용하면 block-level scope를 사용 할 수 있습니다!
let x = 0;
console.log(x); // 0
{
let x = 1;
console.log(x); // 1
}
console.log(x); // 0
4-1 전역 스코프(Global scope)
전역에 변수를 선언하면 어디서든 참조할 수 있게 됩니다. 이를 전역 변수라고 부릅니다.
전역에서 var 키워드를 사용해 선언한 변수는 전역 객체(Global Object) 즉, Browser에서는 window의 property가 됩니다.
var global = 'global';
function foo() {
var local = 'local';
console.log(global); // 'global';
console.log(local); // 'local'
}
foo();
console.log(window.global); // 'global';
console.log(global); // 'global';
console.log(local); // Reference Error;
4-2 함수 레벨 스코프(Function-Level scope)
var myVariable = 'global';
function foo() {
var myVariable = 'local';
console.log(myVariable);
function bar() {
console.log(myVariable);
}
bar(); // 'local'
}
foo(); // 'local'
console.log(myVariable); // 'global'
어떤 함수의 내부 함수는 자신을 포함하고 있는 외부 함수의 변수에 접근할 수 있습니다.
중첩 스코프에서 설명드렸던 것처럼 현재 스코프상에 참조할 수 있는 변수가 없기 때문에 자바스크립트 엔진은 상위 스코프로 이동하며 해당 변수를 찾게 됩니다.
var myVariable = 10;
function foo() {
myVariable = 500;
}
foo();
console.log(myVariable); // 500
또한 함수 내에서 전역 변수를 참조할 수 있으므로 전역 변수의 값도 변경할 수 있습니다!
내부 함수에서는 전역 변수는 물론 상위 함수의 변수에도 접근/변경이 가능합니다.
4-3 렉시컬 스코프
var myVariable = 1;
function foo() {
console.log(myVariable);
}
function bar() {
var myVariable = 10;
foo();
}
foo(); // 1
bar(); // 1
위 코드의 실행 결과는 의아할 수도 있습니다. 스코프는 두 가지 방식으로 작동하게 됩니다.
첫 번째 방식은 일반적이고 다수의 프로그래밍 언어가 사용하는 방식으로 렉시컬 스코프(Lexical Scope)라고 부르며,
두 번째 방식은 몇몇 언어에서 사용하는 방식으로 동적 스코프(Dynamic Scope)라고 부릅니다.
위에서 말씀드린 것처럼 자바스크립트는 렉시컬 스코프로 동작하는데요.
렉시컬 스코프란, 함수를 어디서 호출하는지가 아닌 어디에 선언했는지에 따라 결정되는 것입니다.
위 코드를 예로 들면, foo 함수가 선언된 시점의 상위 스코프는 전역 객체이므로 myVariable값을 전역에서 찾아 사용하게 되는 것입니다.
반대로 동적 스코프란, 함수가 어디서 호출되었는지에 따라 결정되는 것입니다.
자바스크립트에서 this 키워드의 메커니즘과 상당한 연관이 있다고 볼 수 있습니다.
4-4 암묵적 전역
var x = 10;
function foo() {
y = 20;
console.log(x + y);
}
foo(); // 30
위 예제를 보면 y 변수는 선언된 곳이 없습니다.
자바스크립트 엔진은 LHS검색으로 해당 변수를 찾지 못했을 때 의해 암시적으로 최상위 객체에 해당 변수를 property로 추가한다고 하였습니다.
이런 현상으로 마치 전역 변수처럼 동작하게 되는데, 이러한 현상을 암묵적 전역(implicit global)이라고 합니다.
console.log(x); // undefined
console.log(y); // Reference Error
var x = 10;
function foo() {
y = 20;
console.log(x + y);
}
foo();
console.log(x); // 10
console.log(y); // 20
console.log(window.x); // 10
console.log(window.y); // 20
delete window.x;
delete window.y;
console.log(x); // 10
console.log(y); // Reference Error
console.log(window.x); // 10
console.log(window.y); // undefined
foo(); // 30
주의할 것은, 프로퍼티로 추가되었다고 표현했는데 이는 암묵적 전역은 전역 변수가 아닌 것을 의미합니다.
따라서 변수의 호이스팅이 되지 않으며, 단순히 property이기 때문에 delete 연산자로 삭제할 수도 있습니다.
'개발 > Javascript' 카테고리의 다른 글
7. Javascript의 this와 execution context (0) | 2021.07.20 |
---|---|
6. Javascript의 strict mode (0) | 2021.07.19 |
4. Javascript의 함수 (0) | 2021.07.15 |
3. Javascript의 타입 변환 (0) | 2021.07.14 |
2. Javascript의 데이터 타입 (0) | 2021.07.14 |