Blog

변수와 스코프, 메모리

April 1, 2014

변수와 스코프, 메모리

목차

이 포스팅은 "프론트엔드 개발자를 위한 자바스크립트(2013 인사이트, 한선용 옮김)"에서 발췌 요약한 것이며, 인사이트와 저작권에 대한 부분을 의논하여 사전 허락을 받은 것입니다. 자세한 내용은 페이스북 자바스크립트 제대로 하기 스터디 그룹해당 포스트를 참조하시기 바랍니다.

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

자바스크립트 변수는 느슨한 타입을 취하여, 변수의 이름만 그대로이고, 값과 데이타 타입은 스크립트 실행 중에도 바뀔 수 있다.

4.1 원시 값과 참조 값

ECMAScript의 변수는 원시 값과 참조 값(Primitive And Reference Values) 두가지 타입의 데이터를 저장할 수 있다.

원시 값은 단순한 데이터이며, Undefined, Null, 불리언, 숫자, 문자열이다. 변수에 저장된 실제 값을 조작하기 때문에 '값으로' 접근한다고 한다. 원시 값은 고정된 크기를 가지며 스택 메모리에 저장된다.

참조 값은 여러 값으로 구성되는 객체이자 메모리에 저장된 객체이며, 힙 메모리에 저장된다. 객체를 조작할 때는 객체 자체가 아닌 해당 객에 대한 '참조'를 조작하기 때문에 '참조로 접근한다'고 한다.

동적 프로퍼티

참조 값을 다룰 때는 언제든 프로퍼티와 메서드를 추가하거나 바꾸고 삭제할 수 있으며, 객체를 파괴하거나 프로퍼티를 제거하기 전까지는 접근할 수 있다. 원시 값에는 프로퍼티가 없으며, 참조 값만이 동적으로 프로퍼티를 추가할 수 있다.

값 복사

원시 값은 다른 변수로 복사할 때 저장된 값을 새로 생성한 다음 새로운 변수에 복사하여 두 변수를 완전히 분리한다.
참조 값을 변수에서 다른 변수로 복사하면, 힙(heap: 브라우저가 쓰는 메모리)에 저장된 객체를 가리키는 포인터 값을 복사하므로 두 변수는 정확히 같은 객체를 가리킨다. 따라서 한쪽을 조작하면 다른 쪽에도 반영된다.

매개변수 전달(Argument Passing)

ECMAScript의 함수 매개변수는 모두 값으로 전달된다. 원시 값이던 참조 값이던 함수 매개변수는 지역 변수에 복사되어 실행되며 함수 내에서만 동작하고 외부에 반영되지 않는다. 객체의 경우에도 외부 객체에 프로퍼티만 추가될 뿐이며, 생성된 지역 객체는 함수가 실행을 마치는 즉시 파괴된다. 함수 매개변수는 지역 변수와 다를 것이 없다고 생각하라.

타입 판별

typeof 연산자는 변수가 문자열이나 숫자, 불리언, undefined라면 정확한 타입을 알 수 있다. 값이 객체이거나 null이면 "object"를 반환한다.

instanceof 연산자는 변수가 주어진 참조 타입의 인스턴트일 때 true를 반환하기 때문에, 어떤 타입의 객체인지 판별할 때 도움이 된다. 원시 값은 항상 false를 반환다.

요약: typeof 연산자는 값의 원시 타입을 판별하며 instanceof 연산자는 값의 참조 타입을 판별한다.

4.2 실행 컨텍스트와 스코프

실행 컨텍스트(execution context:EC)는 짧게 '컨텍스트'라고 부르며, 비할 바 없이 중요한 개념이다. 변수나 함수의 실행 컨텍스트는 다른 데이터에 접근할 수 있는지, 어떻게 행동하는지를 규정한다. 각 실행 컨텍스트에는 변수 객체(variable object:VO)가 연결되어 있으며 해당 컨텍스트에서 정의된 모든 변수와 함수는 이 객체에 존재한다. 이 객체를 코드에서 접근할 수는 없지만 이면에서 데이터를 다룰 때 이 객체를 이용한다.

가장 바깥쪽에 존재하는 실행 컨텍스트는 전역 컨텍스트이다. 웹 브라우저에서는 이 컨텍스트를 windows라고 부르며, 전역 변수와 함수는 모두 window 객체의 프로퍼티 및 메서드로 생성도니다.실행 컨텍스트는 포함된 코드가 모두 실행될 때 파괴되는데, 이때 컨텍스트 내부에서 정의된 변수와 함수도 함께 파괴된다. 전역 컨텍스트는 애플리케이션이 종료될 때, 예를 들어 웹 페이지에서 나가거나 브라우저를 닫을 때까지 유지된다.

함수를 호출하면 자체의 실행 컨택스트가 생성된다. 코드 실행이 함수로 들어갈 때마다 함수의 컨텍스트가 컨텍스트 스택에 쌓인다. 함수 실행이 끝나면 해당 컨텍스트를 꺼내고 컨트롤을 이전 컨텍스트에 반환한다.

