자바스크립트: 이 기능의 의미는 무엇인가요?

JavaScript에서는 this의 값을 계산하기가 까다로울 수 있습니다. 방법은 다음과 같습니다.

제이크 아치볼드
제이크 아치볼드

JavaScript의 this는 많은 농담의 핵심입니다. 꽤 복잡하기 때문입니다. 하지만 개발자가 이 this를 처리하지 않기 위해 훨씬 더 복잡한 도메인 관련 작업을 실행하는 것을 확인했습니다. this에 대해 잘 모르는 경우 도움이 될 수 있습니다. 저는 this 가이드입니다.

가장 구체적인 상황부터 시작하여 가장 구체적이지 않은 상황으로 마무리하겠습니다. 이 문서는 큰 if (…) … else if () … else if (…) …와 비슷하므로 지금 보고 있는 코드와 일치하는 첫 번째 섹션으로 바로 이동할 수 있습니다.

  1. 함수가 화살표 함수로 정의된 경우
  2. 아니면 함수/클래스가 new로 호출되는 경우
  3. 그렇지 않은 경우 함수에 '바인드된' this 값이 있는 경우
  4. 통화 시간에 this가 설정된 경우
  5. 그렇지 않은 경우 함수가 상위 객체 (parent.func())를 통해 호출되는 경우
  6. 그렇지 않은 경우 함수 또는 상위 범위가 엄격 모드인 경우
  7. 그렇지 않은 경우

함수가 화살표 함수로 정의된 경우:

const arrowFunction = () => {
  console.log(this);
};

이 경우 this의 값은 상위 범위의 this항상 동일합니다.

const outerThis = this;

const arrowFunction = () => {
  // Always logs `true`:
  console.log(this === outerThis);
};

화살표 함수는 this의 내부 값을 변경할 수 없고 외부 this항상 동일하기 때문에 유용합니다.

기타 예

화살표 함수를 사용하면 this 값을 bind로 변경할 수 없습니다.

// Logs `true` - bound `this` value is ignored:
arrowFunction.bind({foo: 'bar'})();

화살표 함수를 사용하면 this의 값을 call 또는 apply로 변경할 수 없습니다.

// Logs `true` - called `this` value is ignored:
arrowFunction.call({foo: 'bar'});
// Logs `true` - applied `this` value is ignored:
arrowFunction.apply({foo: 'bar'});

화살표 함수를 사용하면 함수를 다른 객체의 멤버로 호출하여 this 값을 변경할 수 없습니다.

const obj = {arrowFunction};
// Logs `true` - parent object is ignored:
obj.arrowFunction();

화살표 함수를 사용하면 함수를 생성자로 호출하여 this 값을 변경할 수 없습니다.

// TypeError: arrowFunction is not a constructor
new arrowFunction();

'바인드된' 인스턴스 메서드

인스턴스 메서드에서 this가 항상 클래스 인스턴스를 참조하도록 하려면 화살표 함수와 클래스 필드를 사용하는 것이 가장 좋습니다.

class Whatever {
  someMethod = () => {
    // Always the instance of Whatever:
    console.log(this);
  };
}

이 패턴은 인스턴스 메서드를 구성요소 (예: React 구성요소 또는 웹 구성요소)의 이벤트 리스너로 사용할 때 매우 유용합니다.

위의 코드는 'this가 상위 범위의 this와 동일합니다' 규칙을 깨는 것처럼 보일 수 있지만 생성자에서 설정을 위한 구문 설탕으로 클래스 필드를 생각하면 이해하기 쉽습니다.

class Whatever {
  someMethod = (() => {
    const outerThis = this;
    return () => {
      // Always logs `true`:
      console.log(this === outerThis);
    };
  })();
}

// …is roughly equivalent to:

class Whatever {
  constructor() {
    const outerThis = this;
    this.someMethod = () => {
      // Always logs `true`:
      console.log(this === outerThis);
    };
  }
}

대체 패턴에는 생성자에서 기존 함수를 결합하거나 생성자에서 함수를 할당하는 작업이 포함됩니다. 어떤 이유로 클래스 필드를 사용할 수 없는 경우 생성자에 함수를 할당하는 것이 적합한 대안입니다.

class Whatever {
  constructor() {
    this.someMethod = () => {
      // …
    };
  }
}

그 외에 함수/클래스가 new로 호출되는 경우:

new Whatever();

위의 함수는 thisObject.create(Whatever.prototype)의 결과로 설정하여 Whatever (또는 클래스인 경우 생성자 함수)를 호출합니다.

class MyClass {
  constructor() {
    console.log(
      this.constructor === Object.create(MyClass.prototype).constructor,
    );
  }
}

// Logs `true`:
new MyClass();

이전 스타일 생성자의 경우도 마찬가지입니다.

function MyClass() {
  console.log(
    this.constructor === Object.create(MyClass.prototype).constructor,
  );
}

// Logs `true`:
new MyClass();

기타 예

new로 호출되는 경우 this의 값은 bind를 사용하여 변경할 수 없습니다.

