[JS] 렉시컬환경

heyhey·2023년 3월 22일
0

JavaScript

목록 보기
13/14

JS 는 함수 지향 언어입니다.
함수를 동적으로 생성할 수 있고, 함수 안으로 다른 인수도 넘길 수 있고,
다른곳에서 호출해서 사용할 수도 있습니다.

함수 내부에서 함수 외부에 있는 변수에 접근할 수도 있습니다.
하지만 함수가 생성된 이후에 외부 변수가 변경되면 어떻게 될까요?

변수는 변할까요? 아니면 이전의 값을 가질까요?

중첩함수

nested 함수라고 부릅니다.
함수 내부에서 선언한 함수를 뜻합니다.

일반적으로 많이 사용하는 방식이 아닌 특별한 방식으로 사용해보겠습니다

const makeCounter = () => {
  let count = 0;
  const plus = () => count++;
  return plus;
};

결과

plus1 = makeCount();
plus1(); // 0
plus1(); // 1
plus1(); // 2

?? 이게 어떻게 이렇게 될까요?

중첩함수는 새로운 객체의 프로퍼티 형태나 중첩함수 그 자체로 반환될 수 있습니다.
이렇게 반환된 중첩함수는 어디서든 호출해 사용할 수 있습니다.

여기서 plus 함수를 한개 더 만듭니다.

plus2 = makeCount();
plus2(); // 0
plus1(); // 3

새로 생성한 함수에서는 count 가 0 이 되었습니다.
이 함수들은 서로 독립적이란 것을 알 수 있습니다.

렉시컬 환경


명확한 이해를 돕기 위해 단계를 나눠서 진행해보도록 하겠습니다.

1. 변수

JS 에서 실행중인 함수, 코드 블록, 스크립트 전체는 렉시컬 환경 이라 불리는 내부 숨김 연관 곅체를 갖습니다

렉시컬 환경은 두 부분으로 구성됩니다.

  1. 환경 레코드 - 모든 지역 변수를 프로퍼티로 저장하고 있는 객체입니다.
    this 도 여기에 저장됩니다.

  2. 외부 렉시컬에 대한 참조 - 외부 코드와 연관됩니다.

변수또한 환경 레코드의 프로퍼티일 뿐!

Lexical Enviroment
[ x:3 ]

이렇게 스크립트 전체와 관련된 렉시컬 환경은 global Lexical Environment라고 합니다.

[] 괄호 가 변수가 저장되는 환경 레코드이고, 외부로 참조를 안하기 때문에 외부 렉시컬 환경은 null 입니다.


이번에는 코드를 선언하고 변경할때의 렉시컬 환경을 알아봅니다.

  1. ----- // x:
  • 스크립트가 시작되면 스크립트 내에서 선언한 변수 전체가 렉시컬 전체에 올라갑니다.
  • 이때 변수의 상태는 특수 내부 상태인 uninitialized 가 됩니다.
  • 상태를 인식하긴 하지만 let을 만나기 전까진 변수를 참조할 수 없습니다.
  1. let x // x: undefined
  • 값을 할당하기 전까진 undefined입니다.
  1. x='hi' // x: 'hi'
  • 값을 할당했습니다.

변수는 특수 내부 객체인 환경 레코드의 프로퍼티입니다.
환경 레코드는 현재 실행중인 함수와 코드 블록, 스크립트와 연관되어 있습니다.
변수를 변경하면 환경 레코드의 프로퍼티가 변경됩니다.

렉시컬 환경은 이론상의 객체입니다.
렉시컬 환경을 얻거나 조작하는 것은 불가합니다.
JS 엔진들은 엔진 고유의 방법을 사용해 렉시컬 환경을 최적화합니다.
변수를 버리거나 트릭을 써서 메모리를 절약합니다.

함수 선언문

함수선언은 일반 변수와 비교했을 때 바로 초기화 된다는 차이가 있습니다.

함수 선언문으로 선언한 함수는 렉시컬 환경이 만들어지는 즉시 사용할 수 있습니다.

