모던 자바스크립트 Deep Dive 4장

김규회·2024년 1월 18일
0

04. 변수

4.1 변수는 왜 필요한가?

  • 컴퓨터(자바스크립트 엔진)가 자바스크립트 코드를 실행하고 계산하려면 그 식이 가진 기호(ex. 10, 20, +)들의 의미, 표현식(10+20)의 의미를 해석할 수 있어야 한다.

  • 메모리: 데이터를 저장할 수 있는 메모리 셀의 집합체

    메모리 셀

    메모리가 데이터를 저장하는 방. 메모리 셀 하나의 크기는 1바이트(8bit). 컴퓨터는 메모리 셀의 크기(1바이트) 단위로 데이터를 저장하거나 읽어들인다.

    메모리 주소

    메모리 공간의 위치. 0부터 시작해서 메모리 크기만큼 정수로 표현한다. 모든 데이터는 그 데이터의 형태가 어떻든 모두 2진수로 저장하고 처리한다.

  • CPU: 연산

    • 연산 결과를 재사용하고 싶다면?
    • 메모리 주소로 연산 결과(ex. 30)가 저장된 메모리 공간에 직접 접근하는 것은 위험하다(feat. 운영체제). 또한 메모리 제어를 허용하더라도, 코드가 실행될 때마다 값이 저장될 메모리 주소가 변경되기 때문에 매번 다른 주소를 찾아가야 한다. → 자바스크립트는 개발자의 직접적인 메모리 제어를 허용하지 않는다!
    • 따라서, 기억하고 싶은 값을 메모리에 저장하고, 메모리에 저장된 값을 다시 읽어와 재사용하기 위해 ‘변수’라는 매커니즘을 제공한다.

그럼 변수란 무엇인가?

  • 하나의 값을 저장하기 위해 확보한 메모리 공간 자체
  • 그 메모리 공간을 식별하기 위해 붙인 이름
  • 즉, 프로그래밍 언어에서 값을 저장하고 참조하기 위해 값의 위치를 가리키는 상징적인 이름
  • ‘변수’를 통해 개발자는 메모리 주소에 직접 접근하지 않고도 안전하게 값에 접근할 수 있다.

    변수에 여러 개의 값을 저장하기

    변수는 기본적으로 ‘하나’의 값을 저장하기 위한 매커니즘이다.
    따라서, 여러 개의 값을 저장하려면
    1) 여러 개의 변수를 사용하거나
    2) 배열이나 객체 등의 자료구조를 활용해 여러 개의 값을 그룹화하여 하나의 값처럼 사용해야 한다.
    • 객체 예시
      var user = { id: 1, name: ‘Lee’ };
    • 배열 예시(객체 배열)
      var users = [
      { id: 1, name: ‘Lee’ },
      { id: 2, name: ‘Kim’ }
      ];
  • 변수 이름
    • 메모리 공간에 저장된 값을 식별할 수 있는 고유한 이름
    • 변수 이름은 사람이 이해할 수 있는 언어로, 변수 이름을 통해 변수에 저장된 값의 의미를 명시할 수 있기 때문에 변수에 저장된 값의 의미를 파악할 수 있는 명확한 이름을 붙여주는 것이 좋다.
  • 변수 값
    • 변수에 저장된 값
  • 할당
    • 변수에 값을 저장하는 것
    • 대입, 저장이라고도 함
  • 참조
    • 변수에 저장된 값을 읽어들이는 것

4.2 식별자

  • 변수 이름을 식별자라고도 한다. 또한 함수, 클래스 등 어떤 값을 식별할 수 있는 이름은 모두 식별자라고 한다.

  • 어떤 값을 구별해서 식별할 수 있는 고유한 이름

  • 메모리 공간에 저장되어 있는 어떤 값을 구별해서 식별해내기 위해, 그 메모리 주소를 기억하고 있어야 한다.

    • 메모리 공간에 저장되어 있는 어떤 값을 구별해서 식별해내기 위해, 그 메모리 주소를 기억하고 있어야 한다.

    • 즉, 식별자는 값이 저장되어 있는 메모리 주소와 매핑 관계를 맺으며, 매핑 정보도 메모리에 저장되어야 한다. → 식별자는 값 자체가 아니라 메모리 주소를 기억한다.

    • 식별자는 네이밍 규칙을 준수해야 한다(뒤에서 다룰 예정).

    • 자바스크립트 엔진에 식별자의 존재를 알리기 위해서는 ‘선언’이라는 과정이 필요하다.

    • 호이스팅

