Blog

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

April 3, 2014

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

이 글은 자바스크립트 제대로 배우기 스터디 그룹의 활동으로 «프론트엔드 개발자를 위한 자바스크립트»(2013 인사이트, 한선용 옮김)에서 요약한 글임을 밝힙니다.

변수와 스코프, 메모리

자바스크립트에서 변수는 느슨한 타입을 취하므로 변수는 특정 시간에서 특정 값을 가리키는, 문자 그대로 이름일 뿐, 변수가 가져야 할 데이터 타입에 대한 규칙이 없으므로 변수의 값과 데이터 타입은 스크립트 실행 중에 바뀔 수 있음

원시 값과 참조 값

‘원시 값’은 단순한 데이터이며 ‘참조 값’은 여러 값으로 구성되는 객체를 가르킴
변수에 값을 할당하면 자바스크립트 엔진은 해당 값이 원시 데이터인지 참조인지 판단
원시 타입은 Undefined와 Null, 불리언, 숫자, 문자열 / ‘값으로’ 접근한다고 하며 변수에 저장된 실제 값을 조작함
참조 값은 메모리에 저장된 객체 / 객체를 조작할 때는 사실 객체 자체가 아니라 해당 객체에 대한 ‘참조’를 조작함, ‘참조로 접근한다’라고 함

동적 프로퍼티

참조 값을 다룰 때는 언제든 프로퍼티와 메서드를 추가하거나 바꾸고 삭제할 수 있음

var person = new Object();  
person.name = "Steve";  
alert(person.name); // "Steve"
값 복사

원시 값은 다른 변수로 복사할 때는 현재 저장된 값을 새로 생성한 다음 새로운 변수에 복사함
참조 값은 객체 자체가 아닌 힙에 저장된 객체를 가리키는 포인터를 복사함

var obj1 = new Object();  
var obj2 = obj1;  
obj1.name = "Steve";  
alert(obj2.name); // "Steve"
매개변수 전달

ECMAScript의 함수 매개변수는 모두 값으로 전달됨, 매개변수를 값 형태로 넘기면 해당 값은 지역 변수에 복사됨

ECMAScript의 함수 매개변수는 지역 변수와 다를 것이 없음

타입 판별

typeof 연산자는 변수가 원시 타입인지 파악하기에 최상, 특정 변수가 문자열이나 숫자, 불리언, undefined라면 정확한 타입을 알 수 있음
instanceof 연산자는 변수가 주어진 참조 타입의 인스턴스일 때 true를 반환

'result' = 'variable' instanceof 'constructor'

실행 컨텍스트와 스코프 (매우 중요함!)

실행 컨텍스트는 짧게 ‘컨텍스트’라고 부르며 자바스크립트에서는 매우 중요한 개념
변수나 함수의 실행 컨텍스트는 다른 데이터에 접근할 수 있는지, 어떻게 행동하는지를 규정
각 실행 컨텍스트에는 변수 객체가 연결되어 있으며 해당 컨텍스트에서 정의된 모든 변수와 함수는 이 객체에 존재

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

컨텍스트에서 코드를 실행하면 변수 객체에 ‘스코프 체인’이 만들어짐, 스코프 체인의 목적은 실행 컨텍스트가 접근할 수 있는 모든 변수와 함수에 순서를 정의하는 것, 스코프 체인의 앞쪽은 항상 코드가 실행되는 컨텍스트의 변수 객체, 컨텍스트가 함수라면 ‘활성화 객체’를 변수 객체로 사용, 활성화 객체는 항상 arguments 변수 단 하나로 시작, 변수 객체의 다음 순서는 해당 컨텍스트를 포함하는 컨텍스트(부모 컨텍스트)이며 그 다음에는 다시 부모의 부모 컨텍스트, 전역 컨텍스트의 변수 객체는 항상 스코프 체인의 마지막에 존재, 식별자를 찾을 때는 스코프 체인 순서를 따라가면서 해당 식별자 이름을 검색 (식별자를 찾을 수 없다면 일반적으로 에러가 발생)

