this
this
this는 객체 자신의 프로퍼티나 메서드를 참조하기 위한 자기 참조 변수(self-referencing variable) 다.
this가 가리키는 값, 즉 this 바인딩은 함수 호출 방식에 따라 동적으로 결정된다.
함수 호출 방식 | this가 가리키는 값(this 바인딩) |
일반 함수로서 호출 | 전역 객체, strict mode(undefined) |
메서드로서 호출 | 메서드를 호출한 객체(마침표 앞의 객체) |
생성자 함수로서 호출 | 생성자 함수가 (미래에) 생성할 인스턴스 |
인스턴스가 정확히 누구일까?
function Circle(radius) {
this.radius = radius;
this.getDiameter = function (){
return 2 * this.radius;
};
}
// 인스턴스 생성
const circle1 = new Circle(5); // 10
const circle2 = new Circle(10); // 20
circle1, circle2가 인스턴스임😶
this 바인딩은 함수 호출 방식에 따라 동적으로 결정된다고 했다.
바인딩 이란 식별자와 값을 연결하는 과정을 의미한다. 예를 들어, 변수 선언은 변수 이름(식별자)과 확보된 메모리 공간의 주소를 바인딩하는 것이다. this 바인딩은 this(키워드로 분류되지만 식별자 역할을 한다)와 this가 가리킬
객체를 바인딩하는 것이다.
함수의 호출 방식을 확인해 보자.
console.log(this) // window (this는 어디서든지 참조 가능, 전역에서는 전역 객체 window를 가리킨다)
function square(number) {
// 일반 함수 내부에서 this는 전역 객체 window를 가리킨다.
console.log(this); // window
return number * number;
}
square(2);
const person = {
name: 'lee',
getName() {
// 메서드 내부에서 this는 메서드를 호출한 객체를 가리킨다.
console.log(this); // {name: 'lee', getName: f}
return this.name;
}
};
console.log(person.getName()); // lee
function Person(name) {
this.name = name;
// 생성자 함수 내부에서 this는 생성자 함수가 생성할 인스턴스를 가리킨다.
console.log(this); // Person {name: 'lee'}
}
const me = new Person('lee');
this는 객체의 프로퍼티나 메서드를 참조하기 위한 자기 참조 변수이므로 일반적으로 객체의 메서드 내부 또는 생성자 함수 내부에서만 의미가 있다. 따라서 strict mode(엄격모드)가 적용된 일반 함수 내부의 this에는 undefined가 바인딩된다. 일반 함수 내부에서 this를 사용할 필요가 없기 때문이다.
내부 함수에서 함수 호출 this
var value = 1;
var obj = {
value : 100,
func1 : function(){
this.value += 1;
console.log("func1()'s value: " + this.value); //func1()'s value: 101
func2 = function(){
this.value += 1;
console.log("func2()'s value: " + this.value); //func2()'s value: 2
}
func2();
}
};
obj.func1();
var value = 1;
var obj = {
value : 100,
func1 : function(){
var that = this;
this.value += 1;
console.log("func1()'s value: " + this.value); //func1()'s value: 101
func2 = function(){
that.value += 1;
console.log("func2()'s value: " + that.value); //func2()'s value: 102
}
func2();
}
};
obj.func1();
var obj = {
methodA: function () {
console.log(this); // {inner: {…}, methodA: ƒ}
},
inner: {
methodB: function () {
console.log(this); // {methodB: ƒ}
},
},
};
obj.methodA();
obj.inner.methodB();
화살표 함수
ES6에서는 함수 내부에서 this가 전역객체를 바라보는 문제를 보완하고자, this를 바인딩하지 않는 화살표 함수(arrow function)를 새로 도입했다! 화살표 함수는 실행 컨텍스트를 생성할 때 this 바인딩 과정 자체가 빠지게 되어, 상위 스코프의 this를 그대로 활용할 수 있다.
var obj = {
outer: function () {
console.log(this); // {outer: ƒ}
var innerFunc = () => {
console.log(this); // {outer: ƒ}
};
innerFunc();
},
};
obj.outer();
렉시컬 스코프와 this 바인딩은 결정 시기가 다르다.
함수의 상위 스코프를 결정하는 방식인 렉시컬 스코프는 함수 정의가 평가되어 함수 객체가 생성되는 시점에 상위 스코프를 결정한다. 하지만 this 바인딩은 함수 호출 시점에 결정된다.
메서드 내에서 정의한 중첩 함수도 일반 함수로 호출되면 중첩 함수 내부의 this에도 전역객체가 바인딩.
콜백 함수도 일반 함수로 호출된다면 콜백 함수 내부의 this에도 전역객체가 바인딩.
어떠한 함수라도 일반 함수로 호출되면 this에는 전역객체가 바인딩.
콜백 함수에서 함수 호출 this
setTimeout(function () {
console.log(this); // window
}, 300);
[1, 2, 3, 4, 5].forEach(function (x) {
console.log(this, x); // window 1 , window 2, window 3...
});
document.body.innerHTML += '<button id="a">클릭</button>';
document.body.querySelector('#a').addEventListener('click', function (e) {
console.log(this, e); // e, PointerEvent{} === e.currentTarget
});
setTimeoute 함수와 forEach 메서드는 그 내부에서 콜백 함수를 호출할 때 대상이 될 this를 지정하지 않는다. 따라서 this는 전역객체를 참조
한편 addEventListener 메서드는 콜백 함수를 호출할 때 자신의 this를 상속하도록 정의돼있다.
call, apply, bind
명시적으로 this를 바인딩함
MDN:
ES5는 함수를 어떻게 호출했는지 상관하지 않고 this 값을 설정할 수 있는 bind 메서드를 도입했다.
this의 값을 한 문맥에서 다른 문맥으로 넘기려면 다음 예시와 같이 call()이나 apply()를 사용하세요.
// call 또는 apply의 첫 번째 인자로 객체가 전달될 수 있으며 this가 그 객체에 묶임
var obj = {a: 'Custom'};
// 변수를 선언하고 변수에 프로퍼티로 전역 window를 할당
var a = 'Global';
function whatsThis() {
return this.a; // 함수 호출 방식에 따라 값이 달라짐
}
whatsThis();// this는 'Global'. 함수 내에서 설정되지 않았으므로 global/window 객체로 초기값을 설정.
whatsThis.call(obj); // this는 'Custom'. 함수 내에서 obj로 설정한다.
whatsThis.apply(obj); // this는 'Custom'. 함수 내에서 obj로 설정한다.
function add(c, d) {
return this.a + this.b + c + d;
}
var o = {a: 1, b: 3};
// 첫 번째 인자는 'this'로 사용할 객체이고,
// 이어지는 인자들은 함수 호출에서 인수로 전달된다.
add.call(o, 5, 7); // 16
// 첫 번째 인자는 'this'로 사용할 객체이고,
// 두 번째 인자는 함수 호출에서 인수로 사용될 멤버들이 위치한 배열이다.
add.apply(o, [10, 20]); // 34
call 메서드는 첫 번째 인자를 제외한 나머지 모든 인자들을 호출할 함수의 매개변수로 지정하는 반면,
apply 메서드는 두 번째 인자를 배열로 받아 그 배열의 요소들을 매개변수로 지정한다
비엄격 모드에서 this로 전달된 값이 객체가 아닌 경우, call과 apply는 이를 객체로 변환하기 위한 시도를 합니다.
null과 undefined 값은 전역 객체가 됩니다. 7이나 'foo'와 같은 원시값은 관련된 생성자를 사용해 객체로 변환되며,
따라서 원시 숫자 7은 new Number(7)에 의해 객체로 변환되고 문자열 'foo'는 new String('foo')에 의해 객체로 변환됩니다.
function bar() {
console.log(Object.prototype.toString.call(this));
}
bar.call(7); // [object Number]
bar.call('foo'); // [object String]
bar.call(undefined); // [object global]
bind 메서드는 ES5에서 추가된 기능으로, call과 비슷하지만 즉시 호출하지는 않고 넘겨 받은 this 및 인수들을 바탕으로 새로운 함수를 반환하기만 하는 메서드다.
bind 메서드는 함수에 this를 미리 적용하는 것과 부분 적용 함수를 구현하는 두 가지 목적을 모두 지닌다.
var func = function (a, b, c, d) {
console.log(this, a, b, c, d);
};
func(1, 2, 3, 4); // Window{...} 1 2 3 4
var bindFunc1 = func.bind({ x: 1 });
bindFunc1(5, 6, 7, 8); // { x: 1 } 5 6 7 8
var bindFunc2 = func.bind({ x: 1 }, 4, 5);
bindFunc2(6, 7); // { x: 1 } 4 5 6 7
bindFunc2(8, 9); // { x: 1 } 4 5 8 9
name 프로퍼티
bind 메서드를 적용해서 새로 만든 함수는 독특한 성질이 있다.
바로 name프로퍼티에 동사 bind의 수동태인 'bound' 라는 접두어가 붙는다는 점이다.
어떤 함수의 name프로퍼티가 'bound xxx'라면 이는 곧 함수명이 xxx인 원본 함수에 bind 메서드를 적용한 새로운 함수라는 의미가 된다.
var func = function (a, b, c, d) {
console.log(this, a, b, c, d);
};
var bindFunc = func.bind({ x: 1 }, 4, 5);
console.log(func.name); // func
console.log(bindFunc.name); // bound func
메서드 내부의 this에는 호출한 객체, 즉
메서드를 호출할 때 메서드 이름 앞의 마침표 연사자 앞에 기술한 객체가 바인딩된다.
주의할 것은 메서드 내부의 this는 메서드를 소유한 객체가 아닌 메서드를 호출한 객체에 바인딩된다는 것이다. 프로토타입 메서드 내부에서 사용된 this도 일반 메서드와 마찬가지이다.