4.3 변수 선언

  • 변수를 생성하는 것
  • 더 자세히 말하면 값을 저장하기 위한 메모리 공간을 확보하고, 변수 이름과 확보된 메모리 공간의 주소를 연결하는 행위를 말한다. → 확보&연결을 통해 값을 저장할 준비를 하는 것
  • 변수 선언 키워드
    - var, let, const
    - 원래는 var 키워드만 사용했었는데, ES6 이후로 let, const 키워드가 도입되었다.

    최근 var 키워드가 잘 안 쓰이는 이유

    1) 함수 레벨 스코프

    • 함수 내에서 선언된 변수는 함수 내에서만 유효하다. 따라서, 함수 외부에서 선언한 변수는 모두 전역 변수가 된다. 전역 변수는 전역 객체의 속성으로 간주되기 때문에 전역 객체의 생명주기, 즉 웹페이지를 닫을 때까지 계속 메모리를 차지한다. 쓰지 않을 때도 메모리를 계속 차지하고 있게 되므로 리소스 관리에 비효율적이다.
    • cf) 블록 레벨 스코프: {}로 묶인 코드 블록(ex. if문, for문 … 물론 함수도 포함됨) 내에서 선언된 변수는 해당 블록 안에서만 유효하다(ex. C언어에서 for(int i = 0; i<5; i++)라고 for문을 돌렸을 때, for문 밖에서 i 참조 불가). 변수가 유효할 수 있는 단위가 함수 레벨 스코프보다 더 자잘하기 때문에 변수의 생명 주기를 단축시킬 수 있어 메모리 관리에 더 효율적이다.

    2) var 키워드 생략 가능

    • var 키워드를 생략 가능하기 때문에 의도치 않은 전역변수를 양산할 수 있다.

    3) 변수 중복 선언 허용

    var a = 1; 라고 처음에 선언해 놓고,
    var a = 2; 이렇게 밑에서 동일한 변수를 똑같이 선언해도 오류를 내지 않는다. 이 경우 가장 최근에 선언한 var a = 2;를 참조하기 때문에 개발자의 의도와 다르게 프로그램이 흘러갈 수 있다. (let, const의 경우 변수 중복 선언 시 오류를 냄)

    4) 변수 호이스팅

    • 호이스팅: 변수, 함수 선언문이 코드의 선두로 끌어올려진 것처럼 동작하는 방식
    • var은 let과 다르게 선언 단계와 초기화 단계가 동시에 이루어져 코드 자체의 가독성을 떨어뜨리고, 변수의 초기화 순서에 혼란을 일으킨다.
      • 뒤에서 더 자세히 다룰 예정

    5) TDZ(Temporal Dead Zone)

    • let이랑const는 선언만 되고, undefined라는 값의 할당은 실행 시점에 되기 때문에 초기화 전에 변수를 사용해 버리면 프로그램에서 reference error을 내 준다. 따라서 코드를 예측 가능하게 하고 잠재적 버그를 줄인다. 그런데 var 키워드는 호이스팅 단계에서 초기화까지 진행해 버리므로 프로그램이 오류를 내 주지 않는다.
      • var 키워드를 사용한 변수 선언은 선언 단계와 초기화 단계가 동시에 진행된다. 즉, var score;라고 선언하면 해당 변수를 위한 메모리 공간이 확보되고 거기에 변수 이름 score을 등록함과 동시에 해당 공간에 undefined라는 값이 할당(초기화)된다.
      • 초기화 단계를 거치지 않으면 이전에 다른 애플리케이션이 사용했던 값(=쓰레기값)이 남아있을 수 있다.
      • 선언하지 않은 식별자에 접근하면 참조 에러가 발생한다.

4.4 변수 선언의 실행 시점과 변수 호이스팅

  • 변수 호이스팅
    console.log(score);
    
    var score;
    • 자바스크립트는 인터프리터 방식으로 한 줄씩 실행되기 때문에 변수 score가 선언되기 전에 score를 출력하므로 오류가 날 것 같지만, 참조 에러가 발생하지 않고 undefined가 출력된다.
    • 이유: 자바스크립트는 소스코드를 한 줄씩 순차적으로 실행하기에 앞서 먼저 소스코드의 평가 과정을 거쳐 소스코드를 실행하기 위한 준비를 한다. 이때, 준비 단계인 소스코드 평가 과정에서 변수 선언을 포함한 모든 선언문을 소스코드에서 찾아내 먼저 실행한다.
    • 평가 과정이 끝나면 선언문을 제외한 소스코드들을 한 줄씩 순차적으로 실행한다.
    • 이렇게 변수 선언문이 코드의 선두로 끌어올려진 것처럼 동작하는 자바스크립트 고유의 특징변수호이스팅이라고 한다.

