개발을 한지도 어느덧 3년차... this를 모르는것은 아니지만 그렇다고 완벽하게 아는거 같지도 않아서 한번 정리를 해보려고한다.
MDN 공식문서에 나온 this를 바탕으로 작성을 해보자.
JavaScript에서 함수의 this 키워드는 다른 언어와 조금 다르게 동작합니다. 또한 엄격 모드와 비엄격 모드에서도 일부 차이가 있습니다.
대부분의 경우 this의 값은 함수를 호출한 방법에 의해 결정됩니다. 실행중에는 할당으로 설정할 수 없고 함수를 호출할 때 마다 다를 수 있습니다. ES5는 함수를 어떻게 호출했는지 상관하지 않고 this 값을 설정할 수 있는 bind 메서드를 도입했고, ES2015는 스스로의 this 바인딩을 제공하지 않는 화살표 함수를 추가했습니다(이는 렉시컬 컨텍스트안의 this값을 유지합니다).
this의 값은 상황에 따라 변한다. 어떤 것들이 있는지 알아보자.
전역 실행 맥락에서 this는 엄격 모드 여부에 관계 없이 전역 객체를 참조합니다.
// 웹 브라우저에서는 window 객체가 전역 객체
console.log(this === window); // true
a = 37;
console.log(window.a); // 37
this.b = "MDN";
console.log(window.b) // "MDN"
console.log(b) // "MDN"
function f1() {
return this;
}
// 브라우저
f1() === window; // true
// Node.js
f1() === global; // true
function f2(){
"use strict"; // 엄격 모드 참고
return this;
}
f2() === undefined; // true
비엄격모드에서는 함수가 단순 호출될 때 함수의 형태에 따라 그 this의 값이 정해진다. 선언형과 표준형의 함수의 경우에는 this의 값은 함수를 호출한 방법에 의해 좌우되지만 화살표 함수의 경우에는 렉시컬 컨텍스트 안의 this값을 유지한다.
렉시컬 컨텍스트 안의 this는 무슨말일까...
렉시컬 환경을 공부했던 기억을 되살려보자. 호출된 함수는 렉시컬 환경을 가지고 렉시컬 환경은 환경 레코즈를 가진다. 이때 환경 레코즈 안에는 this가 저장이 되어있는데 함수 선언식과 함수 표현식의 경우 환경 레코즈가 만들어질 때 함수의 형태에 따라 this 바인딩이 이뤄지지만 화살표함수는 this 바인딩이 제공되지 않는다. 따라서 화살표함수는 this에 해당하는 값을 찾기 위해 외부 렉시컬 환경의 this를 찾기 시작한다. 외부 렉시컬 환경에 this에 해당하는 값이 없을 경우 화살표함수에 해당하는 this는 undefined가 된다.
하지만 엄격모드에서는 함수 선언문과 표현식의 this는 아래 코드와 같이 this를 특정짓지 않는 한 undefined가 된다.
const f1 = function() {
console.log(this) // global 또는 window
const f2 = function() {
"use strict"
console.log(this) // undefined
}
// const f2 = function() {
// console.log(this) // global 또는 window --- (A)
// }
// const f2 = () => {
// console.log(this) // global 또는 window --- (B)
// }
()
}
f1()
추가로 (A)와 같은 상황이 된다면 f2함수의 this는 함수 문맥이므로 global을 특정하게 된다.
만약 (B)와 같이 화살표 함수가 된다면 어떻게 될까. 이때 this는 global을 특정하게 되는데 이는 this가 global로 특정 되서가 아니라 렉시컬 컨택스트 안의 this값을 유지 했기 때문이다.
this의 값을 찾기 위해 외부 렉시컬 환경에서 this에 해당하는 값을 찾는다. f2의 외부 렉시컬 환경인 f1에서 this값을 찾는다. f1의 this는 global을 특정하고 있으므로 (B)의 this는 global이 된다.
함수 표현식과 선언문은 엄격모드에서는 실행 문맥에 진입하며 설정되는 값을 유지하며 어떤 값도 설정하지 않았기 때문에 undefined가 된다. 화살표 함수는 엄격모드, 비엄격모드와 무관하게 렉시컬 컨텍스트안의 this 값을 유지한다.
그렇다면 실행 문맥에 진입하면서 this값을 특정하기 위해서는 어떻게 해야할까.
엄격모드와 비엄격모드를 불문하고 선언문과 표현식 함수에서는 this의 값을 bind, call, apply를 통해 특정지을 수 있다.
참고 : 화살표 함수는 this 바인딩을 지원하지 않으므로 bind, call, apply 함수를 적용해도 무시합니다.
아래의 코드를 통해 이해해 보자 우선 call과 apply를 확인해보면
// call 또는 apply의 첫 번째 인자로 객체가 전달될 수 있으며 this가 그 객체에 묶임
var obj = { a: 'Custom' };
// 변수를 선언하고 변수에 프로퍼티로 전역 window를 할당
var a = 'Global';
function whatsThis() {
console.log(this.a)
return this.a; // 함수 호출 방식에 따라 값이 달라짐
}
whatsThis(); // this는 'Global'. 함수 내에서 설정되지 않았으므로 global/window 객체로 초기값을 설정한다.
whatsThis.call(obj); // this는 'Custom'. 함수 내에서 obj로 설정한다.
whatsThis.apply(obj); // this는 'Custom'. 함수 내에서 obj로 설정한다.
다음과 같이 함수를 호출할 때 this를 특정 지을 수 있다. 차이가 있다면 함수를 호출할 때 this를 특정하는 첫번째 인자 이후 파라미터를 넣는 방법인데 call의 경우 arguments를 2, 3, 4... 번째 인자로 넣어주고 apply의 경우 2번째 인자에 배열형식으로 arguments를 넣어준다. 아래 코드를 통해 이해해 보자.
const obj = {
a: 1,
b: 2,
}
const f = function (c,d) {
return this.a + this.b + c + d
}
// call
f.call(obj, 3, 4) // 1+2+3+4 = 10
// apply
f.apply(obj, [3, 4]) // 1+2+3+4 = 10
// bind의 인자로 객체가 전달될 수 있으며 this가 그 객체에 묶임
var obj = { a: 'Custom' };
// 변수를 선언하고 변수에 프로퍼티로 전역 window를 할당
var a = 'Global';
function whatsThis() {
console.log(this.a)
return this.a; // 함수 호출 방식에 따라 값이 달라짐
}
whatsThis(); // this는 'Global'. 함수 내에서 설정되지 않았으므로 global/window 객체로 초기값을 설정한다.
whatsThis.bind(obj);
whatsThis(); // this는 'Global'. 함수 내에서 설정되지 않았으므로 global/window 객체로 초기값을 설정한다.
whatsThis.bind(obj)(); // this는 'Custom'. 함수 내에서 obj로 설정한다.
const bindWhatsThis = whatsThis.bind(obj);
bindWhatsThis(); // this는 'Custom'. 함수 내에서 obj로 설정한다.
bind로 this를 특정 지을때는 call, apply와 다르게 함수가 바로 실행되지 않는다. bind를 통해 this를 특정하여 변수에 저장한 후 함수를 호출 하거나 bind매서드를 적용한 후 '()'를 통해 바로 호출하여 사용할 수 있다.
// this를 반환하는 메소드 bar를 갖는 obj를 생성합니다.
// 반환된 함수는 화살표 함수로 생성되었으므로,
// this는 감싸진 함수의 this로 영구적으로 묶입니다.
// bar의 값은 호출에서 설정될 수 있으며, 이는 반환된 함수의 값을 설정하는 것입니다.
var obj = {
bar: function() {
var x = (() => this);
return x;
}
};
// obj의 메소드로써 bar를 호출하고, this를 obj로 설정
// 반환된 함수로의 참조를 fn에 할당
var fn = obj.bar();
// this 설정 없이 fn을 호출하면,
// 기본값으로 global 객체 또는 엄격 모드에서는 undefined
console.log(fn() === obj); // true
// 호출 없이 obj의 메소드를 참조한다면 주의하세요.
var fn2 = obj.bar;
// 화살표 함수의 this를 bar 메소드 내부에서 호출하면
// fn2의 this를 따르므로 window를 반환할것입니다.
console.log(fn2()() == window); // true
위 예시에서, obj.bar에 할당된 함수(익명 함수 A라고 지칭)는 화살표 함수로 생성된 다른 함수(익명 함수 B라고 지칭)를 반환합니다. 결과로써 함수 B가 호출될 때 B의 this는 영구적으로 obj.bar(함수 A)의 this로 설정됩니다. 반환됨 함수(함수 B)가 호출될 때, this는 항상 초기에 설정된 값일 것입니다. 위 코드 예시에서, 함수 B의 this는 함수 A의 this인 obj로 설정되므로, 일반적으로 this를 undefined나 global 객체로 설정하는 방식으로 호출할 때도(또는 이전 예시에서처럼 global 실행 컨텍스트에서 다른 방법을 사용할 때에도) obj의 설정은 유지됩니다.
객체의 메서드에서 this는 객체를 특정한다.
var o = {
prop: 37,
f: function() {
return this.prop;
}
};
console.log(o.f()); // 37
this.name = 'walwal'
const cat = {
name: 'meow',
foo1: function() {
const foo2 = function() {
return this.name;
}
return foo2();
}
};
cat.foo1(); // 'walwal'
위의 경우 foo2에서 this는 함수 문맥에서의 this가 되므로 global을 특정한다. 그렇다면 foo2의 this가 cat를 특정짓게 하는 방법은 무엇일까
this.name = 'walwal'
const cat = {
name: 'meow',
foo1: function() {
const foo2 = function() {
return this.name;
}
return foo2.call(this);
}
};
cat.foo1(); // 'meow'
this.name = 'walwal'
const cat = {
name: 'meow',
foo1: function() {
const foo2 = () => {
return this.name
}
return foo2();
}
};
cat.foo1(); // 'meow'
함수를 이벤트 처리기로 사용하면 this는 이벤트를 발사한 요소로 특정된다.
// 처리기로 호출하면 관련 객체를 파랗게 만듦
function bluify(e) {
// 언제나 true
console.log(this === e.currentTarget);
// currentTarget과 target이 같은 객체면 true
console.log(this === e.target);
this.style.backgroundColor = '#A5D9F3';
}