변수때와 똑같이 선언하고 어떻게 달라지나 봅시다

  1. ----- // func : function, x:

  2. function func(){}

  3. x='hi' // x: 'hi'

함수를 선언하지 않았을 때도, func은 이미 함수라고 나옵니다.

주의할 점은, let func = function(){..} 처럼 선언했을 때는 해당하지 않습니다.

내부와 외부 렉시컬 환경

함수를 호출해 실행하면 새로운 렉시컬 환경이 만들어집니다.
이 렉시컬 환경엔 매개변수와 함수의 지역변수가 저장됩니다.

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

  • 내부 렉시컬 환경 : 현재 실행중인 함수에 상응
    • 파라미터인 name만 존재함
  • 외부 렉시컬 환경 : 전역 렉시컬 환경인 phrase 와 say 가 존재합니다.

내부 렉시컬 환경은 외부 렉시컬 환경에 대한 참조를 갖습니다.

코드에서 변수에 접근하면 먼저 내부 렉시컬 환경을 검색하고,
발견하지 못하면 외부 환경으로 확장합니다.
이 과정은 전역 환경으로 확장할때까지 반복합니다.

4. 함수를 반환하는 함수

요놈을 다시 들고옵니다.

const makeCounter = () => {
let count = 0;
return fuction(){
	  return count++
};
};

makeCounter 를 호출하게 되면 할 때마다 새로운 렉시컬 환경이 만들어지게 됩니다.
그리고 여기에 이 함수를 실행하는데 필요한 변수들이 저장되게 됩니다.

이 안에서의 중첩함수에서도 렉시컬 환경에 대한 참조가 저장되게 됩니다.


그래서 plus.Environment 에는 {count:0} 이 있는 렉시컬 환경에 대한 참조가 저장되게 됩니다.
호출 장소와 상관없이 자신이 생성된 곳을 기억할 수 있는 Environment 덕분입니다.

이 Environment 는 함수 생성시에만 세팅되고 변하지 않습니다.

count 변수를 찾는데 렉시컬 환경변수에서 찾습니다. 중첩함수 안의 렉시컬 환경은 비어있고, 그 밖의 환경에서 count를 찾았습니다. 변수값 갱신은 변수가 저장된 렉시컬 환경에서 이루어지게 됩니다.

클로저

클로저는 외부 변수를 기억하고 이 외부 변수에 접근할 수 있는 함수를 뜻합니다.

자바스크립트는 모든 함수에서 자연스럽게 클로저가 가능합니다.
왜?
JS 함수는 숨김 프로퍼티를 통해서 자신이 어디서 만들어졌는지를 기억합니다.
그리고 함수에서는 그 프로퍼티를 통해 외부 변수에 접근합니다.

가비지 컬렉션

함수 호출이 끝나면 함수에 대응하는 렉시컬 환경이 메모리에서 제거됩니다.
함수와 관련된 변수도 이때 다 사라지기 때문에 관련 변수를 참조할 수 없습니다.

즉 JS에서 모든 객체는 도달 가능할 때만 메모리에 유지됩니다.

앞에서 만든 함수를 다시 보겠습니다.

const makeCounter = () => {
let count = 0;
return fuction(){
	  return count++
};
};
let fun = makeCounter();

이런 중첩함수를 사용할때는 주의해야합니다.
여러번 fun을 호출하게 되면 각 레시컬 환경은 메모리에서 삭제되지 않기 때문입니다.

이런 상황에서는 도달할 수 없는 상황을 만들어줘야 메모리에서 삭제됩니다.

fun = null

최적화 프로세스

함수가 살아있는 동안에는 이론상으로는 외부 변수가 모두 메모리에 유지됩니다.
하지만 JS 엔진이 지속해서 최적화해서 분석해 외부변수가 사용되지 않으면 삭제하게 됩니다.

Reference : https://ko.javascript.info/closure

profile
주경야독

0개의 댓글