JS 스코프( Scope ) & const var let

16677 단어 TILjsTIL

Achievement Goals

  • JavaScript의 Scope의 의미와 적용 범위를 이해할 수 있다
  • JavaScript의 Scope 주요 규칙을 이해할 수 있다
    • 중첩 규칙
    • block scope(block-level scope) vs. function scope(function-level scope)
    • let, const, var의 차이
    • 전역 변수와 전역 객체의 의미

Scope는 무엇일까?

원래 영어에서는 "범위"라는 의미를 가진다. 아마도 자바스크립트에서도 무언가 제한된 범위에서 사용되는 개념이 아닐까 생각해볼 수 있다.

컴퓨터공학, 그리고 자바스크립트에서의 스코프도 "범위"의 의미를 가지고 있다. 다만, 조금 더 좁은 의미로 "변수의 유효범위"로 사용된다.

스코프는 변수의 접근성과 생존 기간을 제어한다. 예시를 통해 확인해보자!!

const f1 = function(){
	const a = 1; // 함수 스코프
    const b = 2;
    console.log(a + b) // 3
    return a + b
}

let a = 20; // a는 전역 변수
console.log(b) // error: b is not defined
// 함수에서 선언된 변수는 함수가 종료됨과 동시에 사라진다.
// 함수 밖에서는 b는 존재하지 않는 변수이다. 

f1() // 3

예시에서 알수 있듯이, 스코프는 이름이 충돌하는 문제를 덜어주고, 자동으로 메모리를 관리한다.


Scope의 종류

JS의 스코프 즉, 유효 범위는 3가지 종류가 있다.

  • 자바스크립트의 유효 범위
    • 전역 스코프
    • 함수 스코프
    • 블록 스코프 ( => es6에서 등장! )
  • 전역 스코프
    • 스크립트의 어디서든 접근이 가능하기 때문에 사용이 쉽다.
    • 타인과 협업, 라이브러리 사용시 충돌이 가능성이 있다.
      => 다른 개발자와 영역을 나눠서 작업을 하게 된다면, 서로 약속을 하지않은 이상, 변수명이나 함수명이 겹칠 우려가 있다. 그렇기 때문에, 전역 스코프에서 변수나 함수를 선언하는 것은 자제해야 한다.
  • 함수 스코프

    • 함수 내부에서 정의된 변수와 매개변수는 함수 외부에서 접근할 수 없다.
    • 함수 내부에서 정의된 변수라면 함수의 어느 부분에서도 접근할 수 있다.

    이제 개념을 응용해보자!!

const func = function(){
    let a= 1;
    let b= 2;

	const func2 = function(){
    	let b = 5;
        let c = 6;
        a = a + b + c; // 1 + 5  + 6
        console.log(a) // 1
    }
    func2(); // 12
}
func();

=> func2에서 a라는 변수를 선언한 적이 없지만, 이 함수는 func라는 부모함수 안에서 선언된 함수이기 때문에, 부모 함수에서 선언된 변수들을 탐색할 수가 있다.
이 것을 중첩 규칙이라고 하는데, 좀 더 자세하게 알아보자!!

중첩 규칙 ( = scope chain )

  • 내부 함수에서는 외부 함수의 변수에 접근 가능하지만 외부 함수에서는 내부 함수의 변수에 접근할 수 없다.

바로 그게 코드에서 enemy 값을 찾지 못하는 이유다. 그리고 모든 함수들은 전역 객체에 접근할 수 있다.

inner 함수는 name 변수를 찾기 위해 먼저 자기 자신의 스코프에서 찾고, 없으면 한 단계 올라가 outer 스코프에서 찾고, 없으면 다시 올라가 결국 전역 스코프에서 찾는다. 다행히 전역 스코프에서 name 변수를 찾아서 '조코딩'라는 값을 얻었다. 만약 전역 스코프에도 없다면 변수를 찾지 못하였다는 에러가 발생한다. 이렇게 꼬리를 물고 계속 범위를 넓히면서 찾는 관계를 중첩규칙 혹은 스코프 체인이라고 부른다.

  • 블록 스코프
    중괄호 { ... } 안에서만 접근이 가능하다. 블록 내부에 정의된 변수는 블록의 실행이 끝나면 해제된다.
if(true){
	var value = "hello";
}
console.log(value); // "hello"

if(true){
	let value = "world"; // let, const는 중괄호 안에서만 유효 범위를 갖는다
}
console.log(value); // "hello"

여기까지 간단하게 스코프의 정의와 종류에 대해서 알아봤는데, 이제부터는 문제를 통해서 좀 더 자세하게 살펴보자!!

local scope와 global scope

