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

JavaScript에서 this 값을 알아내는 것은 까다로울 수 있습니다. 다음은 this 값을 알아내는 방법입니다.

Jake Archibald
Jake Archibald

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);

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

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에 관해 알아봤습니다. 질문이 있으신가요? 놓친 부분이 있나요? 언제든지 트윗해 주세요.

검토해 주신 마티아스 비넨스, 잉바르 스테파니안, 토마스 슈타이너님께 감사드립니다.