this
자신이 속한 객체 또는 자신이 생성할 인스턴스를 가리키는 자기 참조 변수
this 바인딩
함수 호출 방식에 의해 동적으로 결정된다.
함수 호출 방식 | this 바인딩 |
---|---|
일반함수로서 호출 | 전역객체 |
메서드로서 호출 | 메서드를 호출한 객체(마침표 앞의 객체) |
생성자함수로서 호출 | 생성자 함수가 생성할 인스턴스 |
Function.prototype 메서드에 의한 간접호출 | call / apply / bind 메서드의 첫번째 인수) |
화살표 함수는 this가 다르게 동작하기 때문에 언급하기 전까지는 함수 선언문 또는 표현식으로 예제를 작성할 것이다.
const foo = function() {
console.dir(this);
};
// 1. 일반 함수 호출
foo(); // window
// 2. 메서드 호출
const obj = { foo };
obj.foo(); // obj
// 3. 생성자 함수 호출
new foo(); // foo{}
// 4. Function.prototype.call/apply/bind 메서드에 의한 간접호출
const bar = { name: 'bar' };
foo.call(bar); // bar
foo.apply(bar); // bar
foo.bind(bar)(); // bar
일반함수로 호출 된 모든 함수(중첩함수, 콜백함수 포함) 내부의
this
에는 전역 객체 바인딩
function foo() {
console.log(this); // window
function bar() {
console.log(this); // window
}
// 일반 함수로써 호출
bar();
}
// 일반 함수로써 호출
foo();
var value = 1;
const obj = {
value: 100,
foo() {
console.log(this); // {value: 100, foo: ⨍}
// 콜백함수의 내부의 this에는 전역 객체가 바인딩된다.
setTimeout(function() {
console.log(this); // window
console.log(this.value); // 1
}, 100);
}
};
// 메서드로써 호출
obj.foo();
const circle = {
radius: 5,
getDiameter() {
// 메서드로써 호출되었기 때문에 this는 메서드를 호출한 객체 circle을 가리킨다.
return 2 * this.radius;
}
};
// 메서드로써 호출
console.log(circle.getDiameter()); // 10
// 생성자 함수
function Circle(radius) {
// this는 생성자 함수가 생성할 인스턴스를 가리킨다.
this.radius = radius;
}
Circle.prototype.getDiameter = function() {
// this는 생성자 함수가 생성할 인스턴스를 가리킨다.
return 2 * this.radius;
};
// 인스턴스 생성
// 생성자 함수로써 호출
const circle = new Circle(5);
console.log(circle.getDiameter()); // 10
call / apply / bind
function getThisBinding() {
console.log(arguments);
return this;
}
// this로 사용할 객체
const thisArg = { a: 1 };
// getThisBinding 함수를 호출하며 인수로 전달한 객체를 getThisBinding 함수의 this에 바인딩
// call 메서드는 호출할 함수의 인수를 쉼표로 구분한 리스트 형식으로 전달
console.log(getThisBinding.call(thisArg, 1, 2, 3));
// Arguments(3) [1, 2, 3, callee: ⨍, Symbol(Symbol.iterator): ⨍]
// {a: 1}
// apply 메서드는 호출할 함수의 인수를 배열로 묶어서 전달
console.log(getThisBinding.apply(thisArg, [1, 2, 3]));
// Arguments(3) [1, 2, 3, callee: ⨍, Symbol(Symbol.iterator): ⨍]
// {a: 1}
// bind 메서드는 함수를 호출하지는 않으므로 명시적으로 호출해야 한다.
console.log(getThisBinding.bind(thisArg)(1,2,3);
// Arguments(3) [1, 2, 3, callee: ⨍, Symbol(Symbol.iterator): ⨍]
// {a: 1}
this
는 객체의 프로퍼티나 메서드를 참조하기 위한 자기 참조 변수이므로
일반적으로 객체의 메서드 내부 또는 생성자 함수 내부에서만 의미가 있다.
때문에 strict mode
가 적용된 일반 함수 내부의 this
에는 undefined
가 바인딩 된다.
function foo() {
'use strict';
console.log(this); // undefined
function bar() {
console.log(this); // undefined
}
// 일반 함수로써 호출
bar();
}
// 일반 함수로써 호출
foo();
화살표 함수의 예외 사항을 알기전에 왜 화살표 함수는 다르게 설계되었는지 알아보도록 하자
콜백함수 내부의 this문제
콜백함수 내부의 this가 외부 함수의 this와 다르기 때문에 발생하는 문제를 해결하기 위해 의도적으로 this를 다르게 설계했다.
class Prefixer {
constructor(prefix) {
this.prefix = prefix;
}
add(arr) {
return arr.map(function(item) {
return this.prefix + item;
// TypeError: Cannot read property 'prefix' of undefined
});
}
}
const prefixer = new Prefixer('-webkit-');
console.log(prefixer.add(['transition', 'user-select']));
위의 예제를 보면 Array.prototype.map 메서드가 콜백 함수를 일반함수로써 호출하기 때문에 class 내부는 use strict
모드가 적용되어 일반함수의 this
에는 undefined
가 바인딩 되므로 객체가 아니라는 TypeError
가 발생하게 된다.
콜백함수 내부의 this문제를 ES6이전에는 아래와 같은 방법들로 해결했다.
1. this를 회피시키고 콜백 함수 내부에서 사용하는 방법
add(arr) {
// this 회피
const that = this;
return arr.map(function(item) {
// this 대신 that을 참조
return that.prefix + ' ' + item;
});
}
2. Array.prototype.map의 두번째 인수로 add 메서드를 호출한 prefixer 객체를 가리키는 this를 전달하는 것
ES5에서 도입된 Array.prototype.map은
콜백함수 내부의 this문제
를 해결하기 위해 두번째 인수로 콜백함수 내부에서 this로 사용할 객체를 전달할 수 있다.
add(arr) {
return arr.map(function(item) {
return this.prefix + ' ' + item;
}, this); // this에 바인딩된 값이 콜백함수 내부의 this에 바인딩된다.
}
3. Function.prototype.bind 메서드를 사용하여 add 메서드를 호출한 prefixer 객체를 가리키는 this를 바인딩 한다.
add(arr) {
return arr.map(function(item) {
return this.prefix + ' ' + item;
}.bind(this)); // this에 바인딩된 값이 콜백함수 내부의 this에 바인딩된다.
}
화살표 함수는 함수 자체의
this
바인딩을 갖지 않으므로 화살표 함수 내부에서 this를 참조하면 상위 스코프의this
를 그대로 참조한다.
class Prefixer {
constructor(prefix) {
this.prefix = prefix;
}
add(arr) {
return arr.map(item => this.prefix + item);
}
}
const prefixer = new Prefixer('-webkit-');
console.log(prefixer.add(['transition', 'user-select']));
렉시컬 스코프와 같이 화살표 함수의 this가 함수가 정의된 위치에 의해 결정된다는 것
렉시컬 스코프와 this 바인딩은 결정 시기가 다르다
렉시컬 스코프: 함수 정의가 평가되어 함수 객체가 생성되는 시점에 상위 스코프 결정
this 바인딩: 함수 호출 시점에 결정