본문 바로가기

Frontend study/JavaScript

deep JavaScript - 1. 변수와 스코프

✔  변수를 정의할 때 사용하는 키워드인 let, const 그리고 var의 차이점과

     변수의 스코프에 대해서 정리합니다. 👍

 

  변수와 스코프 

01. ES2015(ES6) 이전의 변수 정의

 

 변수를 선언하는 방법으로 요즘은 let, const 키워드를 주로 사용하며 권장하고 있다. 

 let, const가 등장하기 전에는 변수를 선언할 때 var 키워드를 사용했다. 

 

 var 키워드를 사용하지는 않는 이유는 단순히 let, const가 편해서라기 보단(물론 편해서

사용하는 것도 어느 정도 맞지만) var 키워드로 선언한 변수 혹은 함수들이 가진 문제점들이

있기 때문이다. 

 

var와 let, const는 어떤 차이가 있고 왜 var를 사용하지 않는 게 좋은지 살펴보자!

 

02. var와 let, const의 차이 

 

 01) var 키워드(var 키워드를 지양해야 하는 이유)

   - 함수 레벨 스코프이다. 

    → 함수 외부에 선언된 모든 변수는 전역 변수이다. 이는 전역 변수를 남발할 가능성을 높인다.

   

   - var 키워드의 생략을 허용

    → 암묵적 전역 변수를 만들 가능성이 있다. 

 

   - 한번 선언된 변수로 재선언할 수 있다.

    → 의도치 않은 변수 값이 변경될 우려가 있다.

 

   - 선언하기 이전에 사용이 가능하다 (변수 호이스팅)

     → 선언하기 전에 사용하면 원래 에러가 생겨야 한다. 그러나 var 키워드로 만든 변수는 에러를

         내지 않는다.

 

  💡 이런 이유로 var 키워드를 사용하는 것보다 아래에 설명할 let, const 사용을 권하는 이유이다.

 

 

 02) let, const 키워드

   - 블록 레벨 스코프이다.   

    → 블록 내부에 선언된 변수는 블록 내부에서만 유효하며 외부 스코프에서는 접근할 수 없다.                                               

   - 한번 선언된 변수로 "재선언"할 수 없다.           

    → 한번 선언된 변수로 다시 만들면 에러를 발생시키기 때문에 의도치 않은 변수 값이 변경될 

        일이 없다. 

 

 

//var
{

var userName = 'Mike';
console.log(userName); //'Mike'


var userName = 'Janne';
console.log(userName); //'Janne'
}
 
//let
{
let userName = 'Mike';
console.log(userName); //'Mike'


let userName = 'Janne';
console.log(userName); //error 발생
}

 

 

03.  변수의 스코프

 01) 변수의 스코프란 

   :  "범위"라는 뜻을 가진 스코프는 어떠한 변수에 접근 가능한 유효한 범위이자 식별자(= 참조 대상)를

     찾아내기 위한 규칙을 의미한다. 

        

 02) 스코프의 특징    

   자바스크립트의 var 키워드는 함수 레벨 스코프(Function-level scope)를 따르는 반면에, let과

   const 키워드는 블록 레벨 스코프(Block-level scope)를 따른다. 

 

  ① 함수 레벨 스코프(= var로 선언한 변수의 스코프)

 

   ES6에 let, const 가 도입되기 전에 자바스크립트는 함수 레벨 스코프(Function-level scope)였다.

   함수 레벨 스코프란 함수의 영역만을 스코프로 인정한다. 

 

   함수 내부에서 만들어진 변수는 함수 내부에서만 유효하며 함수 외부에서는 접근할 수 없는 변수의

   유효 범위를 말한다.  

 

   함수 내부에서 선언한 변수를 지역 변수라 하고, 외부에서 선언된 모든 변수는 전역 변수이다.  

          

 

//함수 스코프 
var name = 'Mike';
 
function showInfo() {
    var name = 'James';
    console.log(name);
}
 
