1. 스코프 ?
자바스크립트의 스코프는 다른 언어들과 구별되는 특징이 있다.
var, let, const 키워드로 선언한 변수들의 스코프는 각각 다르게 동작한다.
// 매개변수로 정의한 x, y 는 함수 내부에서만 참조할 수 있다.
// 매개변수의 스코프는 함수 내부이다.
function add(x, y){
return x + y;
}
console.log(add(2, 4)); // 6
console.log(x, y); // ReferenceError : x is not defined.
변수는 선언된 위치에 의해서 유효 범위가 결정된다.
유효 범위란, 다른 곳에서 쓰인 코드가 선언한 변수를 참조할 수 있는 범위를 말한다.
스코프는 변수의 유효한 범위를 말하는 것이다.
즉, 선언된 변수가 어디까지 참조될 수 있는지를 의미한다.
스코프는 자바스크립트 엔진이 어떤 식별자를 참조해야 하는지 결정하는 식별자 결정에 사용된다.
스코프가 다르다면 이름이 같더라도 별개의 변수로 취급한다.
// 변수의 유효한 범위를 통해서 어떤 식별자를 참조해야하는지 결정한다.
var x = 10;
function funct1(){
var x = 20;
console.log(x);
}
funct1(); // 20
console.log(x); // 10
변수 x는 다른 위치에서 2개가 선언되었지만,
함수 외부에서 선언된 x 는 어디에서나 참조할 수 있는 전역 변수이고,
함수 내부에서 선언된 x는 함수 내부에서만 참조할 수 있는 지역변수이다.
두 x 가 저장된 메모리 주소는 다르다!
🧐 중복된 변수 x를 사용할 수 있는 이유?
식별자는 변수, 함수, 객체 등 어떤 값을 구별해주는 역할을 하므로유일해야 한다.
그럼에도 변수 x를 중복해서 사용할 수 있는 이유는 두 x의 스코프가 다르기 때문이다.
스코프가 다르다면 같은 이름의 변수를 중복해서 사용할 수 있다.
함수 내부의 변수 x는 함수 내부에서는 유일한 식별자여야 한다.
2. 스코프의 종류
📌 전역 스코프와 지역 스코프
"자동차 안에서 밖을 볼 수 있지만, 자동차 밖에서는 안을 볼 수 없다"
스코프를 이해하는데 좋은 문장이다.
지역 변수는 자신의 지역 스코프와 자신의 하위 스코프에 대해서 유효하므로, 참조할 수 있다.
즉, 가장 하위에 있는 스코프는 모든 상위 스코프의 변수와 함수를 참조할 수 있다.
하지만 상위 스코프에서 하위 스코프의 변수, 함수를 참조할 수 없다.
스코프 체인에서 식별자를 검색하는 방향이 하위 -> 상위 방향이기 때문이다.
var x = "global x";
var y = "global y";
function outer(){
var z = "outer z";
console.log(x); // "global x"
console.log(y); // "global y"
console.log(z); // "outer z"
function inner(){
console.log(x) // "global x"
console.log(z); // "outer z"
var x = "inner x";
console.log(x); // "inner x"
}
inner();
}
outer();
console.log(x); // "global x"
console.log(z); // ReferenceError : z is not defined.
변수 z는 전역 스코프의 하위 스코프인 outer 함수에서 정의된 변수이므로 전역 범위에서 z를 참조할 수 없다.
반면에, 가장 하위 스코프인 inner 함수는 모든 범위의 변수를 참조할 수 있다.
3. 스코프 체인
함수 안에 다른 함수를 정의하는 함수의 중첩으로 인해서 스코프가 계층적 구조를 갖는다.
이 구조를 스코프 체인이라고 한다.
자바스크립트 엔진은 스코프 체인을 통해서 참조할 식별자를 검색한다.
스코프 | 유효 범위 |
전역(최상위) 스코프 | 변수 x, y 와 outer 함수는 어디서든 참조할 수 있다 |
outer 함수 지역 스코프 | 변수 z 와 inner 함수는 outer 함수 내부에서만 참조할 수 있다. |
inner 함수 지역 스코프 | 변수 x 는 inner 함수 내부에서만 참조할 수 있다. |
변수를 참조하는 경우 자바스크립트 엔진은 변수를 참조하는 위치의 스코프에서부터 시작하여 상위 스코프 방향으로 검색 범위를 넓힌다.
따라서 상위 스코프에서 선언한 변수를 하위 스코프에서도 참조할 수 있다.
하지만, 하위 스코프에서 선언한 변수는 상위 스코프에서 참조할 수 없다.
📌 스코프 체인으로 함수 검색
function funct_1(){
console.log("this is global scope function.");
}
function funct_2(){
function funct_1(){
console.log("this is local scope function");
}
funct_1();
}
funct_2(); // "this is local scope function"
함수도 식별자에 함수 객체의 시작 주소가 할당된 것 말고는 변수와 다른 게 없다.
변수의 스코프 체인 검색과 똑같이 동작한다.
funct_2를 호출하면 funct_2의 지역 스코프 내에 있는 funct_1을 실행한다
4. 함수 레벨 스코프
스코프 체인을 만드는 함수의 중첩 구조에서,
지역은 함수 몸체 내부를 의미하고 지역은 그 지역의 스코프를 만든다.
코드 블록이 아닌 함수에 의해서만 지역 스코프가 생성된다
c, java를 비롯한 언어는 함수뿐만 아니라 if, for, while 문 등에서 사용되는 코드 블록도 하나의 지역 스코프로 인정하고, 이를 블록 레벨 스코프라고 한다.
하지만 자바스크립트에서 var 키워드로 선언된 변수는 오직 함수의 몸체만 지역 스코프로 인정한다.
if, for, while 문 등에서 사용되는 코드 블록을 지역 스코프로 인정하지 않는다는 것이다.
var x = 1;
if(true){
var x = 10;
}
console.log(x); // 10
var y = 2;
function add(){
var y = 20;
console.log(y + 10);
}
console.log(y); // 2
📌 var 키워드를 사용한 변수가 코드 블록을 사용해서 선언되었다 하더라도 지역 스코프를 생성하지 않는다.
var 키워드는 함수 레벨 스코프만 인정하기 때문에 함수 밖에서 var 키워드로 선언된 변수는 모두 전역 변수로 취급된다.
따라서, if 문 내에서 전역변수 x 값을 10으로 바꾼 것은 변수의 재할당에 해당하고 10이 출력된다.
이는 의도치 않게 전역 변수의 값을 재할당 하게 되므로 에러를 발생시킬 수 있다.
이와 다르게, let과 const는 블록 레벨 스코프를 지원한다.
let x = 10;
if(true){
let x = 20;
console.log(x); // 20
}
console.log(x); // 10
const y = 10;
if(true){
const y = 20;
console.log(y); // 20
}
console.log(y); // 10
5. 렉시컬 스코프
var x = 10;
function foo(){
var x = 20;
function moo(){
console.log(x);
}
bar();
moo(); // ?
}
function bar(){
console.log(x)
}
foo(); // 10
bar(); // 10
🧐 foo와 bar을 각각 호출하면 둘 다 전역 변수 10을 출력한다?!
함수의 상위 스코프를 결정하는 방식은 2가지가 있다.
- 동적 스코프 : 함수의 호출 위치를 기준으로 결정
- 정적(렉시컬) 스코프 : 함수의 선언 위치를 기준으로 결정
1번 방식으로 결정하면 bar의 상위 스코프는 함수 foo 와 전역 스코프이다.
2번 방식으로 결정하면 bar 의 상위 스코프는 전역 스코프가 유일하다.
자바스크립트는 렉시컬 스코프를 따른다
즉, 함수가 호출된 위치는 그 함수의 상위 스코프와 아무런 관련이 없다.
함수가 호출되는 위치와 상관없이 항상 정의된 스코프를 참조한다.
foo 안에서 bar를 호출하더라도 bar 가 정의된 시점에서의 상위 스코프에 있는 x를 참조하기 때문에 둘 다 10을 출력한다.
반면 함수 moo는 함수 foo 내부에서 정의되었고, moo의 상위 스코프는 함수 foo와 전역 스코프이다.
x를 출력하기 위해 참조할 때 바로 한 칸 상위 스코프 foo를 먼저 검색하기 때문에 20을 출력한다.
'javascript > DeepDive' 카테고리의 다른 글
[Js 딥다이브] 15. let, const (0) | 2022.11.14 |
---|---|
[Js 딥다이브] 14. 전역변수의 문제점 (0) | 2022.11.10 |
[Js 딥다이브] 12. 함수 (2) (0) | 2022.11.06 |
[Js 딥다이브] 12. 함수 (1) (1) | 2022.11.05 |
[DEEP DIVE] 11. 원시 vs 객체 (0) | 2022.11.02 |