스코프(Scope)는 총 옆에 붙어 있는 작은 망원경 같은 형태의 물체를 일컫는다. 스코프는 사격 시 목표물을 정확하게 조준하기 위해 사용하며 영어 단어의 뜻 자체도 ‘범위'를 의미한다. JavaScript에서 이야기하는 스코프 역시 무언가 제한된 범위를 잘 들여다보기 위해 사용되는 개념이라고 추측해 볼 수 있겠다.
컴퓨터 공학, 그리고 JavaScript에서의 스코프는 "변수의 유효범위"로 사용된다. 즉, 각각의 변수에 접근할 수 있는 범위가 존재한다.
중괄호(블록) 안쪽에 변수가 선언되었는지, 바깥쪽에 변수가 선언되었는지가 중요한데 이 범위를 스코프라고 부른다. 여기서 우리가 알 수 있는 규칙은 바깥쪽 스코프에서 선언한 변수는 안쪽 스코프에서 사용이 가능한 반면에 안쪽에서 선언한 변수는 바깥쪽 스코프에서는 사용할 수 없다는 것이다.
스코프의 정의와 규칙
1. 변수 접근 규칙에 따른 유효 범위 (안쪽 스코프에서 바깥쪽 스코프로는 접근할 수 있지만 반대는 불가능)
2. 중첩이 가능함.
3. 가장 바깥의 스코프는 전역 스코프(Global scope)라고 부름 (반대는 지역(Local scope))
4. 지역 변수는 전역 변수보다 더 높은 우선순위를 가짐
(지역 스코프에 선언한 변수는 지역 변수, 전역 스코프에서 선언한 변수는 전역 변수)
Q. 순서대로 콘솔에 출력되는 결과는?
let name = '김코딩';
function showName() {
let name = '박해커'; // 지역 변수
console.log(name); // 두 번째 출력
}
console.log(name); // 첫 번째 출력
showName();
console.log(name); // 세 번째 출력
A. 순서대로 김코딩, 박해커, 김코딩이다.
첫 번째 출력은 첫째 줄에서 전역 변수로 선언된 name을 가져온다. 이는, showName 함수 안쪽에 선언된 지역 변수 name은 애초에 스코프 규칙에 의해 접근할 수 없기 때문이다. 따라서 "김코딩"을 출력한다.
반면, 두 번째 출력은 함수 안에서 선언한 name이라는 지역 변수에 접근하고 있다. 변수 이름이 전역 변수와 똑같지만, 지역 변수가 전역 변수보다 우선순위가 높으므로, 지역 변수 name이 출력되는 것이다. 동일한 변수 이름으로 인해 바깥쪽 변수가 안쪽 변수에 의해 가려지는(shadow) 이러한 현상을 쉐도잉(variable shadowing)이라고 부른다. 두 번째 출력은 "박해커"이다.
세 번째 출력은 첫 번째 출력과 마찬가지로 전역 변수 name을 출력한다. 지역 변수에 선언된 name 변수는 안쪽 스코프이므로 접근이 불가능하다. 따라서 "김코딩"을 출력한다.
Q. 순서대로 콘솔에 출력되는 결과는?
let name = '김코딩';
function showName() {
name = '박해커';
console.log(name); // 두 번째 출력
}
console.log(name); // 첫 번째 출력
showName();
console.log(name); // 세 번째 출력
A. 순서대로 김코딩, 박해커, 박해커이다.
앞서 문제와는 다르게, 세 번째 줄에서 let 키워드를 사용한 선언이 존재하지 않는다. 이는, '박해커'라는 값으로 할당하고 있는 name 변수는 전역에 선언된 name 변수를 그대로 사용하겠다는 의미이다. 지역 스코프에서 새로 선언되지 않으면 그냥 같은 변수이다.
따라서 showName 함수가 실행되기 전, 처음에는 '김코딩'을 출력하고, 그 이후에는 전역변수 name의 값이 바뀌기 때문에 두 번째 및 세 번째 출력에 '박해커'가 출력된다.
스코프의 종류
먼저 스코프는 두 가지 종류가 있다.
- 블록 스코프(block scope): 중괄호를 기준으로 범위가 구분된다. (반복문 속 변수 i는 중괄호 안에서만 사용할 수 있다)
- 함수 스코프(function scope): function 키워드가 등장하는 함수 선언식 및 함수 표현식은 함수 스코프를 만든다.
여기서 한 가지 유의해야 할 점은 화살표 함수는 블록 스코프로 취급되며 함수 스코프가 아니라는 것이다.
var
블록 스코프 안에서 정의된 변수 i는 블록 범위를 벗어나는 즉시 접근할 수 없다. 따라서 결과로 ReferenceError가 나오게 된다.
그러나 var 키워드를 쓰면 for 문이 만들어낸 블록 스코프를 무시한다. 즉, block 범위를 벗어나도 (같은 function scope 에서는) 사용이 가능하다.
- var 키워드는 블록 스코프를 무시하고, 함수 스코프만 따른다. (단, 화살표 함수의 블록 스코프는 무시하지 않는다.)
(함수 스코프는 함수의 실행부터 종료까지이고, var 선언은 함수 스코프의 최상단에 선언된다. 선언 키워드 없는 선언은 최고 스코프에 선언된다. 함수 내에서 선언 키워드 없는 선언은, 함수의 실행 전까지 선언되지 않은 것으로 취급한다.) - 블록 단위로 스코프를 구분할 때가 더 예측 가능한 코드작성이 용이하므로 let 키워드 사용이 권장된다.
(보통 코드를 작성할 때 블록은 들여쓰기가 적용되고, 그 구분이 시각적으로 분명하기에 보통 블록 스코프를 기준으로 코드를 작성하고, 생각하기 마련이다. 그러나 var는 이 규칙을 무시하므로, 블록 스코프/함수 스코프에 대한 이해가 없으면 코드가 다소 혼란스러울 수 있다. ) - var 키워드를 사용하지 않는다고 해도, 함수 스코프는 let으로 선언된 변수의 접근 범위를 제한한다.
let을 써야하는 이유
위에서의 이유를 제외하고도 let을 써야하는 이유는 let 키워드는 재선언을 방지하기 때문이다.
const
const 라는 키워드도 있다. 변하지 않는 값, 곧 상수(constant)를 정의할 때에는 const를 이용한다. const는 값의 재할당이 불가능하다. 값을 재할당할 경우 TypeError를 내므로, 의도하지 않은 값의 변경을 막을 수 있다.
실습
var 키워드
let 키워드
greeting 변수가 없으므로 ReferenceError가 발생한다. (greeting 변수는 해당 block 안쪽에서만 접근이 가능하기 때문)
선언 없이 변수를 할당하지 말자. 선언 없이 변수를 할당하면, 해당 변수는 var로 선언한 전역 변수처럼 취급된다.
Strict Mode
사실 이런 것은 애초에 브라우저에서 방지해 준다면 더 안전하게 코드를 작성할 수 있을 것이다. Strict Mode는 브라우저가 보다 엄격하게 작동하도록 만들어준다. 앞서 언급한 것처럼 "선언 없는 변수 할당"의 경우도 Strict Mode는 에러로 판단한다. Strict Mode를 적용하려면, js 파일 상단에 'use strict' 라고 입력하면 된다. (따옴표 포함)
'FE > JavaScript' 카테고리의 다른 글
[JS] 클로저 (0) | 2023.03.03 |
---|---|
[JS] 변수 선언 시 주의할 점 (let, const 쓰기) ... Var bye!!!! (0) | 2023.03.03 |
[JS] 얕은 복사와 깊은 복사 (0) | 2023.03.02 |
[JS] 원시 자료형과 참조 자료형 (0) | 2023.03.02 |
[JS] 원본 배열을 변경하는 / 변경하지 않는 배열 메서드 (0) | 2023.03.01 |