상황의 따라 바뀌는 this
this⚡
Javascipt에 this에 알아보기 전에 this는 '이것'이라는 뜻이다.
상황에 따라 다른 대상을 지칭하며 쓰일 수 있는 말이다.
Javascript에서도 this는 상황에 따라 지칭하는 대상이 바뀐다.
그 상황은 '함수가 호출되는 상황' 일 때이다.
Javascript의 this는 일단 기본적으로 함수가 호출될 때 결정되며, 보통 자신을 호출한 주체의 정보가 담긴다.
this는 함수 실행 시 호출(invocation) 방법에 의해 결정되는 특별한 객체이다.
함수 실행 시 결정되므로, 실행되는 맥락에 따라 this는 다르게 결정된다.
🙄함수 실행 방법
1. 전역 공간에서의 this
2. 메서드로서 호출
3. new 를 이용한 생성자 호출
4. 함수로서 호출
5. call/apply를 통한 호출
1. 전역 공간에서의 this
함수 실행 방법은 아니지만, 전역에서 this를 참조하는 경우를 말한다.
전역 공간에서의 this는 '전역 객체'이다.
전역 객체는 브라우저에서는 window, Node.js에서는 global이 된다.
console.log(this === window)
// true (웹 브라우저 환경)
console.log(this === global)
// true (Node.js 환경)
2. 메서드로서 호출
메서드 호출은 객체. 메서드()와 같이 객체 내에 메서드를 호출하는 방법을 의미한다.
단순 객체를 사용한 예제는 흔히 볼 수 있다.
(ex. obj.method()) 이렇게 어떤 객체에 종속된다는 메서드의 특징은 this에도 적용이 된다.
메서드로 호출된 this는 부모 객체를 기준으로 고정(바인딩)되기 때문이다.
var func = function(x) {
console.log(this.x);
}
func(20); // window객체 20 (함수로 호출)
var obj = {
method : func
}
obj.method(20); // {method : f} 20 (메서드로 호출)
3. new 를 이용한 생성자 호출
생성자 함수는 어떤 공통된 성질을 갖는 객체들을 생성하는데 쓰이는 함수이다.
인스턴스를 찍어내는 일종의 '틀'같은 역할을 하는 것이다.
new 키워드를 함수가 바로 이런 '생성자'로 동작할 수 있도록 해준다.
일단 new와 함께 함수를 호출하면 객체를 생성할 수 있게 되는데, 이 객체를 '인스턴스'라고 한다.
ES5, ES6 방식 둘 다 예시를 들어보겠다.
// ES5
let Dog = function(name) {
this.sound = '머엉';
this.name = name;
}
let Baekgu = new Dog('백구');
console.log(Baekgu)
// ES6
class Dog {
constructor(name){
this.sound = '머엉';
this.name = name;
}
}
let Baekgu = new Dog('백구');
console.log(Baekgu)
생성자 함수(Dog)가 호출되면 새로운 인스턴스 객체(Baekgu)가 생성된다.
새로 생성된 객체의 this에는 객체의 성질들이 포함된다.
여기서는 sound라는 미리 지정된 공통의 성질, 그리고 넘겨받는 값에 따라 바뀌는 name이라는 새로운 성질이 모두 해당된다.
위의 예제에서 생성자 함수 내부의 this를 출력해보면 Dog { sound : '머엉', name : '백구' }라고 나올 것이다.
이처럼 생성자 함수 내부의 this는 새로 생성된 인스턴스를 가르킨다.
4. 함수로서 호출
함수는 그 자체로, 혼자서 독립적인 기능을 한다.
어딘가에 종속되지 않기 때문에 다른 메서드들과는 달리 함수는 호출한 주체가 없다.
this는 자신을 호출한 주체의 정보를 담는다고 했지만, 곰곰이 생각해보면 '함수 -> 호출 주체 없음 -> this에 담을 정보 없음' 이렇게 나온다.
독립적으로 존재하는 경우 this는 전역 객체를 가리키게 된다.
5. call/apply를 통한 호출
this는 어떤 규칙에 의해 우리도 모르는 사이에 새 지정되었다.
하지만 call이나 apply 메서드를 사용하면 this를 별도의 대상으로 지정할 수 있게 된다.
두 메서드의 첫 번째 인자로 들어가는 값이 this이기 때문이다.
function Baekgu() {
return '머엉'
}
Baekgu()
Baekgu.call()
Baekgu.apply()
apply도 call과 같은 기능을 하며 첫번째 인자가 this라는 것도 동일하다.
apply는 인자들을 하나의 배열로 받는다는 점에서만 차이가 있다.
구문을 설명해보자면 call의 구문이고, func.call(thisArg[, arg1[, arg2[, ...]]])
apply의 구문이다. func.apply(thisArg, [argsArray])
자세한 설명과 이해는 MDN 공식문서에서 찾아보도록 하자.
정리 call()은 함수에 전달될 인수 리스트를 받고, apply는 인수들의 단일 배열을 받는다.
// null을 this로 지정합니다. Math는 생성자가 아니므로 this를 지정할 필요가 없다.
Math.max.apply(null, [5,4,1,6,2]) // 6
// spread operator의 도입으로 굳이 apply를 이용할 필요가 없다.
Math.max(...[5,4,1,6,2]) // 6
// apply 활용 예시
Jinx = {x: 10};
let goo = function(a, b){
console.log(this, a, b);
};
goo.call(Jinx, 1, 2); // {x : 10}, 1 2
goo.apply({a: 1}, [1, 2, 3]); // {a: 1} 1 2
6. bind를 통한 호출
아래 코드를 보면 call/apply/bind 가 다 정리되어 있다.
여기서는 bind만 대충 이해하고 넘어가겠다.
1차로 bind로 명시적으로 this 값을 할당한 (함수 호출을 위한) 변수를 만들고, 2차로 원할 때에 함수의 매개변수를 추가 혹은 변경할 수 있기 때문에 call, apply보다 좀 더 단계적(?)으로 세분화하여 사용할 수 있게 된다.
var yozoo = {};
var ezi = {};
var sol = {};
function update(birthYear, occupation) { // 2개의 매개변수
this.birthYear = birthYear;
this.occupation = occupation;
};
update.call(yozoo, 1989, 'student'); // yozoo = this값 할당, 이후 함수의 인자 값으로 할당
/*
yozoo =
{
birthYear : 1989,
occupation : 'student'
}
*/
update.apply(ezi, [1989, 'doctor']); // ezi = this값 할당, 이후 함수의 인자 값으로
/*
ezi =
{
birthYear : 1989,
occupation : 'doctor'
}
*/
var bound = update.bind(sol); // sol = this값 할당
bound(1989, 'musician'); // 이후 인자를 추가하여 함수 호출
/*
sol =
{
birthYear : 1989,
occupation : 'musician'
}
*/
정리
this는 어떤 상황에 있느냐에 따라 값이 변한다.
아래는 대표적인 상황인데 상황에 따른 this 값 개념은 자기 공부를 하고 봐야 이해가 가능할 것 같다.
1. 전역 변수 : 전역 객체
2. 함수로 호출: 전역 객체
3. 메서드로 호출: 호출 주체(메서드명 앞 객체)
4. 생성자 함수로 호출 : 새 인스턴스 객체
5. call, apply 메서드로 호출 : 첫 번째 인자