에러 메세지를 보면, firstName은 정의된 적이 없다고 뜬다. 그러나, firstName은 greetSomeone()안에서 정의가 됐다. 왜 이런 현상이 일어날까??

즉, let으로 선언된 firstName은 { ... } 안에서 선언(declare)됐기 때문에, 중괄호 밖에서는 파괴된다.

정리해보자면, 우리는 Q1을 통해서 다음과 같은 사실들을 확인할 수 있다.

그런데, 마지막 사실을 살짝 와닿지 않는다. 이 부분은 Q2를 통해서 더 확실하게 이해해보자!!

지역변수는 항상 전역변수보다 우선순위가 높다. 그래서, 기존에 전역변수로 Richard가 할당되어 있지만, showName()에서는 Jack이 찍히는 거다.

이제 Q2를 좀 더 변형해보자. showName()안에 있는 name에 let이라는 선언문을 쓰지 않으면, 어떻게 될까??

마지막에 찍히는 name 값이 달라지는 것을 확인할 수 있다.

function scope vs block scope

레퍼런스 에러가 떴다는 것은 i 값을 찾을 수 없다는 의미이다. 이유인 즉, i는 block 안에서 선언된 block scope이기 때문이다.

다시 말해서, 선언된 block(중괄호)을 벗어나는 범위에서는 사용할 수 없다.

그렇다면, for문 안에 i를 let이 아니라 var로 선언해도 동일한 결과가 나올까?? Q5를 확인해보자!

=> let과는 다르게 레퍼런스에러가 뜨는 것이 아닌 5가 나온다. 즉, 값을 찾았다는 뜻인데, 어떻게 이게 가능할까??

Q5에서는 i는 function 구분 없이 블록(=중괄호) 차원의 구분만 보인다. 그렇기 때문에, 블록 밖에서도 i는 여전히 존재한다.

  • var보다 let을 사용해야 하는 이유

    • 어떻게 보면, var이 편하게 보일 수도 있는데, Q4와 같이 한정된 블록 레벨안에서만 존재해야지 개발자가 코드를 짤 때, 변수들의 범위가 눈에 확 들어온다.

    • 또한, var의 경우는 예상 못하게 i를 나중에 재사용할 위험성도 내재하고 있기 때문에, var보다는 let을 쓰는 것을 권장한다.

    • var를 사용한 재선언시, 아무런 에러도 내지 않는다. 하지만, let같은 경우, 재선언시에 에러를 내주기 때문에, 이런 실수를 방지하기 위해서라도 let사용이 요구된다.

    • var로 선언된 변수는 전역 객체와 연결된다. 이 말은 다소 헷갈리니 좀 더 알아보자! 최상위 객체인 window 객체 안에는 전역 영역에서 사용할 수 있는 굉장히 다양한 key와 value들이 들어있다.
      그리고 var가 window 객체와 연결된다는 의미는 다음 실험을 통해 확인해보자. myName은 var로 선언이 되있다. 그리고나서, window안에 key와 value값들을 쭉~~ 찾아보면, var로 선언한 myName을 찾을 수 있다. 그러나, let은 이런 현상이 발생하지 않는다. 초반에 언급했듯이, 개발자는 전역변수를 선언하는 것을 극도로 자제해야한다. 전역에서 무슨 변수들이 선언되있는지도 모르고, 다른 개발자가 동일한 이름으로 전역에서 사용할 시에, 코드가 꼬이기 때문이다. 그런 이유에서라도, var보다는 let을 사용해야 한다.

let, const, var의 차이

이제 const에 대해서 알아보자!
const 역시 let과 같은 유효범위를 갖으면, window 객체와 연결되지도 않는다.

다만, 차이가 있다면, 한번 선언과 할당이 이뤄지면, 재할당이 불가능하다.

이 3가지의 선언 키워드의 특징들을 도표로 정리해보면 다음과 같다.

let, const, var의 결론

var는 JS 문법에 사라져도 아무런 지장이 없는 애라고 한다.
하지만, 과거에 작성 됐던 레거시 코드들을 분석하기 위해서라도, 이 친구 특성 정도는 알아둬야 한다.

ES6에서는 기본적으로 const의 사용을 권장한다.
교육 엔지니어님께서도 "프로그래밍을 오래할 수록 let보다 const를 쓸 일이 많다"고 하신다.

const로 선언을 해야, 실수로 값을 바꾸는 일을 막을 수있다. 왜냐면, 값을 바꾸면 에러가 나니까...

* 정리하자면,
기본적으로 const로 선언을 하고, 혹시나 나중에 값을 바꿀 일이 생기면, let 으로 선언을 바꾸자!!!

