자바스크립트 기술면접 6편

Genie·2021년 6월 2일
1
post-thumbnail
  • 클로저(closure)

클로저는 함수와 그 함수가 선언된 렉시컬 환경(상위 스코프를 의미하는 실행 컨텍스트의 렉시컬 환경)과의 조합이다.

자신을 포함하고 있는 외부함수보다 중첩함수가 더 오래 유지되는 경우 외부함수 밖에서 중첩함수를 호출하더라도 외부함수의 지역변수에 접근할 수 있는데 이러한 함수를 클로저라고 한다.

클로저는 중첩함수가 자유변수를 참조하고 있고, 중첩 함수가 외부 함수보다 더 오래 유지되는 경우에 한정하는 것이 일반적이다.


  • 클로저를 사용하는 이유

클로저는 상태를 안전하게 은닉하고 특정함수에게만 상태 변경을 허용하기 위해 사용한다.


  • 렉시컬 스코프(정적 스코프)

렉시컬 환경의 "외부 렉시컬 환경에 대한 참조"에 저장할 참조값,

즉 상위 스코프에 대한 참조는 함수 정의가 평가되는 시점에 함수가 정의된 환경(위치)에 의해 결정된다.


  • 함수 객체와 내부 슬롯 [[Environment]]

상위 스코프를 기억하기 위해 함수는 자신의 내부 슬롯 [[Environment]]에 상위 스코프의 참조를 저장한다.


  • 자바스크립트 클래스

클래스는 함수이며 기존 프로토타입 기반 패턴을 클래스 기반 패턴처럼 사용할 수 있도록하는 문법적 설탕 또는 프로토타입 기반의 객체 생성 메커니즘이다.

클래스는 함수이다. 따라서 클래스는 값처럼 사용할 수 있는 일급객체이다.


  • 문법적 설탕

내부적인 동작은 기존과 동일하지만, 어떤 구현에 맞추어 새로운 문법을 제공하는 경우를 가리킨다.


  • 클래스와 생성자 함수의 차이점
  1. 클래스를 new 연산자 없이 호출하면 에러가 발생한다. 하지만 생성자 함수를 new 연산자 없이 호출하면 일반 함수로서 호출된다.
  2. 클래스는 상속을 지원하는 extends와 super 키워드를 제공한다.
  3. 클래스는 호이스팅이 발생하지 않는 것처럼 동작한다. 하지만 함수 선언문으로 정의된 생성자 함수는 함수 호이스팅이, 함수 표현식으로 정의한 생성자 함수는 변수 호이스팅인 발생한다.
  4. 클래스 내의 모든 코드는 암묵적으로 strict mode가 지정되어 실행되며 strict mode를 해제할 수 없다. 하지만 생성자 함수는 암묵적으로 strict mode가 지정되지 않는다.
  5. 클래스의 constructor, 프로토타입 메서드, 정적 메서드는 모두 프로퍼티 어트리뷰트 [[Enumerable]]의 값이 false이다. 즉, 열거되지 않는다.

  • 클래스 몸체에 정의할 수 있는 메서드
  1. constructor(생성자)
  2. 프로토타입 메서드
  3. 정적 메서드

  • 정적 메서드와 프로토타입 메서드의 차이
  1. 정적 메서드와 프로토타입 메서드는 자신이 속해 있는 프로토타입 체인이 다르다.
  2. 정적 메서드는 클래스로 호출되고 프로토타입 메서드는 인스턴스로 호출된다.
  3. 정적 메서드는 인스턴스 프로퍼티를 참조할 수 없지만, 프로토타입 메서드는 인스턴스 프로퍼티를 참조할 수 있다. (정적 메서드에서 this를 사용할 경우, this는 클래스를 가리킨다.)

  • 클래스의 인스턴스 생성과정
  1. 인스턴스 생성과 this 바인딩
  2. 인스턴스 초기화(옵션)
  3. 인스턴스가 바인딩된 this가 암묵적으로 반환

  • 클래스 필드 정의 제안

클래스 몸체는에는 메서드만 선언할 수 있지만, 클래스 필드 정의 제안은 클래스 필드를 클래스 몸체에 정의할 수 있다.

클래스에 프로퍼티를 추가할 수 있는 문법이다.

class Person {
  // 클래스 필드에 문자열을 할당 => 인스턴스 프로퍼티
  name: 'Kim';

  // 클래스 필드에 함수를 할당 => 인스턴스 메서드(권장X)
  getName = () => this.name;
}

const me = new Person();
console.log(me); // Person { name: "Kim", getName: f }
console.log(me.getName()); // Kim

  • private 필드 정의 제안
class Person {
  #name = '';

  constructor(name) {
    this.#name = name;
  }
}

const me = new Person('Kim');

