본문 바로가기

자바스크립트

[Javascript] - 렉시컬 환경

변수

 

- 자바스크립트에선 실행 중인 함수 코드 블록, 스크립트 전체는 렉시컬 환경이라 불리는 내부 숨김 연관 객체를 갖는다.

-  렉시컬 환경 객체는 두 부분으로 구성된다.

 

1. 환경 레코드 : 모든 지역 변수를 프로퍼티로 저장하고 있는 객체이다 this 값과 같은 기타 정보도 여기에 저장된다.

 

2. 외부 렉시컬 환경에 대한 참조: 외부 코드와 연관된다 

 

변수는 특수 내부 객체인 환경 레코드의 프로퍼티일 뿐이다.
변수를 가져오거나 변경 하는 것은 환경 레코드의 프로퍼티를 가져오거나 변경 함을 의미한다.

 

 

위 두 줄짜리 코드엔 렉시컬 환경 객체가 하나만 존재한다.

이렇게 스크립트 전체와 관련된 렉시컬 환경은 전역 렉시컬 환경이라고 한다. 위 예제에서 네모 상자는 변수가 저장되는 환경 레코드를 나타내고 붉은 화살표는 외부 렉시컬 환경에 대한 참조를 나타낸다.

전역 렉시컬 환경은 외부 참조를 갖지 않기 때문에 화살표가 null을 가리키는 것을 확인할 수 있다.

 

 

위 예제에서 네모 상자들은 코드가 한 줄 한 줄 실행될 때마다 전역 렉시컬 환경이 어떻게 변화하는지 보여준다.

 

1 스크립트가 시작되면 스크립트 내에서 선언한 변수 전체가 렉시컬 환경에 올라간다 이때 변수의 상태는 특수 내부 상태가 된다(uninitialized) 자바스크립트 엔진은  uninitialized 상태의 변수를 인지하긴 하지만 let을 만나기 전까지 이 변수를 참조할 수 없다.

 

2 let phrase가 나타났다 아직 값을 할당하기 전이기 때문에 프로퍼티 값은 undefined 이다 phrase 는 이 시점 부터 사용할 수 있다.

 

3 phrase에 Hello 가 할당됐다.

4 phrase에 Bye 가 할당됐다.

 

요약하자면 

 

1 변수는 특수 내부 객체인 환경 레코드의 프로퍼티이다 환경 레코드는 현재 실행 중인 함수와 코드 블록 스크립트와 연관되어 있다 .

 

2 변수를 변경하면 환경 레코드의 프로퍼티가 변경된다.

 

 

- 렉시컬 환경은 명세서에서 자바스크립트가 어떻게 동작하는지 설명하는데 쓰이는 이론상의 객체이다 따라서 코드를 사용해 직접 렉시컬 환경을 얻거나 조작하는 것은 불가능 하다.

 

- 자바스크립트 엔진들은 명세서에 언급된 사항을 준수하면서 엔진 고유의 방법을 사용해 렉시컬 환경을 최적화 한다 사용하지 않는 변수를 버려 메모리를 절약하거나 다양한 내부 트릭을 써서 말이다.

 

함수 선언문

 

- 자바스크립트에서 함수는 변수와 마찬가지로 값이다 다만 함수 선언문으로 선언한 함수는 일반 변수와는 달리 바로 쵝화 된다는 점에서 차이가 있다 .

 

- 함수 선언문으로 선언한 함수는 렉시컬 환경이 만들어지는 즉시 사용할 수 있다 변수는 let을 만나 선언이 될 때 까지 사용할 수 없다는점과 비교해 차이가 있다.

 

- 따라서 함수를 선언되기 전에도 함수를 사용할 수 있는 것은 바로 이 때문이다 .

 

 

 

- 위 예제는 스크립트에 함수를 추가했을 때 전역 렉시컬 환경 초기 상태가 어떻게 변하는지 보여준다.

- 이러한 동작 방식은 함수 선언문으로 정의한 함수에만 적용 된다.

- let say = function (name){...} 과 같은 함수 표현식은 해당하지 않는다.

 

내부와 외부 렉시컬 환경

- 함수를 호출해 실행하면 새로운 렉시컬 환경이 자동으로 만들어진다 이 새롭게 만들어진 렉시컬 환경엔 함수 호출 시 넘겨받은 매개변수와 함수의 지역변수가 저장된다.

 

 

- say('John')을 호출하면 위와 같은 내부 변화가 일어난다 

 

- 함수가 호출 중인 동안에 호출 중인 함수를 위한 내부 렉시컬 환경과 그 내부 렉시컬 환경이 가리키는 외부 렉시컬 환경을 갖게 된다.

 

 