const 사용법에 대해서 다시 한 번 짚고 넘어가자.

  • const 에는 = (할당)을 한 번 밖에 못 쓴다.
  • 대신에 이런 거는 가능하다.
const b = { name : 'zerocho' };
b.name = 'gilcho'; 

=> 이렇게 b를 대입했을 때, 그 안에 다른 것을 바꾸는 것은 가능하다.
즉, b에다가 =을 딱 한 번만 붙일 수 있지만, 그 b 안의 하위 값들은 =을 사용할 수 있다.

  • 단, const b; 이 경우는 =을 안 붙였어도, = 사용이 불가능하다.

주의사항 - 'use strict'

선언없이 변수를 할당하면, 그것 역시 전역변수가 되서 window 객체와 연결된다.
( JS는 참 문제가 많은 언어다... )

이러한 문제는 타입스크립트를 사용하면, 자연스럽게 해결되지만, 자바스크립트 레벨에서 실수를 방지하고 싶다면, 'use strict'를 JS 파일 최상단에 사용하면 된다!! 그러나, 구글 콘솔에서는 사용이 불가능하고 파일 레벨에서만 사용이 가능하다는 점 유념해두자!!

여기에 대한 자세한 설명은 내가 과거에 썼던 글을 참고해보자!
script의 async 와 defer, 'use strict'

정적 스코프 (lexical scoping)

맨 위에서 Scope의 개념에 대해서 언급할 때 마지막 줄에
이런 내용이 있었다.

그냥 그런가보다 하는데, 솔직히 무슨 말인지 이해가 안된다. 내 생각에는 선언할 때가 아니라 함수를 호출될 때, scope를 가지는게 아닌가 싶었다.

나를 매우 혼란스럽게 한 예시를 하나 살펴보자!!

ex 1)

내 생각에는 wrapper()에서 name을 '김코딩'으로 재선언과 재할당했기 때문에, '김코딩'이 찍힐 것이라고 생각했다.

  • Why??

함수를 처음 선언하는 순간, 함수 내부의 변수는 자기 스코프로부터 가장 가까운 곳(상위 범위에서)에 있는 변수를 계속 참조하게 된다.

wrapper( )가 부모 함수라면 몰라도, 지금 log( )의 부모 함수는 전역 객체인 window이기 때문에 '조코딩'을 찾는다.

즉, wrapper()안에서 log()가 선언이 됐다면,
ex 2)

정답은 김코딩이 됐을 것이다.

이런 것을 lexical scoping 이라고 합니다.

무슨 짓을 해도 log 함수가 한 번 선언된 이상, 전역변수를 가리키게 되어있는 name 변수가 다른 걸 가리키게 할 수 없다. 유일한 방법은 아까처럼 전역변수를 다른 값으로 바꾸는 것이다.

전역변수를 삼가해야 하는 이유와 대안

그러나,

전역변수를 만드는 일은 지양하라고 했는데, 그 이유는 변수가 겹칠 수 있기 때문이다. JS 프로젝트를 혼자만 개발하는 게 아니라, 여러 명과 협동도 하고, 다른 사람의 라이브러리(자바스크립트 코드 모음)를 사용하는 일도 많다. 그런데 전역변수를 사용하다보면, 우연의 일치로 인해 같은 변수 이름을 사용해서 이전에 있던 변수를 덮어쓰는 불상사가 발생할 수 있다.

간단한 해결 방법은 전역 변수 대신 1) 한 번 함수 안에 넣어 지역변수로 만드는 것이다. 아니면 2) 객체 안의 속성으로 만들 수도 있다.

var obj = {
  x: 'local',
  y: function() {
    alert(this.x);
  }
}

위 처럼 하면, obj를 통째로 덮어쓰지 않는 이상, obj.x, obj.y() 이렇게 접근해야 하기 때문에 다른 사람과 섞일 염려가 없다. 전역변수를 obj 하나로 최소화해서 변수가 겹칠 우려도 최소화하는 거다.

이런 방법을 네임스페이스를 만든다고 표현한다. obj라는 고유 네임스페이스를 만들어서 겹치지 않게 하는 것이다. 현재, naver는 jindo, facebook은 FB, jquery는 jQuery(또는 $)같은 대부분의 라이브러리가 네임스페이스를 사용하고 있다.

네임스페이스 대박!!


자료 출처

오늘 TIL은 코드스테이츠에서 학습한 내용과 개인적으로 보충 학습한 내용들을 바탕으로 작성됐다.

함수의 범위(scope)

자바스크립트 3. 데이터타입, data types, let vs var

"const"도 수정 가능하다

실행 컨텍스트와 클로저

좋은 웹페이지 즐겨찾기