내부 컨텍스트는 스코프 체인을 통해 외부 컨텍스트 전체에 접근할 수 있지만 외부 컨텍스트는 내부 컨텍스트에 대해 전혀 알 수 없음, 컨텍스트 사이의 연결은 선형이며 순서가 중요함

스코프 체인 확장

실행 컨텍스트에는 전역 컨텍스트와 함수 컨텍스트 두 가지 타입만 있지만(eval()을 호출할 때 생성되는 세 번째 타입이 있음) 스코프 체인을 확장할 수 있는 방법도 있음, 특정 문장은 스코프 체인 앞 부분에 임시로 변수 객체를 만들며 해당 변수 객체는 코드 실행이 끝나면 사라짐 – try-catch 문의 catch 블록, with 문

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

C와 비슷한 다른 언어에서는 중괄호로 감싼 코드 블록마다 스코프(ECMAScript에서는 실행 컨텍스트라고 부르는)가 생성되므로 조건에 따라 변수를 정의할 수 있음, 하지만 자바스크립트에서는 변수를 선언할 때 해당 변수를 현재 실행 컨텍스트에 추가함

블록 레벨 스코프를 지원하는 언어에서는 for 문의 초기화 부분에서 선언한 변수가 오직 for 문의 컨텍스트 안에서만 존재하지만, 자바스크립트에서는 for 문에서 생성한 변수가 루프 실행이 끝난 후에도 존재

변수 선언

var를 사용해 선언한 변수는 자동으로 가장 가까운 컨텍스트에 추가, 함수 내부에서는 함수의 로컬 컨텍스트가 가장 가까운 컨텍스트이며 with 문 내부에서는 함수 컨텍스트가 가장 가까운 컨텍스트, 변수를 선언하지 않은 채 초기화하면 해당 변수는 자동으로 전역 컨텍스트에 추가

변수를 선언한 다음 초기화를 반드시 할 것! 스트릭트 모드에서는 변수를 선언하지 않고 초기화하려 하면 에러를 냄

식별자 검색

컨텍스트 안에서 식별자를 참조하려 하면 먼저 검색부터 해야 하며, 스코프 체인 앞에서 시작하여 스코프 체인을 따라 전역 컨텍스트에 도달할 때까지 검색 / 전역 컨텍스트에서도 식별자를 찾지 못하면 정의되지 않은 것으로 판단

변수 검색에도 비용이 들어가며 지역 변수는 스코프 체인을 검색할 필요가 없으므로 전역 변수보다 빨리 검색됨, 자바스크립트 엔진에서 식별자 검색을 최적화하고 있으므로 이런 차이가 무시할 정도로 좁혀질 수 있음

가비지 콜렉션

자바스크립트는 실행 환경에서 코드 실행 중에 메모리를 관리하는데 이런 의미에서 가비지 컬렉션 언어라고 불러도 됨, 필요한 메모리를 자동으로 할당하고 더 이상 사용하지 않는 메모리는 자동으로 회수하므로 개발자가 직접 메모리를 관리하지 않아도 됨
더 이상 사용하지 않을 변수를 찾아 해당 변수가 차지하고 있는 메모리를 회수, 사용하지 않는 변수를 식별하는 기준은 보통 두 가지 방법을 사용

표시하고 지우기

자바스크립트에서 가장 널리 쓰이는 방법으로, 가비지 컬렉터가 작동하면 메모리에 저장된 변수 전체에 표시를 남기고 컨텍스트에 있는 변수와 컨텍스트에 있는 변수가 참조하는 변수에 표시를 지움, 이 과정을 거치고 표시가 남아있는 변수는 삭제하고 메모리를 회수

참조 카운팅

각 값이 얼마나 많이 참조되었는지 추적, 변수에 참조 카운트를 두고 참조할 때마다 카운트를 늘리고 참조하는 변수가 다른 값을 할당하면 카운트를 줄임, 가비지 컬렉터가 실행할 때 참조 카운트가 0인 값에서 사용하던 메모리를 회수