1 예시의 내부 렉시컬 환경은 현재 실행 중인 함수만 say에 상응한다 내부 렉시컬 환경엔 함수의 인자인 name으로부터 유래한 프로퍼티 하나만 있다 say('John') 을 호출했기 때문에 name의 값은 John이 된다.

 

2 예시의 외부 렉시컬 환경은 전역 렉시컬 환경이다 전역 렉시컬 환경은 phrase와 함수 say를 프로퍼티로 갖는다

 

- 코드에서 변수에 접근할 땐 먼저 내부 렉시컬 환경을 검색 범위로 잡는다 내부 렉시컬 환경에서 원하는 변수를 찾지 못하면 검색 범위를 내부 렉시컬 환경이 참조하는 외부 렉시컬 환경으로 확장한다 이 과정은 검색 범위가 전역 렉시컬 환경으로 확장될 때까지 반복한다.

 

- 전역 렉시컬 환경에 도달할 때까지 변수를 찾지 못하면 엄격 모드에선 에러가 발생한다 참고로 비 엄격 모드에서는 정의되지 않은 변수에 값을 할당하려고 하면 에러 발생 대신 새로운 전역 변수가 만들어 진다

 

위 예제를 다시 정리하자면

 

1 함수 say 내부의 alert에서 name에 접근할 땐 먼저 내부 렉시컬 환경을 살펴본다 내부 렉시컬 환경에서 변수 name을 찾았다 .

 

2 alert에서 변수 phrase에 접근하려는데 phrase에 상응하는 프로퍼티가 내부 렉시컬 환경에는 없다 따라서 검색 범위는 외부 렉시컬 환경으로 확장된다 외부 렉시컬 환경에서 phrase를 찾았다.

 

 

함수를 반환하는 함수

 

- makeCounter()를 호출하면 호출할 때마다 새로운 렉시컬 환경 객체가 만들어지고 여기에 makeCounter를 실행하는데 필요한 변수들이 저장된다.

 

 

위 say 예제 코드와는 다르게 makeCounter에는 차이점이 하나 있다 makeCounter가 실행되는 도중엔 본문이 한줄 짜리인 중첩 함수가 만들어진다는 점이다 

 

여기서 중요한 사실 하나는 모든 함수는 함수가 생성된 곳의 렉시컬 환경을 기억한다는 점이다 함수는 [[Environment]]라 불리는 숨김 프로퍼티를 갖는데 여기에 함수가 만들어진 곳의 렉시컬 환경에 대한 참조가 저장된다.

 

 

 

따라서 counter.[[Environment]]외엔 {counter: 0}이 있는 렉시컬 환경에 대한 참조가 저장된다.

호출 장소와 상관없이 함수가 자신이 태어난 곳의 렉시컬 환경을 기억할 수 있는 건 바로 [[Environment]] 프로퍼티 덕분이다 [[Environment]] 는 함수가 생성될 때 딱 한번 값이 세팅되고 영원히 바뀌지 않는다.

 

 

counter를 호출하면 각 호출마다 새로운 렉시컬 환경이 생성된다 그리고 이 새롭게 생성된 렉시컬 환경은 counter.[[Environment]]에 저장된 렉시컬  환경을 외부 렉시컬 환경으로 참조한다.

 

실행 흐름이 중첩 함수의 본문으로 넘어오면 count 변수가 필요한데 먼저 자체 렉시컬 환경에서 변수를 찾는다 익명 중첩 함수엔 지역 변수가 없기 때문에 이 렉시컬 환경은 비어있는 상황이다  

 

counter() 렉시컬 환경이 참조하는 외부 렉시컬 환경에서 count를 찾느다 makeCounter 렉시컬 환경에는 count가 존재한다.

 

이제 count++ 가 실행되면서 count 값이 1 증가해야 하는데 변숫값 갱신은 변수가 저장된 렉시컬 환경에서 이뤄진다.

 

실행이 종료된 후의 상태는 그림과 같다

counter를 여러번 호출하면 count 변수가 2, 3 으로 증가하는 이유가 바로 여기에 있다.

 

 

클로저 

 

클로저는 반환된 내부 함수가 자신이 선언되었을 때의 렉시컬 스코프 환경을 기억하며 자신이 선언됐을 때의 환경 밖에서 호출되어도 그 환경에 접근할 수 있는 함수이다 

 

몇몇 언어에선 클로저를 구현하는게 불가능하거나 특수한 방식으로 함수를 작성해야 클로저를 만들 수 있다 하지만 자바스크립트에선 모든 함수가 자연스럽게 클로저가 된다.