JavaScript에서 this
값을 알아내는 것은 까다로울 수 있습니다. 다음은 this
값을 알아내는 방법입니다.
JavaScript의 this
는 꽤 복잡하기 때문에 많은 농담의 소재가 됩니다.
하지만 개발자가 이 this
를 처리하지 않기 위해 훨씬 더 복잡하고 도메인별로 다른 작업을 하는 것을 보았습니다. this
에 대해 잘 모르겠다면 이 도움말이 도움이 되기를 바랍니다. this
가이드입니다.
가장 구체적인 상황부터 시작하여 가장 구체적이지 않은 상황으로 끝내겠습니다. 이 도움말은 큰 if (…) … else if () … else if (…) …
와 같으므로 보고 있는 코드와 일치하는 첫 번째 섹션으로 바로 이동할 수 있습니다.
- 함수가 화살표 함수로 정의된 경우
- 그렇지 않고 함수/클래스가
new
로 호출되는 경우 - 그렇지 않고 함수에 '경계'
this
값이 있는 경우 - 그렇지 않고
this
이 호출 시간에 설정된 경우 - 그렇지 않으면 상위 객체 (
parent.func()
)를 통해 함수가 호출된 경우 - 그렇지 않으면 함수 또는 상위 범위가 엄격 모드인 경우
- 그렇지 않은 경우
함수가 화살표 함수로 정의된 경우:
const arrowFunction = () => {
console.log(this);
};
이 경우 this
값은 상위 범위의 this
와 항상 동일합니다.
const outerThis = this;
const arrowFunction = () => {
// Always logs `true`:
console.log(this === outerThis);
};
화살표 함수는 this
의 내부 값을 변경할 수 없으므로 항상 외부 this
와 동일하므로 유용합니다.
기타 예
화살표 함수를 사용하면 bind
로 this
값을 변경할 수 없습니다.
// Logs `true` - bound `this` value is ignored:
arrowFunction.bind({foo: 'bar'})();
화살표 함수를 사용하면 call
또는 apply
로 this
의 값을 변경할 수 없습니다.
// 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();
위의 코드는 this
를 Object.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
의 구성원으로 호출되므로 this
는 obj
입니다. 이는 호출 시점에 발생하므로 함수가 상위 객체 없이 호출되거나 다른 상위 객체로 호출되면 연결이 끊어집니다.
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
의 멤버로 호출되지 않으므로 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
를 사용하지 않습니다. this
참조를 피하도록 console.log
와 같은 콘솔 메서드가 변경되었으므로 log
를 console
에 바인딩할 필요가 없습니다.
그렇지 않고 함수 또는 상위 범위가 엄격 모드인 경우:
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
에 관해 제가 알고 있는 정보는 이게 전부입니다. 질문이 있으신가요? 제가 놓친 부분이 있나요? 언제든지 트윗해 주세요.