showInfo(); //James
console.log(name//'Mike'

 

 

 

② 블록 레베 스코프(= let, const로 선언한 변수의 스코프)

  ES6에 새롭게 도입된 let, const 키워드는 블록 레벨 스코프(Block-level scope)이다. 

  (여기서 블록은 함수, if/while 문, for 문, try/catch 문의 중괄호({}) 내부를 말한다.)

 

  블록 스코프는 블록 내부에서 선언된 변수는 코드 블록 내에서만 유효하며 코드 블록 외부에서는

  접근할 수 없는 변수의 유효 범위를 말한다. 

 

  코드 블록 내부에 선언한 변수는 지역 변수라 하고 코드 블록 외부에서 선언된 모든 변수는 전역

  변수라 한다. 

 

 

let num1 = 12//전역 변수
 
function print(a) {
    let num2 = a * 2;
    console.log(num1) //num1 참조가능
    return num2
}
 
let result = print(num1);
console.log(result)
 
console.log(num2) //num2 참조 불가능

 

 

04. 스코프 구분

  스코프는 전역 스코프(Global Scope), 지역 스코프(Local Scope)로 나뉜다. 

  지역 스코프는 var 키워드의 경우 함수 단위이고 let, const의 경우 블록 단위이다. 

  

  

  01) 전역 스코프 

    전역 스코프는 코드 블록 혹은 함수 외부에 선언된 변수(=전역 변수)의 스코프를 의미하며 블록 혹은

    함수 내부를 포함한 어느 범위에서든 접근이 가능하다.

 

 

// var
  var val = 'global';
  function func() {
      console.log(val); //undefined
      var val = 'local';
      console.log(val); //local
  }
  func();
  console.log(val); //global
 
================================================
 
// let
  let userName = 'James'
 
  function func() {
      console.log(userName); //ReferenceError
      let userName = 'Mike';
      console.log(userName); //Mike
  }
  func();
  console.log(userName); //James
 
 

 

위 예제에서 변수 val, name 은 어느 범위에서든 접근이 가능하다.  따라서 전역 변수를 사용

하는 것은 되도록 지양하는 것이 좋다. 

 

val 부분 func 함수의 첫 라인, 변수 val undefined 이 출력되는 점과 

let 부분 func 함수의 첫 라인, 변수 userName ReferenceError 에러가 

나는 점에 대해서는 호이 스팅 관련해서 정리!

 

 

(전역 변수를 지양해야 하는 이유?)

 

 ◎ 암묵적 결합 

   전역 변수로 선언한 변수는 전역 객체인 window의 프로퍼티가 된다. 따라서 

   어느 범위에서든 접근이 가능하고 변경할 수 있어 의도치 않게 값이 변경될

   위험성이 있다. 

 

◎ 긴 생명주기

   전역 변수의 생명주기가 길기 때문에 값을 변경할 수 있는 시간이 길고

   모든 함수가 참조할 수 있므로 변경할 기회가 많다. 

 

   프로그램이 종료될 때까지 유효하므로 메모리 리소스가 오랫동안 소비된다. 

 

◎ 스코프 체인 상에서 종점에 존재

   전역 변수는 스코프 체인에서 가장 마지막에 존재한다. 

   따라서 변수를 조회할 때 가장 마지막에 조회되어 속도가 가장 느리다.

   (성능상 큰 차이는 없지만 속도에 차이가 생김) 

 

◎ 네임스페이스 오염

   자바스크립트는 파일이 독립적으로 존재해도 하나의 전역 스코프를 공유하고 있다. 

   이는 다른 파일에서 동일한 변수명이나 함수명에 접근하게 되어 예상치 못한 결과를 

   가져올 수 있어 문제가 된다. 

 

    

02) 지역 스코프 

  지역 스코프는 전역 스코프와는 반대로 코드 블록이나 함수의 내부에서 선언된 변수의 스코프를

  의미하며 이렇게 선언된 변수(= 지역 변수)는 외부에서 접근할 수 없다. 

 

 

//지역 스코프 
function showInfo() {
    const globalVal = {
        name'James',
        age: 20
    };
};
 
showInfo();
console.log(globalVal); //참조할 수 없음

 