const BoundMyClass = MyClass.bind({foo: 'bar'});
// Logs `true` - bound `this` value is ignored:
new BoundMyClass();

new로 호출하면 함수를 다른 객체의 멤버로 호출해도 this 값을 변경할 수 없습니다.

const obj = {MyClass};
// Logs `true` - parent object is ignored:
new obj.MyClass();

그렇지 않고 함수에 '바인드된' this 값이 있는 경우:

function someFunction() {
  return this;
}

const boundObject = {hello: 'world'};
const boundFunction = someFunction.bind(boundObject);

boundFunction가 호출될 때마다 this 값은 bind(boundObject)에 전달되는 객체가 됩니다.

// Logs `false`:
console.log(someFunction() === boundObject);
// Logs `true`:
console.log(boundFunction() === boundObject);

기타 예

바인딩된 함수를 호출할 때 this의 값은 call 또는 apply를 사용하여 변경할 수 없습니다.

// Logs `true` - called `this` value is ignored:
console.log(boundFunction.call({foo: 'bar'}) === boundObject);
// Logs `true` - applied `this` value is ignored:
console.log(boundFunction.apply({foo: 'bar'}) === boundObject);

바인딩된 함수를 호출할 때는 함수를 다른 객체의 멤버로 호출해도 this 값을 변경할 수 없습니다.

const obj = {boundFunction};
// Logs `true` - parent object is ignored:
console.log(obj.boundFunction() === boundObject);

그 외에 통화 시간에 this가 설정된 경우에는 다음을 충족해야 합니다.

function someFunction() {
  return this;
}

const someObject = {hello: 'world'};

// Logs `true`:
console.log(someFunction.call(someObject) === someObject);
// Logs `true`:
console.log(someFunction.apply(someObject) === someObject);

this 값은 call/apply에 전달되는 객체입니다.

안타깝게도 this는 DOM 이벤트 리스너와 같은 요소에 의해 다른 값으로 설정되며, 이를 사용하면 이해하기 어려운 코드가 생성될 수 있습니다.

금지사항
element.addEventListener('click', function (event) {
  // Logs `element`, since the DOM spec sets `this` to
  // the element the handler is attached to.
  console.log(this);
});

위와 같은 경우에는 this를 사용하지 않고 대신 다음과 같이 합니다.

의견을 제시하지
element.addEventListener('click', (event) => {
  // Ideally, grab it from a parent scope:
  console.log(element);
  // But if you can't do that, get it from the event object:
  console.log(event.currentTarget);
});

그렇지 않고 함수가 상위 객체 (parent.func())를 통해 호출되는 경우 다음과 같습니다.

const obj = {
  someMethod() {
    return this;
  },
};

// Logs `true`:
console.log(obj.someMethod() === obj);

이 경우 함수는 obj의 멤버로 호출되므로 thisobj입니다. 이 문제는 호출 시 발생하므로 함수가 상위 객체 없이 호출되거나 다른 상위 객체를 사용하여 호출되면 링크가 끊어집니다.

const {someMethod} = obj;
// Logs `false`:
console.log(someMethod() === obj);

const anotherObj = {someMethod};
// Logs `false`:
console.log(anotherObj.someMethod() === obj);
// Logs `true`:
console.log(anotherObj.someMethod() === anotherObj);

someMethod() === obj는 false입니다. someMethodobj의 멤버로 호출되지 않기 때문입니다. 다음과 같이 시도할 때 이 문제가 발생할 수 있습니다.

const $ = document.querySelector;
// TypeError: Illegal invocation
const el = $('.some-element');

이 경우 querySelector 구현이 자체 this 값을 보고 이 값이 일종의 DOM 노드가 될 것으로 예상하고 위에서 연결이 끊어지기 때문에 연결이 끊어집니다. 위 목표를 올바르게 달성하려면 다음 안내를 따르세요.

const $ = document.querySelector.bind(document);
// Or:
const $ = (...args) => document.querySelector(...args);

재미있는 사실: 일부 API는 내부적으로 this를 사용하지 않습니다. console.log와 같은 콘솔 메서드가 this 참조를 피하기 위해 변경되었으므로 logconsole에 바인딩할 필요가 없습니다.

그렇지 않고 함수 또는 상위 범위가 엄격 모드인 경우 다음을 충족해야 합니다.

function someFunction() {
  'use strict';
  return this;
}

// Logs `true`:
console.log(someFunction() === undefined);

이 경우 this의 값은 정의되지 않습니다. 상위 범위가 엄격 모드이고 모든 모듈이 엄격 모드인 경우 함수에 'use strict'가 필요하지 않습니다.

그 이외의 경우

function someFunction() {
  return this;
}

// Logs `true`:
console.log(someFunction() === globalThis);

이 경우 this 값은 globalThis와 동일합니다.

휴!

이상입니다 this에 대해 제가 알고 있는 정보는 여기까지입니다. 질문이 있으신가요? 놓친 부분이 있나요? 언제든지 트윗해 주세요.

의견을 제공해 주신 마티아스 바인스, 잉바르 스테파니안, 토마스 슈타이너님께 감사드립니다.