This week I Learned 3

주영택·2020년 2월 21일
0

This Week What I Learned

목록 보기
2/50

5%

올해 5% 가 지남

구조분해 할당과 this

FastBus 인스턴스를 destruction 하는 경우 발생하는 오류 정리

https://github.com/fastcampusgit/v2/blob/develop/shared/test/util/bus.spec.js 에서 실패하는 경우의 코드를 참조.

최신 자바스크립트 문법 중 destruction 이란 것이 있는데 구조 분해 할당을 가능하게 한다. 예를 들면 다음과 같다.

const o = { id: 1, name: 2 }
const { id, name } = o
console.assert(id === 1, 'should id is 1')
console.assert(name === 2, 'should name is 2')

구조 분해 할당은 주로 객체나 배열에 사용하는데 디폴트 값 지정 기능과 읽기 쉬운 코드를 만드는데 도움을 준다.
하지만 객체가 this 를 사용하고 있는 경우는 좀 애매한 부분이 있다. 이 때문에 구조 분해 할당을 맘대로 쓰면 곤란하다.

자바스크립트의 this

메소드 안에서 this 가 참조하는 곳은 크게 두 부분으로 나눌 수 있다. 하나는 new 연산자로 생성된 객체 또는 함수의 인스턴스.

class World {
  constructor(id) {
    this.id = id;
  }
  show() {
    return this.id;
  }
}

const w = new World(2);

위 코드에서 w 안의 this 는 World 인스턴스의 this 이다.

그리고 . 으로 호출한 메소드의 객체이다.

const o = {
  id: 1,
  show() {
    return this.id;
  }
};

o.show();

이와 함께 bind, apply, call 등의 Object 메소드를 사용해 this 를 바인딩 할 수 있다.

인스턴스의 구조 분해 할당

문제가 되는 부분은 이렇게 this 를 사용하는 경우에 발생하는 구조 분해 할당을 잘못 사용하게 되면 this 를 참조하지 못하는 상황이 생긴다는 것이다.

흔히 볼 수 있는 간단한 객체를 하나 만들고 생성해 사용해 보자.

class Dog {
  constructor(opt) {
    this.age = opt.age
    this.say = 'bark'
  }
  greet() {
    return this.say;
  }
}
const puppy = new Dog({ age: 1 })
puppy.greet();

greet 의 this 는 puppy 를 참조한다. 하지만 다음처럼 코드를 수행하면 this 는 greet 를 참조할 수 없다.

const { greet } = new Dog({ age: 1 });
greet();  // TypeError: Cannot read property 'say' of undefined

해결 방법 0 - 구조 분해 할당 사용 제한

쉽다.

해결 방법 1 - this 를 참조하는 변수를 선언

다른 하나는 객체가 생성될 때 각 메소드가 생성되는 객체의 this 를 참조하도록 강제하는 방법이다.
ugly 하지만 객체 생성시 this 를 참조하는 식별자를 넣자. 예전에 보던 var that = this; 의 재림이다.

class Dog {
  constructor(opt) {
    this.instance = this;
    this.age = opt.age;
    this.say = "grrrr";
  }
  greet() {
    return this.say;
  }
}
const { instance, greet } = new Dog({ age: 1 });
// greet.bind(instance)()
// greet.apply(instance)
greet.call(instance);

그리고 인스턴스의 this 를 가지고 있는 instance 를 bind, apply, call 등의 메소드로 호출하면 원하는 실행 결과를 얻을 수 있다.

기왕 생성자에 instance 를 추가한 김에 조금 더 개선할 수 있다. 팩토리 메소드를 사용하는 것이다.

class Dog {
  constructor(opt) {
    this.instance = this;
    this.age = opt.age;
    this.say = "mung";
  }
  greet() {
    return this.say;
  }
  static create(opt) {
    const dog = new Dog(opt);
    return {
      greet: dog.greet.bind(dog.instance)
    };
  }
}
const { greet } = Dog.create({ age: 1 });
greet();

구조 분해 할당을 위한 작업이니 정적 생성자에도 구조 분해 할당을 해 보자.

class Dog {
  constructor(opt) {
    this.instance = this;
    this.age = opt.age;
    this.say = "mew";
  }
  greet() {
    return this.say;
  }
  static create(opt) {
    const { instance, age, say, greet }= new Dog(opt);
    return {
      greet: greet.bind(instance)
    };
  }
}
const { greet } = Dog.create({ age: 1 });
greet();

지저분한 과정을 거치긴 했지만 원하는 바를 수행할 수 있게 되었다.

해결 방법 2 - 메소드에 this 를 바인딩하여 객체를 생성

인스턴스 전체 대신 외부로 노출할 메소드에만 this 를 바인딩 하는 방법도 있다.

얼마전까지 React 에서 많이 쓰이는 방법이 아니었나 싶다. 객체 생성자에 참조할 메소드를 선언하고 this 를 바인딩 해 준다.

class Dog {
  constructor(opt) {
    this.age = opt.age;
    this.say = "mew";
    this.greet = this.greet.bind(this);
  }
  greet () {
    return this.say;
  }
}
const { greet } = new Dog({ age: 1 });
greet();

비교적 깔끔한 방법이다.

해결 방법 3 - Arraw 함수를 사용

객체를 선언할 때 일반 함수 대신 prototype 체인이 없는 화살표 함수를 사용하는 방법이 있다. 이 방법은 별도의 생성자도 필요하지 않고 기존 객체의 메소드만 수정하면 되는 장점이 있다.

class Dog {
  constructor(opt) {
    this.instance = this;
    this.age = opt.age;
    this.say = "haha";
  }
  greet = () => {
    return this.say;
  }
}
const { greet } = new Dog({ age: 1 });
greet();

greet 가 참조하는 this 는 항상 Dog 인스턴스 이다.

클래스 선언시에만 유의하면 화살표 함수를 사용하는 것이 가장 쉬운 방법이다. 하지만 퍼포먼스 이슈가 있는 것으로 확인되는 결과가 있다.

링크들

감춰진 expressjs 의 res.once 를 사용하는 express router wrap logger 제작

클래스 프로퍼티 도입과 성능 문제

함수 선언과 화살표 함수에서 this 스코프의 차이

needCursorRule 을 적용하였다는 카카오 페이지 글로벌 프로젝트

뉴스 피드 구현 with Firebase

레디스에서 공개한 트위터 클론 투토리얼

몽고 디비에서 고개한 소셜 인덱싱 투토리얼

profile
NodeJS 백엔드 웹 개발자입니다.

1개의 댓글

comment-user-thumbnail
2020년 2월 21일

테스트 하던 코드 전체 입니다.

class Man {
  constructor(opt) {
    this.init(opt);
    this.age = opt.age;
    this.greet = this.greet.bind(this);
  }

  greet() {
    if (this) {
      return this.say();
    } else {
      return {};
    }
  }
  say() {
    return { age: this.age, hello: "hi" };
  }
  init(opt) {}

  static create(opt) {
    return new Man(opt);
  }
}

const { greet } = new Man({ age: 20 });
const { greet: secretSay } = Man.create({ age: 25 });
greet();
secretSay();
답글 달기