이번 포스팅에선 자바스크립트 This의 대해서 알아보겠다.
코어 자바스크립트의 내용을 학습하고 정리한 글이다.
이전에 실행 컨텍스트에 대해 학습하면서 실행 컨텍스트가 언제 생성되는지 알 수 있었다.
또한, ThisBinding이 실행 컨텍스트에서 이루어진다는 것도 알 수 있었다.
This
는 "어떻게" 호출되는지에 따라 달라진다.
즉, 함수를 실행하는 주체가 누구인지에따라 달라지게 되는 것이다.
호출되는 방식에 따라 5가지로 구분할 수 있다.
전역 공간에서 this
는 브라우저에선 window
, Node.js에선 global
을 가리킨다.
개념상으로 전역 컨텍스트를 호출하는 주체가 전역객체이기 때문이다.
// 브라우저
console.log(this) // window
// 노드
console.log(this) // global
함수 호출 시 this
는 브라우저에선 window
, Node.js에선 global
을 가리킨다.
첫 번째 코드의 경우 전역 공간에서 함수를 호출했기 때문에 this
가 window 또는 global을 가리키는 것은 알겠다.
하지만 두 번째 코드에서는 뭔가 조금 이상하다. b
함수의 경우 전역 공간에서 실행했으니 window 또는 global을 가리키겠지만, b 함수 내부에서 호출한 c
함수 또한 window 또는 global을 가리킨다.
이거는 자바스크립트를 빠르게 만들다보니 생긴 실수라고 얘기한다. 이것을 버그라고 봐야할지 자바스크립트만의 특성이라고 봐야할지 의견이 분분하다.
그렇기 때문에 ES6+ 에서는 this 바인딩을 하지않는 Arrow Function
이란 것이 나왔다. Arrow Function
을 사용하면 상위 컨텍스트의 this를 사용하게되어 이 문제를 해결할 수 있다.
// 첫 번째 코드
function a() {
console.log(this);
}
a(); // window , global
// 두 번째 코드
function b() {
function c() {
console.log(this);
}
c();
}
b(); // window , global
메소드를 호출했을 시 this
는 메소드의 호출 주체(메소드명 앞)이 된다.
var a = {
b: function() {
console.log(this);
}
}
a.b(); // this -> a
var a = 10;
var obj = {
a: 20,
b: function() {
console.log(this.a); // 20
function c() {
console.log(this.a); // 10
}
c();
}
}
obj.b();
만약 우리가 c 함수를 호출했을 때 원하는 결과가 20이라면 어떻게 해야할까?
방법은 두 가지가 있다.
// 1번
var a = 10;
var obj = {
a: 20,
b: function() {
console.log(this.a); // 20
const c = () => {
console.log(this.a); // 20
}
c();
}
}
obj.b();
// 2번
var a = 10;
var obj = {
a: 20,
b: function() {
const self = this;
console.log(this.a); // 20
function c() {
console.log(self.a); // 20
}
c();
}
}
obj.b();
callback 호출 시 this
는 기본적으로 함수내부에서와 동일하다.
let callback = function() {
console.log(this);
}
let obj = {
a: 1,
b: function(cb) {
cb();
}
}
obj.b(callback);
아래 코드에서의 this
는 무엇일까?
let callback = function() {
console.log(this);
}
let obj = {
a: 1,
b: function(cb) {
cb.call(this);
}
}
obj.b(callback);
new 키워드를 통해 생성자함수 호출 시 this
는 인스턴스를 가리킨다.
즉, 생성자함수 호출 시 객체가 만들어지고, 객체가 this가 되는 것이다.
function Who(name, age) {
this.name = name;
this.age = age;
}
let jihoon = new Who('hoo00nn', '26ㅠㅠ');
console.log(jihoon);
/*
Who {
name: 'hoo00nn',
age: '26ㅠㅠ'
}
*/
함수도 일급객체이기 때문에 메소드를 사용할 수가 있다.
언제, 어디에서든 call, apply, bind
메소드들을 사용해서 함수를 실행시킬 수 있는데, 함수가 호출될 컨텍스트를 지정하여 함수를 호출할 수 있게 한다.
이것이 바로 명시적 바인딩이다. this 키워드가 무엇을 참조할 지 명시적으로 알려주는 방법이다.
call 과 apply는 함수를 즉시 호출하고
bind는 this를 바인딩한 새로운 함수를 리턴한다.
function greeting() {
console.log(`My name is ${name}`);
}
const obj = {
name: 'hoo00nn',
}
greeting(); // undefined
greeting.call(user); // My name is hoo00nn
만약 함수의 파라미터가 있다면 어떻게 사용해야할까?
call 함수의 첫번째 인자로 컨텍스트를 넣은 다음, 차례대로 인자를 넣어주면 된다.
function greeting(a, b, c) {
console.log(`My name is ${name} and I know ${a}, ${b}, ${c}`);
}
const language = ['JavaScript', 'Python', 'Java'];
const obj = {
name: 'hoo00nn',
}
greeting(); // undefined
greeting.call(user, language[0], language[1], language[2]);
// My name is hoo00nn and I know JavaScript, Python, Java
만약 인자를 차례로 넣기 귀찮다면 apply 메소드를 사용하여 배열로 한번에 넣을 수 있다.
const language = ['JavaScript', 'Python', 'Java'];
greeting.apply(user, language);
bind
는 새로운 함수를 반환한다.
1. 원본 함수를 복제한 "새로운 함수"를 반환해줄뿐 함수를 실행하지 않으며
2. 반환된 함수를 실행해야 원본함수가 실행된다.
3. 첫번째 인자는 this
값이 되고
4. 전달할 수 있는 인자의 갯수 제한이 없다.
function greeting(a, b, c) {
console.log(`My name is ${name} and I know ${a}, ${b}, ${c}`);
}
const language = ['JavaScript', 'Python', 'Java'];
const obj = {
name: 'hoo00nn',
}
// this = obj, a = 'JavaScript', b = 'Python'
let newFunc = greeting.bind(user, language[0], language[1]);
// c = 'Java', 복사본 함수에게 주어지는 인자도 순차적으로 원본함수에게 전달되기 때문.
newFunc(language[2]);