프로그래밍/JS

4장) 변수와 스코프, 메모리

소복 2015. 11. 22. 23:29

4.1 원시 값과 참조 값

ECMAScript의 변수는 원시 값과 참조 값 타입의 데이터를 저장할 수 있다.

원시 값: 단순한 데이터 (Undefined, Null, Boolean, Number, String) (스택에 저장된다.)

참조 값: 여러 값으로 구성되는 객체를 가리킴


변수에 값을 할당하면 원시 값인지 참조 값인지 판단한다.

- 원시 값은 변수에 저장된 실제 값을 조작한다. (값으로 접근한다.)

- 참조 값은 메모리에 저장된 객체이다.

  메모리 위치에 직접 접근하는 것이 안되므로 객체의 메모리 공간을 직접 조작하는 것은 불가능하다.

  따라서 객체를 조작 할 때는 객체에 대한 '참조'를 조작한다. (참조로 접근한다.)


4.1.1 동적 프로퍼티

값이 변수에 저장되고 난 후 원시 값과 참조 값이 할 수 있는 일은 다르다.

참조 값은 언제든 프로퍼티와 메서드를 추가, 변경, 삭제할 수 있다. (동적으로 프로퍼티를 추가할 수 있다.)

원시 값에 프로퍼티를 추가하면 에러는 생기진 않지만 다음 줄에서 프로퍼티는 사라진다.

ex)

var a = "abc";

a.num = 14;

alert(a.num);        // undefined


4.1.2 값 복사

원시 값과 참조 값은 값을 복사할 때도 다르다.

원시값은 변수의 값을 똑같이 새로 생성하여 다른 변수에 복사한다.

참조 값도 값을 복사하는 것은 마찬가지이나, 그 값은 힙에 저장된 객체를 가리키는 포인터다. 따라서 값을 복사하면 완전히 같은 객체를 가리킨다.


4.1.3 매개변수 전달

매개변수는 변수와 달리 오직 값으로만 전달된다.

따라서 참조 값인 객체를 매개변수로 넘기면 포인터가 넘어가므로 함수 내에서 값을 변경하면 객체의 원래 값도 변경된다.


4.1.4 타입 판별

typeof 연산자는 참조 값에 대해서는 별 쓸모가 없다.

객체나 null은 "object"를 반환하는데, 객체인지 아닌지보다 객체의 타입을 알고 싶은 경우가 많기 때문이다.

이런 경우 instanceof를 사용한다. ( 사용법: [result] = [variable] instanceof [constructor] )

변수가 참조 타입의 인스턴스이면 true를 반환한다.

모든 참조 값은 Object의 인스턴스로 정의되어 있으므로 참조 값이나 Object 생성자에 instanceof를 사용하면 항상 true를 반환한다.


4.2 실행 컨텍스트와 스코프

실행 컨텍스트(컨텍스트라고도 부르며 약자로 EC라고 부르기도 한다.)는 다른 데이터에 접근 할 수 있는지, 어떻게 행동하는지를 규정한다.

각 실행 컨텍스트에는 변수 객체(VO)가 연결되어있다. 해당 컨텍스트에서 정의된 모든 변수와 함수는 이 객체의 프로퍼티 및 메서드로 존재한다.

코드에서 이 객체에 접근할 수는 없지만 이면에서 데이터를 다룰 때 이 객체를 사용한다.


가장 바깥쪽에 존재하는 실행 컨텍스트는 전역 컨텍스트이다.

ECMAScript를 구현한 환경에 따라 이 컨텍스트를 부르는 이름은 각기 다르다.

웹 브라우저에서는 이 컨텍스트를 window라고 부른다.


컨텍스트는 포함된 코드가 모두 실행되고 나면 파괴된다.

함수는 독자적인 컨텍스트를 생성한다. 함수가 끝나면 해당 컨텍스트를 스택에서 꺼내고 컨트롤을 이전 컨텍스트에 반환한다.


