스코프와 var

도현수·2022년 7월 11일
0

javascript

목록 보기
8/20

모든 식별자(변수 이름, 함수 이름, 클래스 이름 등)는 선언된 위치에 따라 다른 코드가 식별자 자신을 참조할 수 있는 유효범위가 결정된다. 이를 스코프(scope)라고 한다.

스코프(scope): 식별자가 유효한 범위

var test1 = 1;

if (true) {
	var test2 = 2;
    ir (true) {
    	var test3 = 3;
        }
   }

function test(){
	var test4 = 4;}

console.log(test1); // 1
console.log(test2); // 2
console.log(test3); // 3
console.log(test4); // 레퍼런스 에러

스코프에 따라 console.log가 참조할 수 있을지 없을지가 달라진다.

다음 또한 생각해본다.

var test1 = 123;

function test(){
	var test1 = 456;
    console.log(test1);
    }
test();

console.log(test1);

위의 경우 test()안의 console.log(test1)과 맨 밑의 console.log(test1) 결과값은 어떻게 될까? 자바스크립트 엔진은 test1이라는 이름이 같은 변수 중 어떤 변수를 참조해야할지를 결정해야하는데 이를 식별자 결정(identifier resolution)이라고 한다. 엔진은 코드를 실행할 때 코드의 문맥(어디서 실행되며 주변에 어떤 코드가 있는가)를 고려한다. 코드의 문맥에 따라 같은 코드 (위 경우에서는 console.log(test1)) 또한 다른 결과를 만들어낸다.
가장 바깥에서 선언된 test1은 어디서든 참조될 수 있다. 하지만 test()에서 선언된 test1은 오직 함수의 코드블록 내에서만 참조될 수 있다. 즉, 이 둘은 식별자 이름은 같지만 스코프가 다른 별개의 변수이다. 따라서 함수 내부의 console.log(test1)은 456, 외부의 console.log(test1)에는 123을 반환한다.

스코프의 종류

스코프는 크게 전역 스코프(global scope)와 지역 스코프(local scope)로 나뉜다.

  • 전역 : 코드의 가장 바깥쪽
  • 지역 : 함수 몸의 내부

변수는 선언된 위치에 따라 스코프가 결정된다. 전역에서 선언된 변수는 전역 스코프를 가지고 어디서든 참조될 수 있으며 이를 전역 변수라고 부른다. 지역에서 선언된 변수는 지역 스코프를 가지고 지역스코프 내부에서만 참조될 수 있으며 이를 지역 변수라고 부른다.

var test1 = 123;

function test(){
	var test1 = 456;
	var test2 = 789;
    console.log(test1); // 456
    console.log(test2); //789
    
    function test2(){
    	var test2 = 153;
        console.log(test2); // 153
        console.log(test1); // 456
        }
    }
    
    
console.log(test1); // 123
console.log(test2); // 레퍼런스 에러

전역 스코프

전역이란 코드의 가장 바깥을 말한다. 전역은 전역 스코프를 만들며 여기서 변수를 선언하면 이는 곧 전역 변수가 된다. 전역 변수는 전역 스코프를 가지고 있기 때문에 코드의 어디서든 참조가 가능하다. 위의 코드에서 가장 바깥의 test1은 전역 변수이다.

지역 스코프

지역은 함수 몸체의 내부를 의미한다. 지역은 지역 스코프를 만들며 여기서 변수를 선언하면 지역 변수가 된다. 지역 변수는 자신의 지역 스코프와 하위 지역 스코프에서 유효하다. 위의 코드에서 test()내부의 console.log(test1)과 console.log(test2)를 보면 둘 다 함수 내부의 변수를 참조했다. 또한 test2()내부의 console.log(test1)을 보았을 때, 하위 지역 스코프에서도 유효함을 알 수 있다.
그런데 test2()의 console.log(test2)와 test1()의 console.log(test1)을 다시 보자. 둘 다 상위 스코프의 변수가 아니라 자신이 가진 스코프의 변수를 참조했다. 이를 통해 하위 지역 스코프는 해당 스코프에서 상위 지역 스코프보다 우선권을 가짐을 알 수 있다.

스코프 체인

함수가 다른 함수의 몸체 내부에서 정의되는 것을 함수의 중첩 이라고 한다. 이 때, 내부에서 정의된 함수를 중첩 함수, 중첩함수를 정의한 함수를 외부함수라고 부른다. 여기서 더 생각해보면, 함수가 중첩되었다는 것은 함수가 가진 스코프 또한 중첩되었다는 것을 의미한다. 즉, 스코프 또한 계층 구조를 가진다. 여기서 외부함수의 지역 스코프를 중첩 함수의 지역 스코프의 '상위 스코프'라고 부른다. 위의 코드에서 test1()의 지역 스코프는 test2()의 지역 스코프의 상위 스코프이고 모든 지역 스코프에 있어서 최상위 스코프는 전역 스코프이다. 변수를 참조할 때 자바스크립트 엔진은 해당 코드가 속한 스코프에서 시작해 점점 상위 스코프로 올라오며 변수를 검색한다. 이와 같은 과정을 거쳐 상위 스코프의 변수를 하위 스코프에서 사용할 수 있는 것이다. 그리고 이는 곧 상위 스코프에서는 하위 스코프의 변수를 참조할 수 없음을 의미하기도 한다.