// private 필드 #name은 클래스 외부에서 참조할 수 없다.
console.log(me.#name);
// SyntaxError: Private field '#name' must be declared in an enclosing class


  • static 필드 정의 제안
class MyMath {
  // static public 필드 정의
  static PI = 22 / 7;

  // static private 필드 정의
  static #num = 10;

  // static 메서드
  static increment() {
    return ++MyMath.#num;
  }
}

console.log(MyMath.PI); // 3.142857142857143
console.log(MyMath.increment()); // 11

  • 상속에 의한 클래스 확장

상속에 의한 클래스 확장은 코드 재사용 관점에서 매우 유용하다.

클래스는 상속을 통해 다른 클래스를 확장할 수 있는 문법인 extends 키워드가 제공된다.

상속을 통해 확장된 클래스를 서브클래스, 파생 클래스, 자식 클래스라 부르고 서브 클래스에게 상속된 클래스를 수퍼 클래스, 베이스 클래스, 부모 클래스라고 부른다.


  • 동적 상속
function Base1() {}

class Base2 {}

const condition = true;

class Derived extends (condition ? Base1 : Base2) {}

const derived = new Derived();

console.log(derived); // Derived {}

console.log(derived instanceof Base1); // true
console.log(derived instanceof Base2); // false

  • super 키워드

함수처럼 호출할 수도 있고, this와 같이 식별자처럼 참조할 수 있는 특수한 키워드

super를 호출하면 수퍼클래스의 constructor를 호출한다.

super를 참조하면 수퍼클래스의 메서드를 호출할 수 있다.

ES6 메서드가 아닌 함수는 super 키워드를 사용할 수 없다. ES6 메서드가 아닌 함수는 내부 슬롯 [[HomeObject]]를 갖지 않기 때문이다.


  • 상속클래스의 인스턴스 생성과정
  1. 서브클래스의 super 호출

    서브클래스는 자신이 직접 인스턴스를 생성하지 않고 수퍼클래스에게 인스턴스 생성을 위임한다. 이것이 바로 서브 클래스의 constructor에서 반드시 super를 호출해야 하는 이유이다.

  2. 수퍼클래스의 인스턴스 생성과 this 바인딩

    생성한 인스턴스는 서브클래스가 생성된 것으로 처리된다.

  3. 수퍼클래스의 인스턴스 초기화

  4. 서브클래스 constructor로의 복귀와 this 바인딩

    super가 반환한 인스턴스가 서브클래스 this에 바인딩된다. 서브클래스는 별도의 인스턴스를 생성하지 않고 super가 반환한 인스턴스를 this에 바인딩하여 그대로 사용한다.

    ⇒ 이처럼 super가 호출되지 않으면 인스턴스가 생성되지 않으며, this 바인딩도 할 수 없다. 서브클래스의 constructor에서 super를 호출하기 전에는 this를 참조할 수 없는 이유가 바로 이 때문이다.

  5. 서브클래스의 인스턴스 초기화

  6. 인스턴스 반환


  • 표준 빌트인 생성자 함수 확장
class MyArray extends Array {
  uniq() {
    return this.filter((v, i, self) => self.indexOf(v) === i);
  }

  average() {
    return this.reduce((pre, cur) => pre + cur, 0) / this.length;
  }
}

const myArray = new MyArray(1, 1, 2, 3);
console.log(myArray); // MyArray(4) [ 1, 1, 2, 3 ]
console.log(myArray.uniq()); // MyArray(3) [ 1, 2, 3 ]
console.log(myArray.average()); // 1.75

MyArray 클래스가 생성한 인스턴스는 Array.prototype과 MyArray.prototype의 모든 메서드를 사용할 수 있다.


  • ES6 함수의 종류(아래 3가지 외에도 제너레이터와 async 함수가 존재함)


  • 화살표 함수

기존의 함수 정의방식보다 간략하게 함수를 정의한 함수

표현만 간략한 것이 아니라 내부 동작도 기존의 함수보다 간략하다.

특히 화살표 함수는 콜백 함수 내부에서 this가 전역 객체를 가리키는 문제를 해결하기 위한 대안으로 유용하다.


  • 화살표 함수와 일반 함수의 차이
  1. 화살표 함수는 인스턴스를 생성할 수 없는 non-constructor이다.

  2. 화살표 함수는 중복된 매개변수 이름을 선언할 수 없다.

  3. 화살표 함수는 함수 자체에 this, arguments, super, new.target 바인딩을 갖지 않는다.

    ⇒ 따라서 화살표 함수 내부에서 this, arguments, super, new.target을 참조하면 상위 스코프의 것을 참조한다.


  • Rest 파라미터

매개변수 앞에 세개의 점을 붙여서 정의한 매개변수를 의미한다.

Rest 파라미터는 함수에 전달된 인수들의 목록을 배열로 전달받는다.

const sum = (...args) => args.reduce((pre, cur) => pre + cur, 0);

console.log(sum(1, 2, 3, 4, 5)); // 15
profile
front-end engineer

0개의 댓글