컨텍스트에서 코드를 실행하면 변수객체에 스코프 체인이 만들어진다.

스코프 체인의 목적은 컨텍스트가 접근할 수 있는 모든 변수와 함수의 순서를 정의하는 것이다.

스코프 체인의 앞쪽은 항상 컨텍스트의 변수 객체이다.

컨텍스트가 함수라면 활성화 객체를 변수 객체로 활용한다. (활성화 객체는 항상 arguments 변수 하나로 시작한다.)

그 다음부터는 해당 컨텍스트의 부모 컨텍스트이며 전역 컨텍스트에 도달할 때까지 부모 컨텍스트이다.


식별자를 찾을 때 스코프 체인 순서를 따라가면서 찾는다. (전역 컨텍스트가 맨 마지막)

따라서 내부 컨텍스트는 외부 컨텍스트에 접근할 수 있지만, 외부 컨텍스트는 내부 컨텍스트에 전혀 알 수 없다.


4.2.1 스코프 체인 확장

특정 코드는 스코프 체인 앞에 임시로 변수 객체를 추가한다. 해당 변수 객체는 코드의 실행이 끝나면 사라진다.

- try-catch의 catch블록 : 에러 객체를 선언하는 변수 객체가 추가된다.

- with문 : with(object)에서 object객체가 추가된다. (with문 내부에서 선언한 변수는 함수의 컨텍스트로 편입된다.)


4.2.2 자바스크립트에는 블록 레벨 스코프가 없습니다.

if문, for문 안에서 선언한 변수는 루프가 끝난 후에도 존재한다.

var을 사용해 선언한 변수는 자동으로 가장 가까운 컨텍스트에 추가된다.

선언하지 않고 초기화하면 자동으로 전역 컨텍스트에 추가된다.

전역 컨텍스트의 변수가 사용하고 싶으면 window.[변수이름]을 사용하면 된다.


4.3 가비지 콜렉션

더 이상 사용하지 않을 변수를 찾아 해당 변수가 차지하는 메모리를 회수한다.

주기적으로 실행되며 원하는 부분에 실행되도록 지정할 수도 있다.

더 이상 사용하지 않을 변수를 식별하는 기준은 브라우저마다 다르지만 보통 두 가지 방법을 사용한다.


4.3.1 표시하고 지우기 (mark-and-sweep)

가장 널리 쓰이는 방법이다.

메모리에 저장된 모든 변수에 표시를 남긴다.

컨텍스트에 있는 변수와 변수가 참조하는 변수의 표시를 지운다.

표시가 남아있는 변수를 파괴한다.


4.3.2 참조 카운팅 (reference counting)

각 값이 얼마나 많이 참조되었는지 카운팅한다.

참조 카운트가 0이 되면 메모리를 회수한다.

A와 B가 서로 참조하면 각각의 참조 카운트는 2가 되어 해당 컨텍스트가 끝나도 메모리 회수를 하지 못해 메모리를 낭비하는 문제가 있다. (순환 참조)


4.3.4 메모리 관리
웹 브라우저에서 사용할 수 있는 메모리는 매우 적기 때문에 가능한 최소한의 메모리만 사용해야 페이지의 성능을 올릴 수 있다.

코드실행에 필요한 데이터만 유지하여 최적화하는 것이 좋다.

필요없어진 데이터에는 null을 할당하여 참조를 제거하는 편이 좋다. (순환 참조문제도 해결할 수 있다.)

수동으로 참조를 제거해야 하는 대상은 주로 전역 변수와 전역 객체의 프로퍼티이다.

참조를 제거해서 메모리가 자동으로 반환되는 것은 아니지만 값의 컨텍스트를 없애서 다음 가비지 콜렉션이 실행되면 메모리가 회수된다.


[출처: 프론트엔드 개발자를 위한 자바스크립트 프로그래밍]