위 코드에서 지역 변수로 선언된 globalVal는 외부에서 사용될 때 ReferenceError 에러를 발생한다. 

 

 

(결과)

 

 

함수 내부에서 선언된 변수를 지역 변수라고 하고, 함수 외부에서 선언한 변수는 모두 전역 변수이다.

전역 변수를 만드는 일은 최대한 지양하며 지역 변수로 범위를 제한하여 외부에서 접근할 수 없게

하는 것이 중요하다. 

 

전역 변수를 만든다는 것은 최상위 객체인 window 객체의 프로퍼티를 만드는 일이다. 

이렇게 만든 변수는 콘솔 창에 window.변수명을 입력하면 변수에 접근이 가능하다.  즉, 얼마든지 외부에서

이 변수의 값을 수정하거나 삭제할 수도 있으므로 주의해야 한다.

 

03) 렉시컬 스코핑(Lexical scoping, 정적 스코프)

  자바스크립트는 함수가 선언된 시점에서 스코프가 정해진다. 즉, 코드가 적힌 순간 스코프가 정해진다.

  이를 렉시컬 스코핑(Lexical scoping)라고 한다. 즉, 함수가 처음 선언되는 시점에 함수 내부의 변수는

  자기 스코프로부터 가장 근접한 곳(= 상위 범위)에 있는 변수를 참조하게 된다. 

 

  (예제 1)

  

var say1 = '안녕!';
let say2 = '잘지냈어?';
 
function sayHello() {
    console.log(say1, say2);
}
 
function sayBye() {
   say1 = '잘 가!';
   say2 = '즐거웠어!';
   sayHello();
}
 
sayBye(); //잘 가! 즐거웠어!
sayHello(); //잘 가! 즐거웠어!

 

 

위 예제에서 sayHello 함수의 스코프는 전역에 선언되었으므로 전역 스코프이고, 전역 변수인 say1, say2를 

각각 참조한다. 따라서 원래는 '안녕하세요', '잘 지냈어?'라는 값이 출력된다.

하지만 sayBye() 함수가 호출되고 함수 내부에서 변수 say1과 say2 이 재할당이 이루어지면서 전역 변수의 값이 변경되어 잘 가!, 즐거웠어!라는 값이 출력되었다. 

 

 

(계속해서 다음 예제를 살펴보자!)

 

 

(예제 2)

 

 

var say1 = '안녕!';
let say2 = '잘지냈어?';
 
function sayHello() {
    console.log(say1, say2);
}
 
function sayBye() {
    var say1 = '잘 가!';
    let say2 = '즐거웠어!';
    sayHello();
}
 
sayBye(); //안녕! 잘지냈어?
sayHello(); //안녕! 잘지냈어?

 

위 예제는 var, let 키워드만 추가했을 뿐이다. 결과는 안녕하세요!라는 값이 두 번 출력이 되었다.  첫 번째

예제에서는 sayBye 함수가 선언된 부분에서 재할당이 이루어져서 '잘 가!, 즐거웠어! 라는 값이 출력되었지 만 두 번째 예제에서는 say1, say2라는 새로운 변수가 선언되었으므로 전역 변수와 sayBye 함수에 있는 변수는 이름만 같을 뿐, 완전히 다른 변수가 되었다.

따라서 sayHello가 sayBye 함수 내부에서 호출되어도 여전히 전역 변수를 참조하고 있어서 안녕!, 잘 지냈어?라는 값이 두 번 출력하게 된 것이다. 

 

 

전역 변수 문제 해결 

 전역 변수로 사용할 것을 지양하고 되도록 지역 변수를 사용하는 것이 좋다. 해결 방법은 전역 변수 대신에

 지역 변수로 선언하는 것이다. 즉, 함수 안에 넣어 변수를 선언하거나 객체 안의 프로퍼티로 만들면 된다.

 

사실, var 키워드 대신 let, const 키워드를 사용하면 이 문제를 해결할 수 있다. let, const는 var와 달리 전역 변수로 생성해도 window 프로퍼티로 올라가지 않는다. 

 

 