컨텍스트에서 코드를 실행하면 변수 객체에 '스코프 체인(scope chain)'이 만들어진다. 스코프 체인의 목적은 실행 컨텍스트가 접근할 수 잇는 모든 변수와 함수에 순서를 정의하는 것이다.스코프 체인의 앞쪽은 항상 코드가 실행되는 컨텍스트의 변수 객체이다. 컨텍스트가 함수라면 '활성화 객체(activation object)'를 변수 객체로 사용한다. 활성화 객체는 항상 arguments 변수 단 하나로 시작한다(이 변수는 전역 컨텍스트에는 존재하지 않는다). 변수 객체의 다음 순서는 부모 컨텍스트이며 그 다음에는 부모의 부모 컨텍스트이다. 이런 식으로 스코프 체인의 마지막인 전역 컨텍스트에 도달할 때까지 계속한다. 식별자를 찾을 때는 스코프 체인 순서를 따라가며 검색하며, 찾을 수 없다면 일반적으로 에러가 발생한다.

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

스코프 체인 확장

Scope Chain Augmentation
실행 컨텍스트에는 전역 컨텍스트와 함수 컨텍스트 두 가지 타입만 있지만(eval()은 세 번째 타입이 있다) 스코프 체인을 확장할 수 있는 방법도 있다.

try-catch 문의 catch 블록과 with 문은 모두 스코프 체인 앞 부분에 임시로 변수 객체를 만들며 코드 실행이 끝나면 사라진다. with 문에서는 해당 객체가 스코프 체인에 추가되며 catch 문에서는 에러 객체를 선언하는 변수 객체가 생성된다.

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

No Block-Level Scopes
블록 레벨 스코프를 지원하는 언어에서는 for문의 초기화 부분에서 선언한 변수가 오직 for문의 컨텍스트 안에서만 존재한다. 그러나 자바스크립트에서는 for문에서 선언한 i변수가 루프 실행이 끝난 후에도 존재한다.

변수 선언

var를 사용해 선언한 변수는 자동으로 가까운 컨텍스트에 추가된다. 함수 내부에서는 함수의 로컬 컨텍스트가 가장 가까운 컨텍스트이며 with 문 내부에서는 함수 컨텍스트가 가장 가까운 컨텍스트이다. 변수를 선언하지 않은 채(함수 내부에서 var 키워드가 생략된 채) 초기화하면 해당 변수는 자동으로 전역 컨텍스트에 추가된다.(외부에서 접근할 수 있게 된다)

항상 변수를 선언한 다음 초기화하라. 스트릭트 모드에서는 변수를 선언하지 않고 초기화하려 하면 에러를 낸다.

식별자 검색

컨텍스트 안에서 식별자를 참조하려면 검색부터 해야 하고, 스코프 체인을 따라 검색을 계속하여 전역 컨텍스트의 변수 객체에 도달할 때까지 계속한다. 식별자를 찾지 못하면 정의되지 않은 것으로 판단한다.

이러한 검색 과정 때문에 식별자가 로컬 컨텍스트에 정의되어 있으면 부모 컨텍스트에 같은 이름의 식별자가 있다 해도 참조할 수 없다.

4.3 가비지 콜렉션

자바스크립트는 실행 환경에서 코드 실행 중에 메모리를 관리한다는 의미에서 가비지 콜렉션(garbage collection) 언어이다. 필요한 메모리를 자동으로 할당하고 더 이상 사용하지 않는 메모리는 자동으로 회수하므로 직접 메모리를 관리하지 않아도 된다.

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

표시하고 지우기(Mark-and-Sweep)

가장 널리 쓰인다.

참조 카운팅(Reference Counting)

널리 쓰이지는 않지만, 이 방식의 주안점은 각 값이 얼마나 참조되었는지 추적한다는 것이다.

참조 카운팅 방법은 넷스케이프 내비게이터 3.0에서 처음 사용되었으나 즉시 순환 참조라는 심각한 문제가 뱔견되었다. 버전 4.0에서 참조 카운팅 방식을 버렸다.

IE 8과 그 이전 버전에서는 BOM과 DOM의 객체들이 참조 카운 방식을 이용하는 C++의 COM 객체로 구현되었다. 이 경우 순환 참조 문제를 해결하려면 해당 객체들에 null을 할당하여 연결을 끊어야 한다. IE 9에서는 BOM과 DOM 객체를 진정한 자바스크립트 객체로 만들어 이 상황을 다소 완화했다.

성능

가비지 컬렉터는 주기적으로 실행되며 메모리 내에 할당된 변수가 많다면 상당한 비용이 드는 작업이므로 실행 타이밍이 중요하다. IE는 가비지 컬렉터를 할당 숫자 기준으로 너무 자주 실행하여 성능 문제를 일으키는 것으로 악명이 높다.

메모리 관리

일반적으로는 가비지 컬렉션 환경에서는 개발자가 메모리 관리를 신경쓰지 않아도 되지만, 웹 브라우저에서 사용할 수 있는 메모리는 일반적인 테스크톱 애플리케이션의 가용 메모리에 비해 매우 적다.

페이지 성능을 올리기 위해 메모리 사용을 최적화하는 가장 좋은 방법은 코드 실행에 필요한 데이터만 유지하는 것이다. 필요없어진 데이터에는 null을 할당하여 참조를 제거(dereference)하는 편이 좋다. 수동으로 참조 제거해야 할 대상은 주로 전역 변수 및 전역 객체의 프로퍼티이다. 지역 변수는 컨텍스트를 빠져나가는 순간 자동으로 참조가 제거된다.

값의 컨텍스트를 없애기 위해서 변수에서 참조를 제거하면 다음 가비지 콜렉션을 실행할 때 해당 메모리가 회수된다.

관련 글들