Zin0_0 2020. 6. 26. 21:51
반응형

변수와 스코프, 메모리

JS는 느슨한 변수 타입 ~> 변수는 특정 시간 특정 값을 가리키는 문자 그대로 이름일 뿐

  • 원시 값과 참조 값

    • 원시 값 : 단순 데이터 (스택 메모리에 저장)
    • 참조 값 : 여러 값으로 구성되는 객체를 가리킴(힙 메모리에 저장)
    • 변수에 값을 할당 ~> JS 엔진이 원시 데이턴지 참조 데이턴지 판단
    • JS는 메모리 위치에 직접 접근하는 것을 허용 X ~> 객체를 조작할 때는 객체 자체가 아닌 객체에 대한 참조를 조작하는 것
  • 동적 프로퍼티

    • 참조 값을 다룰 때는 언제든 프로퍼티와 메서드를 추가/삭제 가능

      var person = new Object();
      person.name = "Zin0";
      alert(person.name);    // Zin0
      
      var name = "Zin0";
      name.age = 28;
      alert(name.age);    // undefined

      원시 값에는 프로퍼티가 없지만 추가하려해도 에러 X.
      다만, 동적으로 프로퍼티를 추가할 수 있는 값은 참조 값 뿐이다.

  • 값 복사

    • 원시 값을 다른 변수로 복사할 때는 저장된 값을 새로 생성해서 새로운 변수에 복사
      var num1 = 5;
      var num2 = num1;
      위의 두 변수는 5라는 값을 각각 갖는다. (다른 메모리 위치)
    • 참조 값을 복사할 때는, Heap 공간에 저장된 객체를 가리키는 포인터 값을 복사해줌.
      var obj1 = new Object();
      var obj2 = obj1;
      obj1.name = "Zin0";
      alert(obj2.name);    // Zin0    
      같은 객체를 가리키기 때문에, obj2에서 해당 프로퍼티에 접근하면, Zin0 값을 가져온다.
  • 매개변수 전달

    • ECMAScript의 함수 매개변수는 모두 값으로 전달

    • 매개변수의 값은 지역 변수에 복사된다. ~> 이름 붙은 매개변수로 복사되면서, ECMAScript에서는 arguments 객체의 한 자리를 차지한다. 원시 값과 참조 값에 따라 값을 복사함.

      function addTen(num) {
          num +=10;
          return num;
      }
      
      var count = 20;
      var result = addTen(count);
      alert(count); // 20
      alert(result); // 30
      function setName(obj) {
          obj.name = "Zin0"
      }
      var person = new Object();
      setName(person);
      alert(person.name); // Zin0
      function setName(obj) {
          obj.name = "Zin0";
          obj = new Object();
          obj.name = "JY";
      }
      var person = new Object();
      setname(person);
      alert(person.name);    // Zin0    
    • 매개 변수가 원시 값일 경우에는 값을 복사하여 지역변수에 저장하고 return함으로 변수값에 변화가 없음.

    • 참조 값의 경우 매개변수가 참조 형태로 전달된 것이 아니라, Heap 공간에 저장된 객체를 가리키는 포인터 값을 복사해 준 것이다. ECMAScript의 함수 매개변수는 지역 변수와 다를 것 없다고 생각하면 편하다.

  • 타입 판별

    • typeof 연산자는 변수가 원시 타입인지 파악하기 최상.
      • 값이 객체거나 null이면 "object"를 반환
      • 함수에 사용하면, "function"을 반환
    • instanceof 연산자는 주어진 참조 타입의 인스턴스인지 파악
      alert(person instanceof Object); // person 변수가 Object의 인스턴스인가?
      alert(colors instanceof Array); // colors 변수가 Array의 인스턴스인가?
      alert(pattern instanceof RegExp); // pattern 변수가 RegExp의 인스턴스인가?
      • 원시 값은 Object의 인스턴스가 아니기 때문에 항상 false 값을 리턴
  • 실행 컨텍스트와 스코프

    • 실행 컨텍스트(Execution Context) - '컨텍스트'라고 부름.

    • 실행 컨텍스트는 다른 데이터에 접근할 수 있는지, 어떻게 행동하는지를 규정한다.

    • 변수 객체가 연결되어 있으며, 해당 컨텍스트에서 정의된 모든 변수와 함수는 이 객체에 존재.

    • 코드에서 직접 접근할 수는 없지만, 이면에서 데이터를 다룰 때 이 객체를 이용

    • 전역 컨텍스트 - 가장 바깥쪽에 존재하는 실행 컨텍스트

      • ECMAScript 구현 황경에 따라 이름이 달라진다.
      • 웹 브라우저에서는 window라 부르고, 이 window 객체에 전역 변수와 함수가 모두 window 객체의 프로퍼티 및 메서드로 생성됨.
      • 실행 컨텍스트는 포함된 코드가 모두 실행될 때 파괴 (Life Cycle)
        • 내부에서 정의된 변수와 함수도 함께 파괴됨
        • 전역 컨텍스트는 애플리케이션이 종료될 때 파괴된다.
    • 함수 호출 ~> 독자적인 실행 컨텍스트 생성 ~> 스택에 쌓임.

      • 함수 실행이 끝나면 컨텍스트를 스택에서 꺼내고 컨트롤을 이전 컨텍스트에 반환
    • 컨텍스트에서 코드를 실행하면, 변수 객체에 스코프 체인이 만들어 진다.

      • 실행 컨텍스트가 접근할 수 있는 모든 변수와 함수에 순서를 정의

      • 스코프 체인의 앞쪽은 항상 코드가 실행되는 컨텍스트의 변수 객체

      • 컨텍스트가 함수라면 활성화 객체를 변수 객체로 사용

      • 활성화 객체는 변수 하나로 시작(전역 컨텍스트에 존재X)

        • 전역 컨텍스트에 도달할 때 까지, 하위 컨텍스트부터 부모 컨텍스트로 계속 이동
      • 식별자를 찾을 때는 스코프 체인을 따라가면서 식별자 이름 검색
        ~> 찾을 수 없을 때는 에러 발생
        var color = "blue";

        function changeColor() {

          var anotherColor = "red";
        
          function swapColors() {
              var tempColor = anotherColor;
              anotherColor = color;
              color = tempColor;
              // color, anotherColor, tempColor 모두 접근 가능
          }
          // color, anotherColor 접근 가능, tempColor는 불가능
          swapColors();

        }
        // color만 접근 가능
        changeColor();

    • 내부 컨텍스트는 스코프 체인을 통해 외부 컨텍스트 전체에 접근 가능하지만, 외부 컨텍스트는 내부 컨텍스트에 대해 전혀 알 수 없다.

  • 스코프 체인 확장

    • 실행 컨텍스트에는 전역 컨텍스트와 함수 컨텍스트 두 타입이 존재(eval() 호출 시, 생성되는 세 번째 타입이 있기는 하다.)

    • 스코프 체인을 확장하는 방법

      • 스코프 체인 앞 부분에 임시로 변수 객체를 만들며, 코드 실행 후에 사라진다.

        • try-catch 문의 catch 블록 - 에러 객체를 선언하는 변수 객체가 생성

        • with 문 - 객체가 스코프 체인에 추가
          function buildUrl() {

          var qs = "?debug=true";
          
          with(location) {
              var url = href + qs;
          }
          return url;

          }
          ▲ with 문이 location 객체에 적용됨 ~> location 객체가 스코프 체인에 추가됨.

      • href 변수는 location.href를 참조, qs는 buildUrl()함수 컨텍스트의 변수 객체에 들어있다. 따라서 with문 내부에서 선언한 변수 url은 함수 컨텍스트로 편입 ~> 함수 값으로 반환 가능

  • JS에는 블록 레벨 스코프가 없다

    if(true) {
        var color = "blue";
    }
    alert(color); // blue
    
    for(var i=0; i<10; i++) {
        doSth(1);
    }
    alert(i);    //10
    • JS에서는 변수를 선언할 때 현재 실행 컨텍스트에 추가한다. 따라서 블록 레벨 안의 변수를 이용 가능

    • 변수 선언

      • var를 사용해 선언한 변수는 자동으로 가장 가까운 컨텍스트에 추가된다.
        • 함수 내부 -> 함수 로컬 컨텍스트
        • with문 내부 -> 함수 컨텍스트
          function add(num1, num2) {
          var sum = num1 + num2;
          return sum;
          }
          var result = add(10,20); //30
          alert(sum); // error
    • 식별자 검색

      • 컨텍스트 안에서 식별자 참조 -> 스코프 체인 앞에서부터 식별자를 탐색

      • 식별자 이름을 찾으면 검색 멈추고 변수를 설정

      • 전역 컨텍스트의 변수 객체에서도 못찾으면, 정의되지 않을 것으로 판단.
        var color = "blue";
        function getColor() {

          return color;

        }
        alert(getColor()); // blue

        function getColor2() {

          val color = "red";
          return color;

        }
        alert(getColor()); // red


  • 가비지 콜렉션
    • 코드 실행 중에 메모리를 관리함 ~> 필요한 메모리 자동 할당, 더 이상 사용 X 메모리는 자동 회수.
    • 코드 실행 중에 특정 시점에서 메모리를 회수하도록 지정할 수 있음
    • 더이상 사용하지 않는 변수를 식별하는 기준은 두 가지 방법을 흔히 사용한다.
  • 1 - 표시하고 지우기
    • JS에서 가장 널리 쓰이는 가비지 컬렉션 방법
    • 변수가 특정 컨텍스트 안에서 사용할 것으로 정의 ~> 그 컨텍스트 안에 있는 것으로 표시
      • 컨텍스트 안에 존재하는 변수의 메모리는 해제하면 안된다. 실행 중에 사용될 가능성이 있기 때문에. 변수가 컨텍스트 밖으로 나가면 컨텍스트 밖에 있는 것으로 표시
    • 가비지 컬렉터가 작동하면 메모리에 저장된 변수 전체에 표시를 남긴다.
    • 그 다음 컨텍스트에 있는 변수와, 컨텍스트에 있는 변수가 참조하는 변수에서 표시를 지운다.
    • 위의 두 과정을 거친 다음에도 표시가 남아있는 변수는 삭제해도 안전함.
  • 2 - 참조 카운팅
    • 널리 쓰이지는 않는다.
    • 각 값이 얼마나 많이 참조됐는지 추적
    • 변수 선언하고 참조 값이 할당되면 참조 카운트는 1
      • 다른 변수가 같은 값을 참조하면 카운트 증가
      • 해당 값을 참조하는 변수에 다른 값 할당 ~> 원래 값의 참조 카운트가 줄어든다.
      • 참조 카운트가 0이 되면 해당 값에 접근할 방법 X, 메모리 회수해도 안전
    • 순환 참조
      • 객체 A와 B가 서로를 참조하는 경우
      • 표시하고 지우는 방식은 함수 실행이 끝날 때, 두 변수가 모두 스코프를 벗어남으로 문제X
      • 참조 카운팅은 서로 참조 카운트 2를 가지기 때문에, 함수 실행 이후에도 두 변수 존재.
      • IE 8을 포함한 이전 버전은 BOM과 DOM 객체들이 c++의 COM(구성 요소 객체 모델)로 구현됨. COM 객체는 가비지 콜렉션에 참조 카운팅 방식 이용. ~> 순환 참조 문제
      • IE 9는 BOM과 DOM 객체를 JS 객체로 만들어서 해결
  • 성능
    • 가비지 컬렉션을 실행하는 타이밍이 중요함 (IE가 가비지 컬렉터를 너무 자주 실행해서 성능 문제를 일으킨다..)
  • 메모리 관리
    • 가비지 컬렉션을 지원하는 프로그래밍 환경에서는 메모리 관리에 신경쓰지 않아도 된다.
    • 하지만, JS라는 환경에서 메모리 관리와 가비지 콜렉션은 다름.
    • 웹브라우저에서 사용할 수 있는 메모리는 일반적인 데스크톱 애플리케이션 가용 메모리에 비해 매우 적기 때문. ~> JS가 시스템 메모리를 전부 사용해서 OS 다운되는 현상 방지 위해
    • 따라서 필요없어진 데이터는 null을 할당하여 참조를 제거하는 편이 좋다.
      • 주로 전역 변수 및 전역 객체의 프로퍼티에 적용
      • 지역 변수는 컨텍스트를 빠져나가는 순간 자동으로 참조 제거
반응형