var test1 = 123;

function test(){
	var test1 = 456;
	var test2 = 789;
    console.log(test1); // 456
    console.log(test2); // 789
    }

console.log(test1); // 123을 출력한다. 하위 스코프인 test()의 test1을 참조하지 않는다.
    

블록 레벨 스코프와 함수 레벨 스코프

지역 스코프에서 선언된 변수는 코드 블록(if, for, while, function 등) 안에서만 참조가 가능하다. 이를 블록 레벨 스코프라고 한다. 중괄호를 기준으로 범위를 구분한다.

if (true) {
	let test = 1;
    console.log(test); // 1
    }
console.log(test) // 레퍼런스 에러

그러나 var로 선언된 변수의 경우, 오로지 함수의 코드블록(즉, 함수의 몸체 내부)만을 지역 스코프로 인정한다. 다시 말하자면, 함수가 아닌 다른 코드 블록에서는 var는 전역 변수이다. 이를 함수 레벨 스코프 라고 한다. 다음을 보자

var test = 123;

if (true) {
	var test = 456;
    console.log(test); //456
    }
    
console.log(test); //456

블록 레벨 스코프에서는 if 안의 test는 오직 {} 안에서만 참조될 수 있어야 한다. 그러나 var는 function만을 지역 변수로 인정하기 때문에, if에서는 var가 전역 변수처럼 작용한다. 그리고 var는 재선언이 가능한 특징이 있기 때문에 맨 밑의 console.log(test)에서 test가 456으로 재선언 된 것이 되어 456을 반환하는 것이다.

렉시컬 스코프(lexical scope)

렉시컬 스코프는 함수를 어디서 정의했는지, 즉, 문맥을 따지며 상위 스코프를 결정하는 것을 말한다. 함수의 상위 스코프는 언제나 자신이 정의된 스코프이다. 다음의 코드를 보자

var test = 1

function test1() {
	var test = 123;
    function test()
    }

function test2() {
	console.log(test);
    }

test1(); //
test2();

만약 함수의 상위 스코프가 정해져 있지 않고, 함수를 호출할 때마다 바뀐다고 가정해보자. 그렇다면 위에서 test1()은 123, test2()은 1을 반환할 것이다. 이 경우 함수가 호출될 때마다 함수의 상위 스코프를 바꿔야 한다. 이를 동적 스코프라고 부른다.
자바스크립트에서는 렉시컬 스코프를 따르기 때문에 test1()과 test2()모두 1을 반환한다. test2 함수가 정의되었을 때, test2는 자신이 정의된 스코프를 기억하고 호출 되었을 때 상위 스코프로 사용한다.

window 객체

window객체는 브라우저의 창 전체를 의미하며 전역 변수 전체를 객체로 가지고 있다. 함수를 선언하거나 var로 전역 변수를 선언하면 window에서 객체로 조회가 가능하다.

var test = 123;
console.log(window.test); 123

변수선언에서 주의할 점

전역 변수의 사용을 최소화할 것

'모든 변수를 맨 바깥, 그러니까 전역으로 빼면 스코프를 고려하지 않아도 되니 편하겠는데?'라는 생각을 할 수도 있다. 그러나 애플리케이션을 만들 때 자바스크립트를 길게 작성하고 다른 사람이 만든 함수와 로직이 들어오면서 모든 코드에서 전역 변수를 참조하고 변경하는 '암묵적 결합'이 발생할 수 있다. 이렇듯 변경될 위험성과 코드가 길어졌을 때 전역 변수의 값을 찾기 힘들어진다. 즉, 가독성 또한 나빠진다.

let과 const를 주로 사용할 것

var로 선언한 변수는 함수 레벨 스코프를 가지고 재선언 또한 가능하다. 따라서 let 과 const를 사용하는 것이 바람직하다.

변수 키워드 없이 변수 선언을 하지 말 것

function test() {
	test1 = 123;
    }
test();
console.log(test1) ; 123
console.log(window.test1) ; 123

변수 키워드 없이 변수 선언을 했을 경우 마치 var를 이용한 전역 변수 선언처럼 취급된다. 이는 'stric mode'를 통해 막을 수 있으며 "선언 없는 변수 할당"의 경우 strict Mode는 에러로 판단한다. 이를 적용하려면, js 파일 상단에 'use strict' 라고 입력하면 된다.

0개의 댓글