for...of 내부원리, 제네레이터, GC

devAnderson·2022년 1월 10일
1

TIL

목록 보기
21/103

for of의 내부원리

for of가 작동하기 위해서는 객체 내부적으로 [Symbol.iterator] 특수 내장 프로퍼티가 존재해야 한다.

let iterableObj = {
   name1 : "hello"
  .
  .
  [Symbol.iterator] : function // for of 호출시 이 프로퍼티의 값을 호출할 수 없으면 타입에러가 발생한다.
}

Symbol.iterator은 함수로, 호출하는 순간 이터레이터를 리턴한다.
이터레이터란 상태와, 그 상태를 변경할 수 있는 next 라는 함수명을 가진 메서드가 있는 객체를 뜻한다.

let iterator = iterableObj[Symbol.iterator]();
// { state, next }

for of는 이 이터레이터를 기반으로 반복을 진행하기 시작한다.
이때 next를 호출하는데, next 메서드는 value와 done이라는 프로퍼티를 보유한 객체를 리턴하는 함수로, 상태에 존재하는 값을 value로 가져온 후, 조건에 부합하지 않는다면 상태를 변경시킨다. 모든 반복이 종료되면 done이 true가 된다
만약, 특정 프로퍼티의 descriptor에 있는 enumerable이 false일 경우, for of의 반복 대상에서 제외된다.

let nextReturn = iterator.next();
// { value, done }

제네레이터

제네레이터는 이터레이터의 형태를 이용한 함수로, 특정 조건을 만족하기 전까지는 계속 next를 통해 반복적으로 값을 리턴할 수 있다

정확하게 말해서, 제네레이터 함수는 이터레이터를 프로토타입에 포함한 객체를 리턴하는 함수이다.
일반 함수는 return문을 통해 한번만 값을 리턴할 수 있지만, 제네레이터는 여러번 값을 리턴하는것처럼 만들 수 있다.
function* 라는 문구를 통해 제네레이터라고 표시할 수 있고, 프로토타입에 있는 이터레이터의 상태에 해당하는 값은 yield이다. 다만 여기서 next는 상태를 변경하기보다 value값에 yield에 표현된 값을 차례대로 가져온다.

> 제네레이터 함수가 리턴하는 제네레이터 객체의 내부구조

GC

보통 값의 할당과 같이 메모리에 공간을 확보하고 데이터를 넣었을 때 사용이 완료되어 더이상 쓸모없어진 (unreachable) 한 값들은 자동적으로 GC에서 수거하여 메모리를 다시 확보하는 기능이다.
쓸모없어진다는 의미는 도달이 불가능하다는 뜻이다. 즉, 더이상 해당 메모리의 값에 접근할 방도가 사라질 때를 뜻한다

let name = "hello"

자바스크립트가 돌아가고 있고 스크립트 내용에 해당 내용만 존재한다면, 메모리공간에 hello라는 값은 저장이 된 상태로 name이라는 식별자 포인터를 통해 접근이 가능한 상태이다.

하지만 만약 아래의 줄에 해당 내용이 추가가 되어있다면

name = "hello"

원시타입의 값들은 불변하고 고유하기 때문에 설령 동일한 hello값이라고 하더라도 해당 할당문이 뜻하는 것은

"야, 새로운 메모리 공간에 hello라는 값을 만들었으니, name 식별자 포인터는 이 새로운 hello의 메모리 주소를 가리켜라" 라는 소리가 된다.

따라서, 기존에 있던 다른 공간에 박혀있던 hello 값은 도달 불가능한 값이 된다. 이럴 때 메모리 공간을 차지하는 해당 값은 더이상 필요가 없으므로 GC의 수거대상이 된다.

GC의 순환참조

자바스크립트에서 객체는 레퍼런스 타입이다. 즉, 서로가 서로의 객체에 대한 레퍼런스를 공유가 가능하다.
이때 GC가 어떻게 동작하는지를 잘 살펴보면,
1. 우선 루트에 대한 마크업을 한다 (루트란, 전역변수나 함수의 매개변수와 같이 명확한 이유없이는 사라지지 않을 메모리의 값들을 뜻한다)
2. 이 마크업된 루트가 참조하는 메모리들을 마크한다
3. 계속해서 마킹을 진행하며 도달이 되지 않는 장소까지 마크를 반복한다
4. 마크가 되지 않은 값은 GC의 수거대상이 되어 메모리에서 빠져나간다.

이 대표적인 예시를 아래로 들자면

레퍼런스 타입이 서로 참조할 때의 형태

father이 null이 되는 순간 mother의 hasband 역시 null이 될 것 같아보이지만 그렇지 않을 것을 알 수 있다.
이것의 이유는 명백하다.
father와 mother이 가리키는 것은 동일한 객체의 메모리 레퍼런스주소였다. 하지만 father포인터가 null이라는 새로운 값의 메모리 주소를 포인팅한다고 하더라도 mother의 hasband가 아직 그 메모리 주소를 포인팅 하고 있기 때문에 GC의 대상이 아니었고, 살아남은 것이다.

만약 mother.hasband 역시 null이 되었다면, 해당 { name: "John" } 와 연관된 메모리들은 수거대상이 되어 사라지게 된다

profile
자라나라 프론트엔드 개발새싹!

0개의 댓글