자바스크립트 this 이해하기 (feat. call, apply, bind의 차이)
자바스크립트에서 사용하는 call, apply, bind에 대해 알아보려고 합니다.
하지만 그전에 JavaScript에서 this에 대해 우선 먼저 알아보겠습니다. 아니 나는 call, apply, bind가 궁금해서 왔는데 왜 this를 이야기 하는거죠? 라는 의문이 드실 텐데.. 우선 this를 이해하게 되면 부수적으로 call, apply, bind가 연관되어서 이해되기 시작합니다. 그리고 This를 이해하게 되면서 우리를 그렇게 괴롭히던 다양하게 변경되는This(트렌스포머)를 이해할 수 있게 됩니다.
📖 This 너는 누구니?
자바스크립트 내에서 this는 '누가 나를 불렀느냐'를 뜻한다고 합니다. 즉, 선언이 아닌 호출에 따라 달라진다는 거죠.
call, apply, bind는 자바스크립트에서 this를 명시적으로 바꾸는 함수입니다. 이제 왜 this를 왜 우선적으로 이해해야하는지 조금은 감이 잡히시나요?
그럼 각 상황별로 this가 어디에 바인딩되는지 알아봅시다.
1. 단독으로 쓴 this
묻지도 따지지도 않고 this를 호출하는 경우엔 global object를 가리킵니다. 브라우저에서 호출하는 경우 [object Window]가 되겠죠? 이는 ES5에서 추가된 strict mode(엄격 모드)에서도 마찬가지입니다.
'use strict';
var x = this;
console.log(x); //Window
2. 함수 안에서 쓴 this
함수 안에서 this는 함수의 주인에게 바인딩됩니다. 함수의 주인은? window객체죠!
✔ 일반적인 This
function myFunction() {
return this;
}
console.log(myFunction()); //Window
var num = 0;
function addNum() {
this.num = 100;
num++;
console.log(num); // 101
console.log(window.num); // 101
console.log(num === window.num); // true
}
addNum();
위 코드에서 this.num의 this는 window 객체를 가리킵니다. 따라서 num은 전역 변수를 가리키게 됩니다. 다만, strict mode(엄격 모드)에서는 조금 다릅니다. 함수 내의 this에 디폴트 바인딩이 없기 때문에 undefined가 됩니다.
✔ strict mode This
"use strict";
function myFunction() {
return this;
}
console.log(myFunction()); //undefined
"use strict";
var num = 0;
function addNum() {
this.num = 100; //ERROR! Cannot set property 'num' of undefined
num++;
}
addNum();
따라서 this.num을 호출하면 undefined.num을 호출하는 것과 마찬가지기 때문에 에러가 납니다.
3. 메서드 안에서 쓴 this
그럼 일반 함수가 아닌 메서드라면 어떨까요? 메서드 호출 시 메서드 내부 코드에서 사용된 this는 해당 메서드를 호출한 객체로 바인딩됩니다.
var person = {
firstName: 'John',
lastName: 'Doe',
fullName: function () {
return this.firstName + ' ' + this.lastName;
},
};
person.fullName(); //"John Doe"
4. 이벤트 핸들러 안에서 쓴 this
이벤트 핸들러에서 this는 이벤트를 받는 HTML 요소를 가리킵니다.
var btn = document.querySelector('#btn')
btn.addEventListener('click', function () {
console.log(this); //#btn
});
5. 생성자 안에서 쓴 this
생성자 함수가 생성하는 객체로 this가 바인딩 됩니다.
function Person(name) {
this.name = name;
}
var kim = new Person('kim');
var lee = new Person('lee');
console.log(kim.name); //kim
console.log(lee.name); //lee
하지만 new 키워드를 빼먹는 순간 일반 함수 호출과 같아지기 때문에, 이 경우는 this가 window에 바인딩됩니다. (🐄오름)
var name = 'window';
function Person(name) {
this.name = name;
}
var kim = Person('kim');
console.log(window.name); //kim
6. 명시적 바인딩을 한 this (call, apply, bind 메서드 사용)
명시적 바인딩은 짝을 지어주는 거예요. 이 this는 내꺼! 같은 거 ;P
apply() 와 call() 메서드는 Function Object에 기본적으로 정의된 메서드인데요, 인자를 this로 만들어주는 기능을 합니다.
✅ Function.prototype.call()
먼저 MDN의 기재되어 있는 call()의 매개변수의 설명을 보면 아래와 같습니다.
func.call(thisArg[, arg1[, arg2[, ...]]])
- thisArg: func 호출에 제공되는 this의 값
- arg1, arg2, ...: func이 호출되어야 하는 인수
첫 번째 매개변수 thisArg는 각 함수의 실행 문맥의 this를 특정하게 지정하는 매개변수임을 알 수 있습니다. 그리고 두 번째부터는 호출할 함수의 인수들이 들어가게 됩니다.
아래 예제 코드를 보면 call을 이용하여 person2의 함수를 호출하고 있지만 call 메서드 첫 번째 매개변수에 person1을 넣어주고 있기 때문에 this는 person2가 아닌 person1을 가리키게 됩니다.
let person1 = {
name: 'Jo'
};
let person2 = {
name: 'Kim',
study: function() {
console.log(this.name + '이/가 공부를 하고 있습니다.');
}
};
person2.study(); // Kim이/가 공부를 하고 있습니다.
// call()
person2.study.call(person1); // Jo이/가 공부를 하고 있습니다.
function HowisThis() {
console.log(this);
}
HowisThis(); //window
var obj = {
x: great,
};
HowisThis.call(obj); //{x:great} HowisThis.apply(obj)로 써도 똑같습니다.
✅ Function.prototype.apply()
fun.apply(thisArg, [argsArray])
- thisArg: func 호출에 제공되는 this의 값
- argsArray: func이 호출되어야 하는 인수를 지정하는 유사 배열 객체
첫 번째 매개변수 thisArg는 Function.prototype.call()과 같이 this를 지정합니다. 하지만 call()과 다르게 apply()는 두 번째 매개변수를 배열 형태로 넣게 된다는 차이점이 있습니다.(배열 또는 유사배열 객체)
function add(c, d) {
return this.a + this.b + c + d;
}
var o = {a: 3, b: 3};
// 첫 번째 인자는 'this'로 사용할 객체이고,
// 이어지는 인자들은 함수 호출에서 인수로 전달된다.
add.call(o, 6, 6); // 18
// 첫 번째 인자는 'this'로 사용할 객체이고,
// 두 번째 인자는 함수 호출에서 인수로 사용될 멤버들이 위치한 배열이다.
add.apply(o, [20, 10]); // 36
✅ Function.prototype.bind()
func.bind(thisArg[, arg1[, arg2[, ...]]])
- thisArg: 바인딩 함수가 타겟 함수의 this에 전달하는 값
- arg1, arg2, ...: func이 호출되어야 하는 인수
bind()는 새롭게 바인딩한 함수를 만듭니다. 바인딩한 함수는 원본 함수 객체를 감싸는 함수로서 bind()는 call(), apply()와 같이 함수가 가리키고 있는 this를 변경하게 되지만 해당 this가 호출되지는 않습니다. 따라서 변수를 할당하여 호출하는 형태로 사용됩니다.
let person1 = {
name: 'Jo'
};
let person2 = {
name: 'Kim',
study: function() {
console.log(this.name + '이/가 공부를 하고 있습니다.');
}
};
person2.study(); // Kim이/가 공부를 하고 있습니다.
// bind()
let student = person2.study.bind(person1);
student(); // Jo이/가 공부를 하고 있습니다.
7. 화살표 함수로 쓴 this
'아니! ! 😱😱😱😱 왜 함수 안에서 this가 전역 객체가 되는 거야!!' 라는 마음의 소리가 들리신다면 이제는 This를 이전보다는 좀 더 이해하게 되신겁니다. 위와 같은 this가 전역 객체가 되는 것을 방지하기 위해서는 화살표 함수를 사용 하면 됩니다.
화살표 함수는 전역 컨텍스트에서 실행되더라도 this를 새로 정의하지 않고, 바로 바깥 함수나 클래스의 this를 쓰거든요.
var Person = function (name, age) {
this.name = name;
this.age = age;
this.say = function () {
console.log(this); // Person {name: "Nana", age: 28}
setTimeout(function () {
console.log(this); // Window
console.log(this.name + ' is ' + this.age + ' years old');
}, 100);
};
};
var me = new Person('Nana', 28);
me.say(); //global is undefined years old
위 코드를 보면 내부 함수에서 this가 전역 객체를 가리키는 바람에 의도와는 다른 결과가 나왔습니다.
var Person = function (name, age) {
this.name = name;
this.age = age;
this.say = function () {
console.log(this); // Person {name: "Nana", age: 28}
setTimeout(() => {
console.log(this); // Person {name: "Nana", age: 28}
console.log(this.name + ' is ' + this.age + ' years old');
}, 100);
};
};
var me = new Person('Nana', 28); //Nana is 28 years old
하지만 화살표 함수로 바꾸면 제대로 된 결과가 나오는 걸 볼 수 있습니다.
출처 :