모던 JavaScript 튜토리얼(https://ko.javascript.info/)을 읽고 정리한 내용입니다.
자바스크립트의 this, 함수 this 바인딩
자바스크립트의 this는 다른 프로그래밍 언어의 this와는 동작 방식이 다르다.
자바스크립트에서는 모든 함수에 this를 사용 가능하다.
this는 런타임에 의해 결정되고 컨텍스트에 따라 달라진다.
같은 함수라도 다른 객체에서 호출했다면 this가 참조하는 값이 달라진다.
let user = { name: "John" };
let admin = { name: "Admin" };
function sayHi() {
alert( this.name );
}
// 별개의 객체에서 동일한 함수를 사용함
user.f = sayHi;
admin.f = sayHi;
// 'this'는 '점(.) 앞의' 객체를 참조하기 때문에
// this 값이 달라짐
user.f(); // John (this == user)
admin.f(); // Admin (this == admin)
admin['f'](); // Admin (점과 대괄호는 동일하게 동작함)
js 객체안에 존재하지 않는 프로퍼티에 값을 할당하면 해당 프로퍼티가 동적으로 추가되고 할당이 된다.
따라서 user와 admin객체에 모두 sayHi 함수가 메소드로 생성이 된다.
각 객체의 메소드를 호출하면 모두 this는 해당 객체들을 참조한다.
화살표 함수의 this
this 가 없는 화살표 함수
화살표 함수는 일반 함수와 달리 고유한 this를 가지지 않는다.
화살표 함수에서 this를 참조하면 화살표 함수가 아닌 외부 함수에서 this를 가져온다.
let user = {
firstName: "보라",
sayHi() {
let arrow = () => alert(this.firstName);
arrow();
}
};
user.sayHi(); // 보라
별개의 this가 만들어지는걸 원치 않고 외부 컨텍스트에 있는 this를 이용하고 싶은 경우 유용하다.
let group = {
title: "1모둠",
students: ["보라", "호진", "지민"],
showList() {
this.students.forEach(
student => alert(this.title + ': ' + student)
);
}
};
group.showList();
group 객체의 showList() 메소드의 forEach문은 화살표 함수를 실행하는데,
이 때 화살표 함수의 this는 함수가 아닌 외부 컨텍스트인 group을 가리킨다.
화살표 함수 안의 this는 화살표 밖의 메서드인 showList의 this와 동일한 this를 가리킨다.
다음과 같이 화살표 함수 대신 일반 함수를 사용하면 에러가 발생한다.
let group = {
title: "1모둠",
students: ["보라", "호진", "지민"],
showList() {
this.students.forEach(function(student) {
// TypeError: Cannot read property 'title' of undefined
alert(this.title + ': ' + student)
});
}
};
group.showList();
일반 함수는 자체 this를 가지므로 에러가 발생한다.
함수 바인딩
함수에서 this가 사라지는 경우가 있다.
let user = {
firstName: "John",
sayHi() {
alert(`Hello, ${this.firstName}!`);
}
};
setTimeout(user.sayHi, 1000); // Hello, undefined!
user.sayHi는 분리된 함수 만 전달하므로 컨텍스트를 잃어버린다.
브라우저 환경에서 setTimeout은 인수로 전달 받은 함수를 호출 할 때 this에 window객체를 할당한다.
따라서 window객체에서 참조하는 값을 찾게 되는데 당연히 window객체에는 firstName이라는 프로퍼티가 없으므로 undefined가 된다.
이 때 this를 수정하게 해주는 내장 메서드 bind를 사용하여 this를 변경할 수 있다.
bind
let boundFunc = func.bind(context);
func.bind(context)는 함수처럼 호출 가능한 '특수 객체'를 반환한다.
이 객체를 호출하면 this가 context로 고정된 함수 func를 반환한다.
예시
let user = {
firstName: "John"
};
function func() {
alert(this.firstName);
}
let funcUser = func.bind(user);
funcUser(); // John
func에 user객체를 바인딩 했으므로 this.firstName은 user객체 안의 프로퍼티를 참조한다.
바인딩 된 함수에 인수를 보내면 인수는 원본 함수 func에 그대로 전달된다.
let user = {
firstName: "John"
};
function func(phrase) {
alert(phrase + ', ' + this.firstName);
}
// this를 user로 바인딩합니다.
let funcUser = func.bind(user);
funcUser("Hello"); // Hello, John (인수 "Hello"가 넘겨지고 this는 user로 고정됩니다.)
객체 메서드의 bind
let user = {
firstName: "John",
sayHi() {
alert(`Hello, ${this.firstName}!`);
}
};
let sayHi = user.sayHi.bind(user); // (*)
// 이제 객체 없이도 객체 메서드를 호출할 수 있습니다.
sayHi(); // Hello, John!
setTimeout(sayHi, 1000); // Hello, John!
// 1초 이내에 user 값이 변화해도
// sayHi는 기존 값을 사용합니다.
user = {
sayHi() { alert("또 다른 사용자!"); } // 출력되지 않음.
};
user.sayHi를 가져오고 메서드에 user를 바인딩 한다. sayHi는 이제 묶인 함수가 되어 단독으로 실행도 가능하고 전달할 수도 있음.
인수는 그대로 전달되고 bind에 의해 this만 고정된다.
let user = {
firstName: "John",
say(phrase) {
alert(`${phrase}, ${this.firstName}!`);
}
};
let say = user.say.bind(user);
say("Hello"); // Hello, John (인수 "Hello"가 say로 전달되었습니다.)
say("Bye"); // Bye, John ("Bye"가 say로 전달되었습니다.)