도대체 어딜 바라보는지 알 수 없는 this를 간파해보자.
참고) 코어 자바스크립트
this는 실행 컨텍스트가 생성될 때 결정된다.
실행 컨텍스트는 함수가 호출될 때 생성되므로,
함수 호출 시점에 따라 this가 바라보는 대상이 달라질 수 있음을 의미한다.
전역 공간에서 this는 전역 객체
를 가리킨다.
브라우저 환경에서 전역객체는 window
, Node.js 환경에서는 global
이다.
console.log(this === window) // true
어떤 함수를 호출할 때, 함수 이름 앞에 객체가 있는 경우,
그 함수는 해당 객체의 메서드이다.
메서드로써 호출할 때,
즉 점표기법(.)
이나 대괄호표기법[]
으로 메서드를 호출한다면
호출 대상의 객체가 this
가 된다.
const obj = {
method: function (x) { console.log(this, x); }
};
obj.method(1) // { method: f }, 1
obj['method'](2) // { method: f }, 2
함수로써 호출할 때, this는 스코프 체인상의 최상위 객체인 window
가 된다.
왜냐하면 함수는 호출 시 호출 주체(객체 지향에선 객체)를 명시하지 않기 때문에,
호출 시 실행 컨텍스트에 this가 지정되지 않고,
이럴 경우 this는 전역객체를 바라보게 된다.
😎 정리: 함수를 호출했을 때 앞에 점(.)
| 대괄호[]
표기를 사용했다면
메서드
로써 호출된 것이고,
이때의 this는 메서드 구문 앞에 객체
가 된다.
함수로써 호출된 경우는 this가 전역 객체(window)
가 된다.
this에 관해서 내가 오해하고 있던 사실은,
함수로써 호출될 때에도 해당 스코프에 this가 없다면
상위 객체에 닿을 때까지 올라가는 형식인 줄 알았다.
하지만 함수가 호출되고 실행 컨택스트가 만들어지면 this가 없기 때문에
this는 바로 전역 객체가 된다.
A 함수의 인자로 B함수를 전달할 때, B함수를 콜백함수라 한다.
A 함수는 B 함수의 내부 로직에 따라 실행되고, this 또한 이 때 값이 결정된다.
document.body.querySelector('#a')
.addEventListener('click', function (e) {
console.log(this, e) // this = document.body.querySelector('#a')
})
콜백함수 또한 함수이기 때문에 this를 지정하지 않으면 this = window
가 되지만,
addEventListener 내부의 콜백함수는 addEventListener 메서드
의 this를 상속받는다.
즉, A 함수(메서드)가 콜백 함수의 this를 결정하고,
특별히 없을 경우 콜백 함수의 this는 전역 객체를 바라본다.
객체지향 언어에서 생성자는 클래스(class)
이고,
클래스를 통해 만든 객체를 인스턴스(instance)
라 한다.
JS에선 생성자 함수를 통해 함수 또한 생성자의 역할을 할 수 있다.
어떤 함수가 new 키워드로 호출되면, 그때의 this는 곧 새로 만들 인스턴스 자신이 된다.
let Cat = function (name, age) {
this.bark = '야옹';
this.name = name;
this.age = age;
};
let cheeze = new Cat('치즈', 7);
console.log(cheeze);
// Cat { bark: '야옹', name: '치즈', age: 7 } => 인스턴스 객체
🔥 과정 설명
Cat 변수에 name, age를 인자로 받는 익명 함수를 할당함.
👇🏻
함수 내부에선 this에 접근해 각 프로퍼티에 값을 대입.
👇🏻
new 키워드로 Cat 함수(생성자 함수)를 호출해 변수 cheeze에 할당.
👇🏻
console.log로 출력 시 Cat 클래스의 인스턴스 객체가 출력.
즉 cheeze에 할당된 생성자 함수 내부의 this는 cheeze 인스턴스를 바라본다.
현재 컨텍스트에 this가 없다면, 바로 전역객체로 올라가지 않고
직전 컨텍스트의 this를 바라보게 할 순 없을까?
이 방법은 꼼수라고 할 수 있다.
상위 스코프의 this
를 변수에 저장한다음 내부 함수에서 변수를 사용하는 방법이다.
// obj라는 객체가 있고, outer 메서드가 있는 상황.
// outer 메서드 안에 inner라는 함수가 있음 (메서드 아님)
const obj = {
outer: function () {
let self = this;
let inner = fuction () {
console.log(self);
};
inner();
}
};
obj.outer(); // this = obj
🔥 과정 설명
outer 스코프에서 self 변수에 this를 저장한다.
👇🏻
outer 스코프는 obj.outer()
형태로 실행되는데
이때 outer 메서드의 실행 컨텍스트 내부에 this = obj
가 된다.
👇🏻
그다음 outer 메서드의 내부 위에서 아래로 파싱하며 self 변수에 this(=obj)
가 할당된다.
👇🏻
inner()
가 실행되면 console.log(self) 는 obj가 출력된다.
ES6에서, 함수 호출 시
this가 전역객체를 바라보는 문제를 보완하고자 화살표 함수를 도입했다.
화살표 함수
는 함수 호출 시 실행 컨텍스트에서 this 바인딩 과정을 생략해 this가 아예 없고,
스코프 체인 상 가장 가까운 this
에 접근한다.
그 전까지는 왜 이방법을 사용하지 않았냐면,
ES5까진 this의 이런 문제가 설계상의 오류였기 때문이라 한다 ㅋㅋㅋ
let obj = {
outer: function () {
console.log(this); // this = obj
let inner = () => {
console.log(this); // this = obj
};
inner();
}
}
call 메서드는 첫 번째 인자를 this로 바인딩하고,
나머지 인자들을 호출할 함수의 매개변수로 지정한다.
call 메서드를 활용하면 임의의 객체를 this로 지정할 수 있다.
const func = function (a, b, c) {
console.log(this, a, b, c);
};
func(1, 2, 3); // this = window
func.call({ x: 1 }, 4, 5, 6); // this = {x : 1}
const obj = {
a: 1,
method: function (x, y) {
console.log(this.a, x, y);
}
};
obj.method(2, 3) // 1, 2, 3
obj.method.call({a : 4}, 5, 6) // 4, 5, 6
첫번째 인자를 this로 바인딩하고,
두번째 인자를 배열로 받아 해당 배열의 요소들을 호출할 함수의 매개변수로 지정한다.
const func = function (a, b, c) {
console.log(this, a, b, c);
};
func.apply({x : 1}, [4, 5, 6]); // {x : 1}, 4, 5, 6
const obj = {
a: 1,
method: function (x, y) {
console.log(this.a, x, y);
}
};
obj.method.apply({a: 4}, [5, 6]); // 4, 5, 6
bind 메서드는 넘겨받은 this와 인수들을 바탕으로 새 함수를 반환한다.
const func = function (a, b, c, d) {
console.log(this, a, b, c, d);
};
func(1, 2, 3, 4); // window{...}, 1, 2, 3, 4
const bindFunc = func.bind({x: 1})
bindFunc() // {x: 1} undefined undefined undefined undefined
const bindFunc2 = func.bind({x: 1}, 4, 5);
bindFunc2(); // {x: 1}, 4, 5, undefined, undefined
bindFunc2(6, 7); // {x: 1}, 4, 5, 6, 7
bindFunc2(8, 9); // {x: 1}, 4, 5, 8, 9
bindFunc
변수는 this를 {x: 1}
로 지정한 새 함수가 담긴다.
bindFunc2
변수는 this를 {x: 1}
로,
앞에서 두 개의 인수를 4, 5로 지정한 새 함수를 할당한다.
bind 메서드로 반환된 새 함수는 name 프로퍼티
에 bound
접두사가 붙는다.
const func = function (a, b, c, d) {
console.log(this, a, b, c, d);
};
console.log(func.name) // func
const bindFunc = func.bind({x: 1});
console.log(bindFunc.name)// bound func
원본 함수 func에 bind
를 적용한 함수라는 걸 알 수 있는 덕분에 코드 추적에 용이하다.
앞에 🧐 this 바인딩 1. 변수 활용하기에서 썼던 예시를
call
과 bind
로 깔끔하게 처리할 수 있다.
const obj = {
outer: function () {
console.log(this); // outer 메서드의 this = obj
const inner = function () {
console.log(this);
};
inner.call(this); // call 메서드로 즉시실행 + this 바인딩 to obj
}
};
obj.outer();
const obj = {
outer: function () {
console.log(this); // this = obj
const inner = function () {
console.log(this);
}.bind(this);
inner(); // this = obj
}
};
obj.outer();
자바스크립트의 유연함 덕분인지 this는 함수 호출에 따라 바라보는 객체가 매우 다르다.
그 때문에 여태까지 대충 넘어가 마음 한 켠에 부담으로 남아있었는데,
한번 쭉 훑고 나니 부담이 사라졌다.
아직 3주차이긴 하지만 여태까지 나는 어떠한 지식을 공부하기 위해
처음부터 끝까지 단계적으로 밟아나가려는 욕심이 있었다.
그렇게 하지 않으면 전에 배우지 못했던 지식들이 다음에 발목을 잡을 것 같기도 해서인데...
워낙에 강의에서 배울 게 많다보니 자연스럽게 이 공부법을 버리게 되었고,
당장에 필요하고 기초적인 지식들을 우선적으로 공부하고자 하는 욕심이 더 커졌다.
마치 가지치기를 하는 것처럼 그때 그때 필요한 지식들을 얻으니 오히려 더 안정적이고 부담감이 덜 하다.
또 단순히 읽는 것에 그치지 않고 TIL 이나 article로 남기니 한 문장을 적기 위해 여러번 곱씹는 과정에서 머릿속에 각인되는 것도 있다 ㅎㅎ
내 머릿속에 지식들이 쌓이는 게 기특하고 재밌다 힛😎