아래의 방법은 var 키워드 당시의 전역 변수의 문제를 해결하는 방법으로 알아두자!

 

 01)  네임스페이스 객체

   

   (예제)

var globalVal = {} //전역 변수 객체
 
globalVal.userInfo = {
    name'Mike',
    age: 35,
    gender: 'male'
}
 
console.log(globalVal.userInfo.name//Mike

 

 

위 예제와 같이 전역 객체를 선언하고 전역 변수처럼 사용할 변수를 전역 객체의

프로퍼티로 추가하는 방법이다. 이러한 방식을 네임스페이스라고 부르며 

전역 객체로 선언된 변수는 네임스페이스(전역으로 사용하는) 역할을 담당하게 된다. 

 

이같은 방식은 문제를 완전히 해결하지는 못한다. 만약 누군가 고의적으로 프로퍼티의 값을

바꾸면 의도치 않는 결과를 생긴다.  즉, globalVal.userInfo.name = 'James'로 바꾸어 값을 

변경할 위험이 있다. 이를 해결하는 방법은 함수로 선언해서 즉시 실행해주는 즉시 실행 함수로

만드는 것이다. 

 

 

02)  즉시 실행 함수

 함수를 정의함과 동시 즉시 호출하는 함수를 즉시 실행 함수라 한다. 정의한 후 바로 실행되고

 없어지기 때문에 한 번밖에 실행할 수 없다. 따라서 즉시 실행 함수에 변수를 제한하여 지역 변수로

 만들 수 있다. 

 

 즉시 실행 함수는 많은 라이브러리에서 사용하는 구문이며, 모듈 패턴이라고도 한다. 

 

 자바스크립트에서는 자바처럼 private라는 키워드(= 접근 제한자)가 존재하지 않으므로 비공개

 변수를 만들 수가 없다. 따라서 이 방식을 사용하면 비공개 변수 기능을 만들 수 있다. 

 

 

var newVal = (function () {
   var stuID = 'ABS23B123';
    return {
        name: 'Mike',
        age: 35,
        gender: 'male'
    }
})()
 
console.log(newVal.age) //35

 

 

위와 같이 즉시 실행 함수 내부에 전역 변수처럼 사용할 프로퍼티를 리턴해주면 된다. 

그럼 리턴된 프로퍼티만 접근할 수 있도록 제한할 수 있다. 

 

 

 

결과를 확인하면 name 은 출력되지 않고 리턴한 프로퍼티(age, fender)만 출력하고 있다. 

 

 

05. 스코프 체인          

 자바스크립트 코드가 실행 시, 자바스크립트 엔진이 변수를 조회할 때 변수가 속한 해당 스코프에서

 먼저 찾고, 없다면 상위 스코프에서 찾게 된다. 그래도 없다면 최종적으로 전역 스코프에서 찾는다.

 

 이렇게 범위를 넓히면서 찾는 관계(= 스코프 간의 상하 관계)를 스코프 체인이라 한다.

  

 

 

(예제)

let userName = 'donghyun';
 
function outer() {
    console.log('외부', userName);
    function inner() {
        let nickname = 'blueone';
        console.log('내부', userName);
        console.log(userId); //Error: userId is not defined
    }
    inner();
}
outer();

  

inner 함수가 실행할 때, 변수 userName을 찾기 위해 우선 inner 함수 내부에서 변수가 있는지 찾고, 

없으면 한 단계 올라가 outer 스코프에서 찾고 그래도 없으면 최종적으로 전역 스코프에서 찾는다. 

만약 최종적으로 전역 스코프에도 찾을 수 없다면 예제와 같이 에러를 발생시킨다.

 

 

 

참조 사이트

 - 제로초 블로그 

 - Dev_YooJin

 - poiemaweb 

 

   

'Frontend study > JavaScript' 카테고리의 다른 글

deep JavaScript - 2. 호이스팅  (0) 2022.02.28
JavaScript - 10. Event  (0) 2022.02.27
JavaScript - 9. DOM (= 문서 객체 모델)  (0) 2022.02.27
JavaScript - 8. 객체  (0) 2022.02.27
JavaScript - 7. 함수  (0) 2022.02.27