객체, 클래스를 공부하다보면
this
라는 것이 많이 등장한다.
엉단어 뜻으로 어렴풋이 "현재 가리키는 요소"를 의미한다는 것은 알겠는데...
정확한 의미는 무엇이고, 어떻게 활용되는지 알아보고자한다.
아래에서 사용되는 코드들은 전부 크롬 개발자 도구, 즉 브라우저에서 실행되었습니다.
'점 앞’의 this는 객체를 나타냅니다. 정확히는 메서드를 호출할 때 사용된 객체를 나타내죠.
- Javascript Info -
this
는 메서드를 호출할 때 사용된 객체를 나타낸다고 한다.
이게 무슨 의미일까? 예시를 살펴보자.
let user = {
name: "John",
age: 30,
sayHi() {
// 'this'는 '현재 객체'를 나타냅니다.
alert(this.name);
},
};
user.sayHi(); // John
sayHi()
내부에서 this
가 사용되었다.
여기에서 메서드를 호출할 때 사용된 객체는 무엇일까?
음...메서드는 sayHi()
이고...이 메서드를 호출한 곳은 user.sayHi()
니까...
아! 메서드를 호출할 때 사용된 객체는 user
구나!
그렇다. 그래서 this
는 user
객체를 나타낸다.
이 경우 this.name
은 어떤 값을 지내게 될까?
this
가 user
객체를 나타내므로 user.name
에 있는 값인 "John"이 된다.
이게 무슨 소릴까? 런타임에 결정된다??
동일한 함수라도 다른 객체에서 호출했다면 'this’가 참조하는 값이 달라집니다.
- Javascript Info -
자바스크립트에서는 다른 언어들과 다르게 호출되는 곳에 따라서 this
가 나타내는게 달라진다는 뜻이다.
this
라는 단어 자체의 뜻이 "이 것"이니까 더 이해하기 편한 것 같기도하다...
각설하고, 예시를 살펴보자.
let user = { name: "John" };
let admin = { name: "Admin" };
function sayHi() {
alert(this.name);
}
// 별개의 객체에서 동일한 함수를 사용함
user.f = sayHi;
admin.f = sayHi;
// 'this'는 '점(.) 앞의' 객체를 참조하기 때문에
// this 값이 달라짐
user.f(); // John (this == user)
admin.f(); // Admin (this == admin)
같은 함수 sayHi()
를 각각 다른 객체에 할당해서 실행하는 예시이다.
sayHi()
는 this
를 활용하여 name
을 보여주는 함수이다.
하나는 user
, 하나는 admin
에 할당되어 호출이 되는데,
각각 어떻게 동작을 하게 될까?
우선, 지금까지 this
에 대한 개념을 종합해보면
호출되는 곳에 따라서 나타내는게 달라지며, 메서드를 호출할 때 사용된 객체를 나타낸다.
그렇다면 user.f()
에서 사용되는 this
는 어떤 의미일까?
f()
를 호출할 때 사용된 user
객체를 나타낸다.
그렇다면 admin.f()
에서 사용되는 this
는 어떤 의미일까?
f()
를 호출할 때 사용된 admin
객체를 나타낸다.
즉, this
가 나타내는 값이 달라졌다!
호출되는 곳에 따라 this
가 나타내는 것이 변하는걸 런타임에 결정된다고 하는 것이다.
몇 가지 예시를 통해 어떤 상황에 쓰이는 this
가 어떤 의미를 가지는지 살펴보자.
그냥 사용할 때와 일반 함수 내에서 사용할 때를 살펴보겠다.
메서드 내에서의 this
는 이전 예시에서 살펴봤기 때문에 생략한다.
더 다양한 예시는 nykim님 블로그를 참고해주시면 되겠습니다.
this
냅다 this
만 사용하는 경우에는 전역 객체 window
를 나타낸다.(브라우저에서)
위에서 배운 것에 따르면 this
는 메서드를 호출할 때 사용한 객체를 나타낸다.
일반 함수를 호출할 때 사용되는 객체는 무엇일까?
function sayHi() {
return this;
}
그렇다. window
객체이다.
이런 특성을 통해 다음 예제가 가능하다는 것을 알 수 있다.
var num = 0;
function addNum() {
this.num = 100;
num++;
console.log(num); // 101
console.log(window.num); // 101
console.log(num === window.num); // true
}
addNum();
this.num
에서 this
는 window
를 나타내므로,
this.num
이 var num = 0
을 말하고 있다는 것을 알 수 있다.
var num === this.num === window.num
인 상황인 것이다.
그래서,
첫 번째 console.log()
에서는 num
에 100이 할당된 상태에서 1 증가한 101이 출력된다.
두 번째 console.log()
에서도 101로 증가되어있는 num
이 출력된다.
세 번째 console.log()
에서는 var num === window.num
이므로 true
가 출력된다.
그런데, var
는 함수 스코프를 가지기 때문에 어디서든 접근이 가능하다.
또한, window
는 전역 영역을 담당하기도 한다.
var
로 선언된 num
을 window
에서 접근할 수 있는 이유가 바로 이 것 때문이라고 생각된다.
그래서 약간 예제를 틀어보았다.
var
대신 let
을 사용해보자. 어떤 변화가 있을까?
let num = 0;
function addNum() {
this.num = 100;
num++;
console.log(num); // 1
console.log(window.num); // 100
console.log(num === window.num); // false
}
addNum();
let
은 블록 스코프를 가진다. 어디서든 접근할 수는 없다.
즉, this.num === window.num
이지만, let num
과는 별개라는 것이다.
그래서,
첫 번째 console.log()
에서는 let num = 0
에서의 num
에 접근했다.
두 번째 console.log()
에서는 this.num = 100
에서의 num
에 접근했다.
세 번째 console.log()
에서는 첫 번째, 두 번째가 각각 다른 것을 가르키므로 false
이다.
그런데, 코드를 엄격 모드(strict mode)에서 실행하면 결과가 또 다르다.
아래 예시를 살펴보자.
"use strict";
function sayHi() {
alert(this);
}
sayHi(); // undefined
엄격 모드에서 실행하면, this엔 undefined가 할당됩니다.
- Javascript Info -
즉, this
가 window
를 의미하지 않게 된다는 것이다.
따라서, 방금 본 예제 코드들을 엄격 모드에서 실행하면
"use strict";
var num = 0;
function addNum() {
this.num = 100; //ERROR! Cannot set property 'num' of undefined
num++;
}
addNum();
이렇게 에러가 발생한다. undefined.num
이라는 것은 접근 할 수 없는 것이기 때문이다.
function Person(name) {
this.name = name;
}
const park = new Person("park");
console.log(park.name);
new
를 통해 생성한 함수에서 사용되는 this
는 new
가 생성한 함수에 바인딩된다.
단, new
를 통하지 않는다면 일반 함수이기 때문에
function Person(name) {
this.name = name;
}
const park = Person("park");
console.log(park.name); // TypeError: Cannot read property 'name' of undefined
전역 객체인 window
에 바인딩된다.
따라서, window
로부터 접근해야된다.
function Person(name) {
this.name = name;
}
const park = Person("park");
console.log(window.name); // "park"
위에서 this
의 특징을 살펴봤는데, 화살표 함수(Arrow Function)에서는 이게 좀..다르다!
예시를 살펴보자.
let user = {
firstName: "보라",
sayHi() {
let arrow = () => alert(this.firstName);
arrow();
},
};
user.sayHi(); // 보라
sayHi()
내부에 arrow()
라는 화살표 함수가 선언되어있다. 그리고 바로 실행된다.
즉, user.sayHi()
를 실행하면 sayHi()
는 arrow()
를 생성하고 실행한다.
뭔가..위에서 계속 배운대로라면 this
는 메서드를 호출할 때 사용한 객체니까...
this
는 arrow()
를 나타내는거 아니야?? 라고 생각할 수 있다.
그치만, 그렇지않다! 왜??
화살표 함수는 일반 함수와는 달리 ‘고유한’ this를 가지지 않습니다. 화살표 함수에서 this를 참조하면, 화살표 함수가 아닌 ‘평범한’ 외부 함수에서 this 값을 가져옵니다.
- Javascript Info -
그렇다. 화살표 함수에서의 this
는 화살표 함수가 아니라 외부 함수를 나타내기때문이다.
즉, arrow()
내에 있는 this
는 외부 함수 user.sayHi()
의 this
가 되는 것이다.
user.sayHi()
의 this
가 되었기때문에, this
는 user
를 나타내고 "보라"가 출력되는 것이다.
와닿지않는다면 아래 예시를 추가로 살펴보자.
let user = {
firstName: "보라",
sayHi() {
let user2 = {
firstName: "파랑",
sayBye() {
let arrow = () => {
alert(this.firstName);
};
arrow();
},
};
user2.sayBye();
},
};
user.sayHi(); // 파랑
직전 예시랑 달리진 것은 객체를 한번 더 사용해서 깊어졌다는 것 뿐이다.
여기서의 this
는 어떨까?
자, 순서대로 살펴보자.
user.sayHi()
를 통해 sayHi()
를 실행하고, sayHi()
는 user2
객체를 생성한다.
직후에 user2
객체에 있는 sayBye()
를 실행하며, sayBye()
는 arrow()
를 선언 및 실행한다.
arrow()
는 여전히 화살표 함수이다.
그렇다면...arrow()
에서 사용된 this
는 "외부" 함수에서 값을 가져온다.
따라서 user2
의 firstName
에 접근하여 값을 가져오는 것을 볼 수 있다.
이 예시까지 보고나니, "외부"라는 것은 가장 바깥이 아니라 바로 맞닿아있는 바깥이라는 것도 알았다.
이제 이해는 되었다. 그런데 이걸 언제 써먹는지를 모르겠다.
왜 굳이 this
가 외부 함수를 나타내게 하는걸까?
별개의 this가 만들어지는 건 원하지 않고, 외부 컨텍스트에 있는 this를 이용하고 싶은 경우 화살표 함수가 유용합니다.
- Javascript Info -
그렇다고한다. 예시를 살펴보자.
let group = {
title: "1모둠",
students: ["보라", "호진", "지민"],
showList() {
this.students.forEach((student) => alert(this.title + ": " + student));
},
};
group.showList();
// 1모둠: 보라
// 1모둠: 호진
// 1모둠: 지민
forEach()
에서 화살표 함수를 사용했기 때문에 this.title
은 화살표 함수 바깥에 있는 메서드인 showList()
가 가리키는 대상과 같다.
위에서 공부했던 내용에 따라 group
객체를 나타낸다는 것을 알 수 있다.
따라서, 화살표 함수의 this.title
은 group.title
과 같다고 할 수 있다.
만약, 화살표 함수가 아닌 일반 함수를 사용했다면 에러가 발생하게 된다.
let group = {
title: "1모둠",
students: ["보라", "호진", "지민"],
showList() {
this.students.forEach(function (student) {
// TypeError: Cannot read property 'title' of undefined
alert(this.title + ": " + student);
});
},
};
group.showList();
에러는 forEach()
에 전달되는 함수의 this
가 undefined
이기 때문에 발생한다.