4.5 값의 할당

  • 값의 할당은 할당연산자를 사용한다.
  • 우변의 값을 좌변의 변수에 할당한다.
  • var score = 80; 과 같이 선언과 할당을 한 문장에 할 수도 있다.
  • 다만, 하나의 문장이라도 두 개의 실행 시점은 다르다. 선언은 런타임 이전에(변수 호이스팅), 할당은 런타임에 실행된다.
  • 즉, 런타임 이전에 변수를 선언하면서 undefined로 초기화해 놓았다가, 이후 할당하는 시점에 80으로 값을 변경(재할당)한다. 이때, 기존의 undefined 값을 지우고 80을 새로 넣는 게 아니라, 새로운 메모리 공간을 확보해 거기 80을 저장하고 score이 그 새로운 공간을 가리키게 만든다.
console.log(score); 

//score = 80;
var score = 80;

console.log(score);
  • 위 코드에서 변수 호이스팅으로 끌어올려지는 코드는 var score; 하나다. 따라서 변수 호이스팅으로 인해 바뀐 코드 순서대로 다시 코드를 써 보면
var score;  //undefined
let score;  //선언만

console.log('할당 이전: '+score);  //참조 에러
score = 80;
console.log('할당 이후: '+score);
  • 이렇게 되기 때문에, 위쪽 출력문에서는 할당 이전의 값인 undefined, 아래쪽 출력문에서는 할당 이후의 값인 80이 출력된다.

let과 const는 선언만 호이스팅된다 (var의 문제점 4번 보완)

변수를 선언하는 시점부터 변수를 사용하는 것이 자연스러운데, 변수 선언만 먼저 위로 끌어올리는 호이스팅 방식 때문에 선언되기 전의 변수를 사용하는 등의 코딩이 가능했다. 이런 코딩 방식이 개발자 입장에서 코드의 가독성을 떨어뜨리고, 실행 순서를 혼란스럽게 만든다는 지적이 발생하면서 ES6부터 새로 생긴 let과 const 키워드를 이용한 변수는 선언만 호이스팅되어 위로 끌어올려지고, undefined는 실행 시점에 초기화된다.

4.6 값의 재할당

  • 재할당: 이미 값이 할당되어 있는 변수에 새로운 값을 또다시 할당하는 것
  • 처음에 선언과 동시에 undefined로 초기화되기 때문에 엄밀히 말하면 변수에 처음으로 값을 할당하는 것도 사실 재할당에 속한다.
  • 이전 값이 저장되어 있던 메모리 공간을 지우는 것이 아니라, 새 메모리 공간을 확보한 후 변수가 가리켜야 하는 메모리를 재지정하는 것이다! 이전 값을 저장하고 있던 메모리 공간은 가비지 콜렉터에 의해 자동 해제된다(해제 시점은 예측할 수 없다).
  • 상수: 값을 재할당할 수 없는 변수(const score = 80;)

가비지 콜렉터란?(Garbage Collector)

가비지 콜렉터는 메모리 공간을 주기적으로 검사하여 더 이상 사용되지 않는 메모리를 해제하는 기능을 애기한다. 자바스크립트는 매니지드 언어로서 개발자가 직접 메모리 제어를 할 수 없어 가비지 콜렉터를 통해 메모리 누수를 방지한다.

단점

  • 언제 가비지 컬렉션이 진행될지 예측하기 어렵다.
    • 객체가 쓸모 없어지는 시점에 정확히 메모리가 해제되지 않기 때문에 최적의 메모리 관리가 되지 않는다.
  • 가비지 컬렉터가 동작하는 시간이 든다.
    • 어떤 객체가 쓸모 없는지 판단하는 시간이 소요된다.

4.7 식별자 네이밍 규칙

  • 네이밍 규칙
    • 예약어는 식별자로 사용할 수 없다.
    • 특수문자를 제외한 문자, 숫자, 언더스코어(_), 달러 기호($)를 포함할 수 있다.
    • 숫자를 포함할 수는 있으나 숫자로 시작하는 건 안 된다.
    • 변수는 쉼표로 구분해 하나의 문에서 여러 개를 한 번에 선언 가능하다.
    • 대소문자는 구별한다.
  • 네이밍 컨벤션
    • 카멜 케이스

      var firstName;
    • 스네이크 케이스

      var first_name;
    • 파스칼 케이스

      var FirstName;
    • 헝가리언케이스

      var strFirstName; //type + identifier
    • 책에서는 카멜 케이스, 파스칼 케이스 추천

profile
프론트엔드 Developer

0개의 댓글