Frontend Engineer - 박준형

Published on

클래스 Deep Dive

Authors
  • Name
    Twitter

클로저

클로저를 알기전에 자바스크립트가 렉시컬 스코프를 따르는 프로그래밍 언어인걸 알아야한다.

렉시컬 스코프

자바스크립트 엔진은 함수를 어디서 호출했는지가 아니라 함수를 어디에 정의했는지에 따라 상위 스코프를 결정하고 이를 렉시컬 스코프라 칭한다.

여기서 함수의 상위 스코프를 결정한다는것은 렉시컬 환경의 외부 렉시컬 환경에 대한 참조에 저장하게될 참조값을 결정한다는말과 일치한다.

이를 바꿔말하면 아래처럼 말할 수 있다.

렉시컬 환경의 외부 렉시컬 환경에대한 참조에 저장할 참조값은 함수정의가 평가되는 시점에 함수가 정의된 환경에의해 결정되고 이것이 렉시컬 스코프다.

클로저와 렉시컬 환경

const x = 1

function outer() {
  const x = 10
  const inner = function () {
    console.log(x)
  }

  return inner
}
const innerFunc = outer()

innerFunc()

outer 함수 호출하면 함수내부가 평가되며inner 함수는 자신이 평가될때 자신이 정의된 위치에 의해 결정된 상위 스코프를 [[Environment]] 내부슬롯에 저장한다. 이때 저장한 상위스코프는 함수가 존재하는 한 유지된다.

outer 함수는 inner 함수를 반환하며 생명주기가 마감된다. 즉 outer 함수의 실행컨텍스트는 pop 되는것인데 이떄 outer 함수의 지역변수 x또한 생명주기를 다하게될것이다. 하지만 innerFunc를 호출하면 x의값이 10으로 잘 표현된다.

inner 함수가 전역변수 innerFunc에 의해 참조되고있으므로 outer함수의 실행컨텍스트가 제거되더라도 outer 함수의 렉시컬 환경까지 소멸한것은 아니다. 가비지 컬렉터는 누군가가 참조하고 있는 메모리 공간을 함부로 해치지않는다

클로저는 상태를 의도치않게 변경되지 않도록 안전하게 은닉하고 특정함수에게만 상태 변경을 허용하여 상태를 안전하게 변겨하고 유지하기 위해 사용된다

클래스

대부분의 객체지향 프로그래밍 언어는 클래스를 정의하고 멤버에 접근 제한자를 선언하여 공개 범위를 한정할 수 있다. 하지만 자바스크립트는 접근 제한자를 제공하지 않는다.

ES5이전에는 클래스 문법을 제공하지않았고 ES6이상의 문법부터 클래스를 제공하게되었다. 그렇다고 ES6의 클래스가 기존의 프로토타입 기반 객체지향 모델을 폐지하고 새롭게 클래스 기반 객체지향 모델을 제공하는것이 아니다. 사실 클래스는 함수이며 기존 프로토타입 기반 패턴을 클래스 기반 패턴처럼 사용하게 하는 Syntactic sugar라고 본다.

클래스는 기존 프로토타입 기반 객체 생성 패턴의 단순한 신태틱 슈가라기보단 새로운 객체 생성 메커니즘이라고 보는편이 좋다.

인스턴스 생성

클래스는 생성자함수라고했다. 그러다보니 new 연산자와 함께 호출되어 인스턴스를 생성한다.

클래스는 생성자함수와 프로토타입메서드, 정적메서드로 구성된다

정적메서드와 프로타타입메서드의 차이는 무엇일까?

  • 정적메서드와 프로토타입메서드는 자신이 속해있는 프로토타입 체인이 다르다
  • 정적메서드는 클래스로 호출되고 프로토타입메서드는 인스턴스로 호출된다.
  • 정적메서드는 인스턴스 프로퍼티를 참조할 수 없지만, 프로토타입메서드는 인스턴스 프로퍼티를 참조할 수 있다.

인스턴스 생성 과정

  1. new 연산자와 함께 호출하면 생성자함수 코드가 실행되기 전에 앞서 암묵적으로 빈객체가 생성된다. 이 빈 객체가 클래스가 생성한 인스턴스가 된다. 이떄 클래스가 생성한 인스턴스의 프로토타입으로 클래스의 prototype 프로퍼티가 가리키는 객체가 설정된다. 인스턴스는 this에 바인딩되며 생성자함수 내부의 this는 클래스가 생성한 인스턴스를 가리키게된다.
  2. 생성자함수가 실행되어 this에 바인딩 되어있는 인스턴스를 초기화한다. 인스턴스에 프로퍼티를 추가하고 생성자함수가 전달받은 초기값으로 인스턴스의 값을 초기화한다. 만약 생성자함수가 생략되었다면 이 과정도 생략된다.
  3. 모든 처리가 끝나면 완성된 인스턴스가 바인딩된 this가 암묵적으로 반환된다.

상속관계에 있는 클래스는 어떻게 인스턴스를 생성하는지 알아보자

  1. 수퍼클래스와 서브클래스를 구분하기위해 [[ConstructorKind]]라는 내부슬롯을 가진다. 다른 클래스를 상속받지않는 클래슨느 base로 설정되지만, 상속받는 서브클래스는 derived로 설정이된다. 그리고 서브클래스는 자신이 직접 인스턴스를 생성하지 않고 수퍼클래스에게 인스턴스 생성을 위임한다.
  2. 수퍼클래스의 생성자함수 내부의 코드가 실행되기 이전에 암묵적으로 빈 객체를 생성하고 이 빈객체가 클래스가 생성한 인스턴스가 된다. 따라서 수퍼클래스의 생성자함수 내부의 this는 생성된 인스턴스를 가리킨다.
  3. 수퍼클래스의 생성자함수 코드가 실행되며 this에 바인딩 되어있는 인스턴스를 초기화한다.
  4. super의 호출이 종료되고 제어 흐름이 서브클래스의 생성자함수 내부로 돌아온다. 이때 super가 반환한 인스턴스가 서브클래스 this에 바인딩된다. 서브클래스는 별도의 인스턴스를 생성하지않고 그대로 사용한다.
  5. 서브클래스의 생성자함수 코드가 실행되며 this에 바인딩되어있는 인스턴스에 프로퍼티를 추가하고 초기화한다.