Scope는 무엇일까요? 원래 영어 단어 자체는 범위라는 의미를 가집니다. 자바스크립트에서 이야기하는 스코프 역시, 무언가 제한된 범위를 잘 들여다보기 위해 사용되는 개념이라고 추측해볼 수 있을거예요.
컴퓨터 공학, 그리고 자바스크립트에서의 스코프도 "범위"의 의미를 가지고 있습니다. 더 자세하게는 "변수의 유효범위"로 사용됩니다. 이번 유닛을 진행하면서 Scope의 종류와 각 선언 키워드 (let, const)를 어떻게 사용하고 무엇을 권장하는지 알아봅시다.
let username = 'kimcoding';
if (username) {
let message = `Hello, ${username}!`;
console.log(message); // "Hello, kimcoding!"
}
console.log(message); // ReferenceError
해설
4번째 줄에서 message를 출력할 때는, 3번째 줄의 username을 바깥 스코프에서 가져왔으므로 정상적으로 출력됩니다.
그러나, 6번째 줄에서는 message라는 변수 자체가 안쪽 스코프에 선언되어 있으므로, 바깥쪽에서는 접근할 수 없습니다.
콘솔에 순서대로 출력되는 결과는 무엇인가요?
let greeting = 'Hello';
function greetSomeone() {
let firstName = 'Josh';
return greeting + ' ' + firstName;
}
console.log(greetSomeone()); // 'Hello Josh'
console.log(firstName); // ReferenceError
greeting 변수는 바깥 스코프에 정의되어 있으므로, 함수 안쪽에서 사용할 수 있습니다. 따라서 greeting 변수와 firstName 변수의 조합의 의해 'Hello Josh' 문자열이 출력됩니다.
반면에, firstName 변수는 안쪽 스코프에 정의되어 있으므로 바깥쪽에서는 접근이 불가능합니다. 따라서 ReferenceError를 냅니다.
첫번째 규칙 : 안쪽 스코프에서 바깥쪽 스코프로는 접근 가능
하지만 바깥쪽 스코프에서 안쪽 스코프로는 불가능
- 위 예시처럼 변수에 접근할 수 있는 범위가 존재합니다. 중괄호(블록) 안쪽에 변수가 선언되었는가, 바깥쪽에 변수가 선언되었는가가 중요합니다. 이 범위를 우리는 스코프라고 부릅니다.
- 범위가 중괄호(블록) 또는 함수에 의해 나뉘어지고, 그 범위를 스코프라고 부른다고 했습니다.
여기서 우리가 알 수 있는 규칙은 바로 다음과 같습니다.- 바깥쪽 스코프에서 선언한 변수는 안쪽 스코프에서 사용 가능합니다.
반면에, 안쪽에서 선언한 변수는 바깥쪽 스코프에서는 사용할 수 없습니다.
두번째 규칙 : 스코프는 중첩이 가능하다.
- 특별히 가장 바깥쪽의 스코프는 전역 스코프(Global Scope)라고 부른다.
- 전역의 반대말은 지역(local)으로 전역이 아닌 다른 스코프는 전부 지역 스코프(local scope)라 한다.
let name = '김코딩'; // 전역 변수
function showName() {
let name = '박해커'; // 지역 변수
}
let name = '김코딩';
function showName() {
let name = '박해커'; // 지역 변수
console.log(name); // 두번째 출력 : 박해커
}
console.log(name); // 첫번째 출력 : 김코딩
showName();
console.log(name); // 세번째 출력 : 김코딩
- 첫번째 출력은 첫째 줄에서 전역 변수로 선언된
name
을 져온다showName
함수 안쪽에 선언된 지역 변수name
은 애초에 스코프 규칙에 의해 접근할 수 없기 때문에 따라서 "김코딩"을 출력- 두번째 출력은 함수 안에 선언한
name
이라는 지역 변수에 접근을 하고있다.- 변수 이름이 전역 변수와 같지만, 지역 변수가 전역 변수보다 우선순위가 높으므로, 지역변수
name
이 출력이 된다.- 동일한 변수 이름으로 인해 바깥쪽 변수가 안쪽 변수에 의해 가려지는(shadow) 이러한 현상을 쉐도잉(variable shadowing)이라고 부른다.
- 그러므로 두번째 출력은 "박해커" 이다.
- 세번째 출력은 첫번째 출력과 마찬가지로 전역 변수
name
을 출력한다.- 지역 변수에 선언된
name
변수는 안쪽 스코프이므로 접근이 불가능- 따라서 "김코딩"을 출력한다.
let name = '김코딩';
function showName() {
name = '박해커';
console.log(name); // 두번째 출력 : 박해커
}
console.log(name); // 첫번째 출력 : 김코딩
showName();
console.log(name); // 세번째 출력 : 박해커
showName
함수가 실행되기 전, 처음에 '김코딩'을 출력하고if (true) { console.log('i am in the block') } for (let i = 0; i < 10; i++) { console.log(i); // 변수 i는 중괄호 안에서만 사용 가능 } { console.log('it works') }
function getName(user) { return user.name; } let getAge = function (user) { return user.age; }
유의할점
let getAge = user => { return user.age; } // 같은 함수여도, 화살표 함수를 사용하면 블록 스코프 취급됨 // let getAge = function (user) { return user.age; } // function 키워드를 사용하면 함수 스코프다.
for (let i=0; i<10; i++) { console.log(i); } console.log('final i :', i); //블록 스코프 안에서 정의된 변수 i는 블록 범위를 벗어나는 즉시 접근할 수 없다. //결과로는 ReferenceError가 나오게 됨. -------------------------------------------------------- for(var i =0; i<5; i++) { console.log(i); } console.log('final i:',i); //앞서 보았던 코드와 동일하나, 변수 선언을 let 대신 var를 이용했다. //이에 대한 결과는 5이다. 왜 그럴까? //결론부터 말하면, var 키워드는 for 문이 만들어낸 블록 스코프를 무시한다.
- var 키워드로 정의한 변수는 블록 스코프를 무시하고, 함수 스코프만 따른다.
- 그러나, 모든 블록 스코프를 무시하는 건 아님. 화살표 함수의 블록 스코프는 무시하지 않는다.
- 함수 스코프는 함수의 실행부터 종료까지이고, var 선언은 함수 스코프의 최상단에 선언된다.
- 선언 키워드 없는 선언은 최고 스코프에 선언된다.
- 함수 내에서 선언 키워드 없는 선언은, 함수의 실행 전까지 선언되지 않 은 것으로 취급한다.
- 보통 코드를 작성할 때 블록은 들여쓰기가 적용되고, 그 구분이 시각적으 로 분명하다.
- 따라서 많은 사람들은 블록 스코프를 기준으로 코드를 작성하고, 생각하기 마련이다.
- 그러나 var는 이 규칙을 무시하므로, 코드를 작성하는 사람이 블록 스코 프/함수 스코프에 대한 이해가 없으면 코드가 다소 혼란스러울 수 있다.
- 블록 단위로 스코프를 구분했을 때 훨씬 더 예측 가능한 코드를 작성할 수 있으므로 let 키워드의 사용을 권장한다.
- var을 사용하지 않는다 해도, 함수 스코프는 let으로 선언된 변수의 접 근 범위를 제한한다.
- var키워드 보다 let키워드가 더 안전하다.
- 왜냐하면 var 키워드는 재선언을 해도 아무런 에러도 내지 않지만, let 키워드는 재선언을 방지한다.
- 변하지 않는 값, 곧 상수(constant)를 정의할 때에 사용
- let 키워드와 동일하게, 블록 스코프를 따른다.
- 값의 변경을 최소화하여 보다 안전한 프로그램을 만들 수 있다.
- 값을 새롭게 할당할 일이 없다면, const 키워드의 사용이 권장된다.
- const는 값의 재할당이 불가능하다.
- 값을 재할당할 경우 TypeError를 내므로,
의도하지 않은 값의 변경을 막을 수 있다.
let const var 유효 범위 블록 스코프 및 함수 스코프 블록 스코프 및 함수 스코프 함수 스코프 값 재할당 가능 불가능 가능 재선언 불가능 불가능 가능
- window객체는 사실 브라우저의 창(window)을 의미하는 객체이지만, 이와 별개로 전역 영역을 담고 있기도 한다.
- 함수 선언식으로 함수를 선언하거나, var로 전역 변수를 만들면, window 객체에서도 동일한 값을 찾을 수가 있다.
- 전역 변수는 가장 바깥 스코프에 정의한 변수이다.
따라서, 어디서든 접근이 가능하다.- 보통 애플리케이션을 만들 때에는, 내가 직접 작성하지 않은 수많은 다른 함수와 로직이 포함된다.
- 너도나도 똑같은 이름으로 전역 변수를 선언하려고 한다면 분명 문제가 발생할 것이다.
- 이를 side effect라고 한다.
- 전역 변수를 최소화하는 것은 side effect를 줄이는 좋은 방법이다.
- var 키워드는 블록 스코프를 무시한다.
- 또한 재선언을 해도 에러를 내지 않는다.
- 따라서, let과 const를 주로 사용해라....
- 전역 변수를 var로 선언하는 것은 브라우저의 내장 기능을 사용하지 못하게 만들 수도 있다.
선언 없이 변수를 할당하지 마라..
선언 없이 변수를 할당하면, 변수는 var로 선언한 전역 변수처럼 취급이 된다.
function showAge(){ age = 90; //여기서 age는 전역 변수 취급을 받는다. console.log(age); } showAge(); console.log(age); //90 console.log(window.age); //90
age를 선언한 적이 없으나, 값을 할당하면서 마치 var로 선언된 전역 변수처럼 작동한다.