IE8과 그 이전 버전에서는 객체 중 일부가 네이티브 자바스크립트 객체가 아님, BOM과 DOM의 객체들은 C++의 COM으로 구현되었는데 COM 객체는 가비지 컬렉션에 참조 카운팅 방식을 사용 (순환 참조 문제가 생길 수 있으므로 주의할 것)

성능

가비지 컬렉터는 주기적으로 실행되며 메모리 내에 할당된 변수가 많다면 상당한 비용이 드는 작업이므로 가비지 컬렉션을 실행하는 타이밍이 중요함

메모리 관리

일반적으로 가비지 컬렉션을 지원하는 프로그래밍 환경에서는 개발자가 메모리 관리를 신경쓰지 않아도 되지만, 자바스크립트라는 환경에서는 웹 브라우저에서 사용하기 때문에 가용 메모리가 매우 적음
가능한 한 최소한의 메모리만 사용해야 페이지 성능을 올릴 수 있음, 메모리 사용을 최적화하는 가장 좋은 방법은 코드 실행에 필요한 데이터만 유지하는 것, 필요없어진 데이터에는 null을 할당하여 참조를 제거하는 편이 좋음
변수에서 참조를 제거한다 해서 할당된 메모리가 자동으로 반환되는 것은 아니며 참조 제거의 요점은 값의 컨텍스트를 없애서 다음에 가비지 컬렉션을 실행할 때 해당 메모리를 회수

요약 (이것만 읽어도 됨 ^^)

원시 값과 참조 값
  • 원시 값은 고정된 크기를 가지며 스택 메모리에 저장
  • 원시 값을 한 변수에서 다른 변수로 복사하면 값 자체가 복사
  • 참조 값은 객체이며 힙 메모리에 저장
  • 변수에 참조 값을 저장하면 해당 변수는 객체에 대한 참조만 저장할 뿐 객체 자체를 저장하지는 않음
  • 참조 값을 한 변수에서 다른 변수로 복사하면 해당 객체에 대한 참조만 복사하므로 두 변수는 같은 객체를 참조
  • typeof 연산자는 값의 원시 타입을 판별하며 instanceof 연산자는 값의 참조 타입을 판별
실행 컨텍스트
  • 실행 컨텍스트에는 전역 컨텍스트와 함수 컨텍스트가 있음
  • 실행 컨텍스트에 진입할 때마다 스코프 체인이 만들어지며, 스코피 체인은 변수와 함수를 검색할 때 쓰임
  • 함수 컨텍스트는 해당 스코프에 있는 변수, 해당 스코프를 포함하는 컨텍스트에 있는 변수, 전역 컨텍스트에 있는 변수에 모두 접근할 수 있음
  • 전역 컨텍스트는 전역 컨텍스트에 있는 변수와 함수에만 접근할 수 있으며 로컬(함수) 컨텍스트에 있는 데이터에 직접적으로 접근할 수 없음
  • 실행 컨텍스트는 변수에 할당된 메모리를 언제 해제할 수 있는지 판단하는데 도움이 됨
가비지 컬렉션
  • 값이 스코프를 벗어나면 자동으로 표시되고 다음에 가비지 컬렉션을 실행할 때 삭제
  • 주로 쓰이는 가비지 컬렉션 알고리즘은 ‘표시하고 지우기’라 불리는데, 이 방법은 현재 사용하지 않는 값에 표시를 남겨서 메모리를 회수하는 방법
  • 다른 알고리즘은 참조 카운팅이라 불리는데, 이 방법은 값이 얼마나 많이 참조되었는지 추적하는 방법 / 거의 쓰이지 않지만 IE에서 DOM 요소처럼 네이티브 자바스크립트 객체가 아닌 객체를 접근해야 하기 때문에 쓰임
  • 참조 카운팅 알고리즘에는 순환 참조 문제가 있음
  • 변수에서 참조를 제거하면 순환 참조 문제도 해결할 수 있고 가비지 컬렉션에도 도움이 됨 / 가능한 한 많은 메모리를 회수해 효율적으로 관리하려면 전역 객체, 전역 객체의 프로퍼티, 순환 참조에 대한 참조를 제거해야 함