자바스크립트 실행 문맥(링크)를 이해한 후 읽으시길 추천드립니다!
자바스크립트는 실행 가능한 코드(예를 들어 함수)를 만나면 그 코드를 평가해서 실행 문맥을 만드는데, 실행 문맥에는 실행에 필요한 모든 정보가 담겨 있다. 실행 문맥을 구성하는 컴포넌트에는 렉시컬 환경 컴포넌트와 디스 바인딩 컴포넌트가 있다. 디스 바인딩 컴포넌트는 그 함수를 호출한 객체의 참조가 저장되는 곳으로, 이것이 가리키는 값이 해당 실행 문맥의 this가 된다.
this 값은 ‘함수가 호출되었을 때 그 함수가 속해 있던 객체의 참조’이다.
→ this의 값은 함수가 호출되어 실행되는 시점에 결정된다.
아래의 예시에서 출력값은 어떻게 될까?
let user = {
firstName: "John",
sayHi() {
alert(`Hello, ${this.firstName}!`);
}
};
setTimeout(user.sayHi, 1000);
getName
함수 내부에서 this.firstName
을 출력하는데, 여기서 this
는 user
객체의 메소드로 호출되었으므로 user
객체가 될 것만 같다.
그러나 결과값은 undefined
가 나온다. (!!)
그 이유는 user.sayHi
가 user
객체에서 분리되어 전달되고, setTimeout
에서 인수로 전달받은 함수를 호출할 때, this
에 window
를 할당하기 때문이다.
→ 자바스크립트 함수는 특정 객체에 묶여 있지 않다.
다양한 상황에서 this가 가리키는 값
앞에서 언급한 예시를 다시 한 번 살펴보자. 해당 예시에서는 this
가 user
객체를 가리키길 기대했지만 다른 객체(예시에서는 전역 객체)를 가리키고 있다.
원하는 대로 ‘John’이 출력되게끔 하는 방법에는 두 가지가 있는데,
첫 번째는 실행 문맥의 외부 렉시컬 환경 참조를 활용하는 것이다.
다음의 코드를 보자.
let user = {
firstName: "John",
sayHi() {
alert(`Hello, ${this.firstName}!`);
}
};
setTimeout(() => {
user.sayHi(); // Hello, John!
}, 1000);
이 코드에서는 setTimeout
에서 인수로 전달받은 함수를 호출할 때 외부 렉시컬 환경에서 user
을 받아서 보통 때처럼 메서드를 호출하게 된다.
하지만 이 경우 setTimeout
이 트리거 되기 전에(1초가 지나기 전에) user
가 변경되면, 변경된 객체의 메서드를 호출하게 된다는 문제점이 있다.
두 번째 방법은 this 객체를 직접 설정해주는 것이다.
앞으로 설명할 call
, apply
, bind
는 모두 Function 객체의 메서드로 this와 인수를 설정할 수 있게 해준다.
bind() 메서드는 선택한 this와 인수를 적용한 새로운 함수를 반환한다.
fn.bind(thisArg[, arg1[, arg2[, ...]]])
bind()
는 call()
및 apply()
와는 달리 함수를 즉시 실행하지 않고, thisArg
에 넣은 this
값 및 arguments
로 넣어준 인수(arg1, arg2…
)를 갖는 함수를 리턴한다.앞서 언급한 예시에 bind()
를 적용해보면 다음과 같다.
let user = {
firstName: "John",
sayHi() {
alert(`Hello, ${this.firstName}!`);
}
};
let sayHi = user.sayHi.bind(user);
setTimeout(sayHi, 1000); // Hello, John!
call() 메서드는 선택한 this와 인수를 사용하여 함수를 호출한다. 인수는 쉼표로 구분한 값이다.
fn.call(thisObj, args1, args2, ...)
apply() 메서드는 선택한 this와 인수를 사용하여 함수를 호출한다. 인수는 배열 객체다.
func.apply(thisObj, argumentsArray);
참고 자료
책 <모던 자바스크립트 입문>, 이소 히로시 지음
함수 바인딩
How to Use the Call, Apply, and Bind Functions in JavaScript - with Code Examples