본문 바로가기
{Javascript}

03.04.24 You Don't Know JS 스터디 후기

by Davey 2024. 3. 10.
728x90

Created by 2023ⓒKyle Simpson

this는 도대체 어떻게 탄생했을까?
비동기 콜백지옥은 왜 만들어지게 되었나?

 

이번 글에서는 위 질문에 대한 궁금증을 조금이나마 해소하고 싶은 오랜 꿈이 있었습니다. 이에 열정 가득하고 실력 짱짱한 상우님 그리고 상권님과 함께 'You Don't Know JS'의 this와 프로토타입 및 비동기와 성능에 대한 책을 2023년 8월 29일부터 2024년 2월 14일까지 총 12회에 걸쳐서 스터디 한 내용을 회고하고, 당시 느꼈던 인사이트들을 공유하고자 합니다.

 

명시적 바인딩이 암시적 바인딩보다 우선순위가 높다.

 JavaScript에서는 함수를 호출할 때 this 키워드가 해당 함수를 호출한 객체를 참조하도록 설계 되었습니다.  바인딩은 특정 컨텍스트(context) 또는 객체에 함수를 연결하는 과정을 의미합니다. 책에서는 클래스 바인딩은 제외하고 함수 호출시 this 바인딩만 다루고 있었고, 함수를 호출할 때의 4가지 this 바인딩 규칙이 존재한다고 언급하고 있습니다. 만약 함수랑 다른 클래스 메서드를 이벤트 핸들러로 사용하려고 하면 어떤 차이가 있을까요?  this가 예상한 클래스 인스턴스를 참조 하지 않을 수 있기 때문에, 메서드를 해당 인스턴스에 아래와 같이 바인딩 해서 사용해야 됩니다.

 

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    // Now `this` refers to the class instance, so this line is safe:
    this.setState({ clicked: true });
  }

  render() {
    return <button onClick={this.handleClick}>Click me</button>;
  }
}

 

 

제 함수가 실행되는 동안 this가 무엇을 참조할지를 호출부가 어떻게 결정하는지 기본 바인딩 부터 암시적, 명시적 바인딩 및 new 바인딩까지 다양하게 비교해서 이해할 필요가 있습니다. 

this는 작성 시점이 아닌 런타임 시점에 바인딩되며, 함수 호출 당시 상황에 따라 콘텍스트가 결정됩니다. 함수 선언 위치와 상관없이 this 바인딩은 오로지 어떻게 함수를 호출했느냐에 따라 정해지는 형태입니다. 참고로 컨텍스트는 실행중인 코드의 환경 또는 범위를 나타내며, 객체는 프로그래밍 언어에서 데이터와 기능을 함께 그룹화하는 기본 단위입니다.

 

바인딩이란 특정 함수 호출 당시 컨텍스트 또는 객체에 함수가 연결되는 과정을 의미합니다. 예를 들어, 클래스 메서드를 이벤트 핸들러로 사용하려고 하면 this가 예상한 클래스 인스턴스를 참조하지 않을 수 있습니다. 이런 경우에는 메서드를 클래스 인스턴스에 바인딩해야 합니다.

 

아래 예시 코드에서 this.handleClick = this.handleClick.bind(this); 라인은 handleClick 메서드를 클래스 인스턴스에 바인딩합니다. 이렇게 하면 handleClick 내부의 this가 항상 클래스 인스턴스를 참조하게 됩니다.

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    // Now `this` refers to the class instance, so this line is safe:
    this.setState({ clicked: true });
  }

  render() {
    return <button onClick={this.handleClick}>Click me</button>;
  }
}

 

 

자바스크립트는 클래스와 함수에서 this 동작이 조금씩 다르게 동작합니다. 해당 책에서는 함수에서의 this를 자바스크립트 함수에서 바인딩에는 크게 3가지로 분류되는데, 첫째로 기본 바인딩입니다. 비엄격 모드에서 기본 바인딩은 전역 객체를 참조하지만, 엄격 모드에서는 기본 바인딩 대상에서 전역객체를 제외하고, undefined로 항상 결정됩니다. 

function foo() { "use strict";
  console.log( this.a ); 
}

var a = 2;
foo(); // 타입 에러: 'this'는 'undefined'입니다(TypeError: 'this' is 'undefined').

 

둘째로 암시적 바인딩이 있습니다. 이는 호출부에 콘텍스트 객체가 있는지, 즉 객체의 소유owning/포함 containing 여부를 확인하는 방식입니다. 하지만, 암시적 바인딩은 함수 레퍼런스를 객체에 넣기 위해 객체 자신을 변형해야 하고, 함수 레퍼런스 프로퍼티를 이용해서 바인딩 할때 예기치 않게 this를 바뀌는 문제를 해결할 수 없습니다. 나아가서는 콜백함수를 마음대로 통제할 수 없는 현상도 막지 못합니다. 

function foo() { 
  console.log( this.a );
}

var obj = { 
  a: 2,
  foo: foo 
}

obj.foo(); // 2

 

호출부를 고정적으로 조절할 수 없을까?라는 질문의 답으로  셋째, 명시적 바인딩이 탄생하게 됩니다.

call과 apply 혹은 bind 메서드를 이용하면 바인딩할 객체를 첫째 파라미터로 받아 함수 호출시 해당 개체로 this를 셋팅하는 원리입니다. 슬프게도 이렇게 명시적으로 바인딩해도 앞서 언급한 this 바인디잉 도중에 소실되거나 임의로 덮어씌워져 버리는 문제는 해결할 수 없습니다.

function foo() { 
  console.log( this.a );
}

var obj = { 
  a: 2
};

foo.call( obj ); // 2

 

마지막으로, new 바인딩이 있습니다. 함수 앞에 new를 붙여 생성자 호출을 하면 다음과 같은 일들이 저절로 일어납니다.

1. 새 객체가 만들어진다.
2. 새로 생성된 객체의 
[[Prototype]]이 연결된다.
3. 새로 생성된 객체는 해당 함수 호출 시 
this로 바인딩된다.

4. 이 함수가 자신의 또 다른 객체를 반환하지 않는 한 new와 함께 호출된 함수는 자동으로 새로 생성된 객체를 반환한다.

 

function foo(a) { 
  this.a = a;
}

var bar = new foo( 2 );
console.log( bar.a ); // 2

 

자바크스립트의 new 연산자는 전통 클래스 언어의 생성자는 클래스 인스턴스 생성시 new 연산자로 호출 되는것과 유사하지만 실재론 인스턴스를 생성하지 않습니다.

 

모든건 순서가 있기 나름인데, 4가지 바인딩 규칙이 중복으로 적용되면 기본 바인딩은 후순위로 밀려나게 됩니다. 그렇다면 1순위는 누구일까요? 암시적 바인딩과 명시적 바인딩

자바스크립트는 단일 스레드로 동작하는데 어떻게 비동기를 처리할 수 있을까?

 

잡큐(Job Queue)> 마이크로 태스크 큐(MicroTask Queue) > 매크로 태스크 큐(MacroTask Queue) 우선 순위를 통해 단일 스레드에서도 비동기를 아래와 같이 처리합니다.

 

출처:&nbsp;https://dev.to/lydiahallie/javascript-visualized-promises-async-await-5gke#syntax

 

 

728x90

댓글