상황에 따라서 달라지는 this를 정리해보려고 한다.
0 JavaScripot에서 this
1 상황에 따라 달라지는 this
1-1 전역 공간에서의 this
1-2 메서드로서 호출할 때 그 메서드 내부에서의 this
함수 vs 메서드
메서드 내부에서의 this
1-3 함수로서 호출할 때 그 함수 내부에서의 this
함수 내부에서의 this
메서드의 내부함수에서의 this
메서드의 내부 함수에서의 this를 우회하는 방법
this를 바인딩하지 않는 함수
1-4 콜백 함수 호출 시 그 함수 내부에서의 this
1-5 생성자 함수 내부에서의 this
2 명시적으로 this를 바인딩하는 방법
2-1 call 메서드
2-2 apply 메서드
2-3 call/apply 메서드의 활용
유사배열객체에 배열 메서드를 적용
생성자 내부에서 다른 생성자 호출
여러 인수를 묶어 하나의 배열로 전달하고 싶을 때 apply 활용
2-4 bind 메서드
name 프로퍼티
상위 컨텍스트의 this를 내부함수나 콜백함수에 전달하기
2-5 arrow function의 예외사항
2-6 별도의 인자로 this를 받는 경우(콜백 함수 내에서의 this)
JavaScripot에서 this
-
다른 객제지향 언어와 this의 차이점
- 다른 대부분의 객체지향 언어의 this: 클래스로 생성한 인스턴스 객체
- 클래스에서만 사용할 수 있기 때문에 헷갈리지 않거나 많지 않음
- 자바스크립트 this: 어디에서나 쓸 수 있음
- 상황에 따라 this가 바라보는 대상이 달라짐
- 문제를 해결하려면 원인을 알아야 하는데, 정확한 작동방식을 이해하지 못하면 원인을 해결하기 어려움
- this 확인으로 '함수, 객체(메서드)'의 구분할 수 있는 거의 유일한 방법
1 상황에 따라 달라지는 this
this는 기본적으로 실행 컨텍스트가 생성될 때 함께 결정(실행컨텍스트 설명할때 설명됨)
아래 상황별로 this가 5가지 다른 값을 보여주는 내용을 설명
1-1 전역 공간에서의 this
전역 공간에서 this는 전역객체를 가리킨다.
-
전역 변수와 전역객체
var a = 1 console.log(a) //1 console.log(window.a) //1 console.log(this.a) //1
-
위 결과 값이 같은이유는?
- 전역변수 선언시 javascrip engine이 전역객체의 프로퍼티로 할당
- javascript 모든 변수는 특정 객체의 프로퍼티로 등록되고 동작
- 특정객체: 실행컨텍스트의 LexicalEnvironment
- 전역컨텍스트의 경우 LexicalEnvironment는 전역객체를 그대로 참조(실행컨텍스트 도식화 그림 참고
-
전역 변수를 접근하는 과정은? -> scope chain
- a에 접근하고자 하면 스코프 체인에서 a를 검색하다 가장 마지막에 도달하는 '전역 스코프'의 LexcialEnvironment(전역객체)에서 해당 프로퍼티를 a를 발견해서 그 값을 반환하기 때문
-
전역변수와 전역객체의 차이점
- 전역객체의 프로퍼티로 할당한 경우에서는 삭제 o
- 전역변수로 선언한 경우에는 삭제 x
var b = 2; delete b; //false console.log(b, window.b, this.b) //2 2 2 window.c = 3; delete window.c; //true console.log(c, window.c this.c); // Uncaught ReferenceError: c is not defined
1-2 메서드로서 호출할 때 그 메서드 내부에서의 this
함수 vs 메서드
-
함수를 실행하는 방법 2가지: 함수호출, 메서드
- 함수: 그 자체로 독립적인 기능을 수행
- 메서드: 자신을 호출한 대상 객체에 관한 동작을 수행
-
함수, 메서드로 호출시 this의 차이
- 함수 호출시 this: 전역객체 (아래 예제코드 POINT 주석 참고)
- 메서드로 호출시 this: 호출한 메서드
함수로서 호출, 메서드로서 호출 비교
var func = function(x){
console.log(this, x);
}
func(1); // Window{...} 1
var obj = {
method: func
}
obj.method(2) // {method: f} 2
-
함수로서 호출시 일반함수, arrow function 여부에 따른 차이
var obj = { bar: function() { // 함수로서 호출시 일반함수 console.log(this) // {bar:f} return function() { return this; // this = window }; }, } obj.bar()() === window //true //POINT
var obj = { bar: function() { // 함수로서 호출시 arrow function return () => this; //POINT: this = bar (주의: use arrow funciton) } }; obj.bar()() === obj //true
1-3 함수로서 호출할 때 그 함수 내부에서의 this
함수 내부에서의 this
- this에는 호출한 주체 정보가 담기는데 어떤 함수를 함수로서 호출시 this 지정 안됨
- 함수로서 호출하는 것은 호출 주체(객체지향 언어에서의 객체)를 명시하지 않고 개발자가 코드에 직접 관여해서 실행한 것이기 때문에 호출 주체의 정보를 알 수 없다.
- 실행 컨텍스트가 지정되지 않은 경우 this는 전역 객체를 바라본다 -> 따라서 함수에서 this는 전역객체를 가리킨다.
메서드의 내부함수에서의 this
-
실행순서1,2,3의 return value
- 실행순서1: {outer: f, outer2: f} === obj1
- 실행순서2: window{...}
- 실행순서3: {obj2Property: "obj2Property", innerMethod: ƒ}
- 포인트는 실행순서2, 3은 같은 function을 호출하지만 함수로서 호출(실행순서2), 함수로서 호출(실행순서3)에 따라 this값이 달라진다.
-
POINT
- innerFunc에 console.log는 B, C에 의해서 호출이 되는데
- B처럼 innerFunc이 함수로 호출될때는 this는 전역 변수를 가르킨다.
- C처럼 innerfunc이 메서도로 호출될때는 this는 호출한 대상을 가르킨다.
var obj1 = { outer: function() { console.log(this) //실행순서1 by A var innerFunc = function() { console.log(this) //실행순서2 by B / 실행순서3 by C } innerFunc() // B var obj2 = { innerMethod: innerFunc, obj2Property: "obj2Property", } obj2.innerMethod() // C }, outer2: function() {}, } obj1.outer() // A
메서드의 내부 함수에서의 this를 우회하는 방법
- POINT 참고
var obj1 = {
outer: function() {
console.log(this) // {outer: f}
var innerFunc1 = function() {
console.log(this) // window {...}
}
innerFunc1();
var me = this; // <= POINT
var innerFunc2 = function() {
console.log(me); // <= POINT: {outer: f}
}
innerFunc2()
},
}
obj1.outer()
this를 바인딩하지 않는 함수
아래 예제 주석 참고
-
log 결과값
- [1]: {outer: f}
- [2]: window {...}
- [3]: {outer: f}
- [4]: window {...}
- [5]: {outer: f}
var obj1 = { outer: function() { console.log(this) // [1] var innerFunc = function() { console.log(this) // [2] } innerFunc() var me = this var innerFunc2 = function() { console.log(me) // [3] POINT 메서드의 내부 함수에서의 this를 우회하는 방법 console.log(this) // [4] } innerFunc2() var innerFunc3 = () => { console.log(this) // [5] POINT: this를 바인딩하지 않는 함수(arrow function) } innerFunc3() }, } obj1.outer()
1-4 콜백 함수 호출 시 그 함수 내부에서의 this
callback function의 제어권을 가지는 함수(메서드)가 콜백 함수에서의 this를 무엇으로 할지를 결정
특별히 정의하지 않은 경우에는 기본적으로 함수와 마찬가지로 전역객체를 바라본다.
콜백 함수에 bind를 사용하여 2-4 bind
setTimeout(function() {
console.log(this) //window 객체
}, 3000)
;[1, 2, 3].forEach(function(v) {
console.log(this, v) //window 객체
})
//event Callback function 참고
document.body.innerHTML += `<button id="a"> 클릭 </button>`
document.body.querySelector("#a").addEventListener("click", function(e) {
console.log(this, e)
})
- "button click event Callback function this"과 "id='a' dom" 객체 비교 => 같다.
-
addEventListner function 내부 구현 추측
-
addEventListner function this가 'document.body.querySelector('#a')' 이기 때문에
이 값을 callback function을 call메서드를 이용해 명시적으로 this바인딩 할 수 있겠다.addEventListener: function(a, callbackFunc){ ... callbackFunc.call(this, event); ... }
-
1-5 생성자 함수 내부에서의 this
생성자 함수에서 this는 생성될 인스턴스를 참조
var Dog = function(name, age) {
this.bark = "RRRR"
this.name = name
this.age = age
}
var planets = new Dog("행성", 2)
var girl = new Dog("소녀", 3)
var universe = new Dog("우주", 1)
console.log(planets, girl, universe)
/*
planets: Dog {bark: "RRRR", name: "행성", age: 2}
girl: Dog {bark: "RRRR", name: "소녀", age: 3}
universe: Dog {bark: "RRRR", name: "우주", age: 1}
*/
console.log({ bark: planets.bark, name: planets.name, age: planets.age })
/*
{bark: "RRRR", name: "행성", age: 2}
*/
- 위 두 console.log를 확인했을때 Dog에의해서 생성된 instance가 this가 된것을확인 할 수 있다.
2 명시적으로 this를 바인딩하는 방법
2 이 목차는 call(), apply(), bind()에 대해서 자세하게 다루도록 위해서 다른 포스트에서 설명하도록한다. call(), apply(), bind()
2-1 call 메서드
2-2 apply 메서드
2-3 call/apply 메서드의 활용
유사배열객체에 배열 메서드를 적용
생성자 내부에서 다른 생성자 호출
여러 인수를 묶어 하나의 배열로 전달하고 싶을 때 apply 활용
2-4 bind 메서드
name 프로퍼티
상위 컨텍스트의 this를 내부함수나 콜백함수에 전달하기
var obj = {
outer: function() {
console.log(this) //{outer: ƒ, outer2: ƒ}
var innerFunc = function() {
console.log(this) //{outer: ƒ, outer2: ƒ}
}
innerFunc.call(this) //POINT
},
outer2: function() {
console.log(this) //{outer: ƒ, outer2: ƒ}
var innerFunc = function() {
console.log(this) //{outer: ƒ, outer2: ƒ}
}.bind(this) //POINT
innerFunc()
},
outer3: function() {
console.log(this) //{outer: ƒ, outer2: ƒ}
var innerFunc = function() {
console.log(this) // window{...}
} //POINT
innerFunc()
},
}
obj.outer()
obj.outer2()
obj.outer3()
2-5 arrow function의 예외사항
- "this를 바인딩하지 않는 함수" 목차 참고
2-6 별도의 인자로 this를 받는 경우(콜백 함수 내에서의 this)
var report = {
sum: 0,
count: 0,
add: function() {
var args = Array.prototype.slice.call(arguments)
args.forEach(function(arg, idx) {
debugger //idx가 0일때 this -> {sum: 0, count: 0, add: ƒ, average: ƒ} :report 객체
this.sum += arg
++this.count
}, this) //POINT
},
average: function() {
return this.sum / this.count
},
}
report.add(10, 20, 30)
console.log({ sum: report.sum, count: report.count, average: report.average() })
//{sum: 60, count: 3, average: 20}
-
만약 위 코드에서 POINT 부분 forEach function에 두번째 parameter "thisArg"가 없었다면 this.sum += entry;에서 this는 window객체를 가르키기 때문에 console.log 결과는 아래와 같다.
{sum: 0, count: 0, average: NaN}
- 콜백함수와 함께 thisArg를 인자로 받는 메서드
Array.prototype.forEach(callback[, thisArg]);
Array.prototype.map(callback[, thisArg]);
Array.prototype.filter(callback[, thisArg]);
Array.prototype.some(callback[, thisArg]);
Array.prototype.every(callback[, thisArg]);
Array.prototype.find(callback[, thisArg]);
Array.prototype.findIndex(callback[, thisArg]);
Array.prototype.flatMap(callback[, thisArg]);
Array.prototype.from(callback[, thisArg]);
Set.prototype.forEach(callback[, thisArg]);
Map.prototype.forEach(callback[, thisArg]);
참고
- 코어 자바스크립트 - 위키북스
- 인사이드 자바스크립트
- 자바스크립트 완벽 가이드