이 글은 Zerocho님의 [인간 JS 엔진 되기 1-6] 영상을 수강한 이후에 이를 토대로 정리한 글입니다.
영상의 출처를 이 글 하단에 남깁니다. 좋은 강의 해주신 Zerocho님 감사합니다.
- this : Browser 환경 (JavaScript)에서는 window, Server 환경 (Node)에서는 global 객체를 의미하는데, 최근에는 globalThis이라는 객체로 통합되었다고 함
- Strict mode에서의 this는 undefined가 된다.
- ES2015 모듈은 strict mode 자동 적용
- this는 함수가 '호출' 될때 결정된다!
- 기본적으로 this는 window를 가리키는데, 그것이 바뀌는 경우가 크게 3가지가 있다.
- this가 어떤 것을 가리키는지 확인하는 법 : 함수가 '호출' 될때, this가 가리키는 것을 바꾸어주는 행위 (아래 3가지 중 최소 1가지) 를 했는지만 체크하면 된다. 행위를 했으면, this가 바뀔 것이고, 그게 아니라면 그대로 window를 가리킨다.
- 그 3가지에 대해 하단에 정리함
1. 객체.메서드 형태로 호출하는 경우
const obj = {
name : 'kim',
sayName() {
console.log(this.name) // kim
}
}
obj.sayName(); // 호출!!
obj.sayName() 형식으로 호출했으므로, 이때 sayName 메서드의 this는 sayName 메서드를 포함하고 있는 객체인 obj가 된다.
만약 아래와 같은 형식으로 함수를 호출하면, 객체의 메서드라 하더라도 this가 obj를 가리키지 않는다.
const obj = {
name : 'kim',
sayName() {
console.log(this.name) // window.name이 되어 kim이 나오지 않는다.
}
}
const sayNewName = obj.sayName;
sayNewName(); // 호출!!
이때의 sayName 메서드는 sayNewName(); 의 형식으로 호출이 되므로 (그냥 일반적인 함수의 호출 형태이다.)
즉, this가 가리키는 객체는 변하지 않고 그대로 window 객체를 가리킨다.
=> 객체의 메서드에 있는 this라 하더라도 항상 그 객체를 가리킨다고 단정지으면 안된다.
2. new 연산자와 함께 생성자 함수를 호출하여 객체를 만드는 경우
function Human(name) {
this.name = name;
}
const human = new Human('kim');
console.log(human.name); // kim
JavaScript에서는 기본 함수에도 new 연산자를 붙여서 호출하면 생성자 함수처럼 동작하게 할 수 있다.
위와 같은 소스코드에서, new 연산자를 이용하여 함수를 호출하면, 이때의 this는 해당 객체를 가리킨다.
하지만 new 연산자와 함께 생성자 함수를 호출하지 않으면 생성자 함수로 동작하지 않으므로, 이때의 this는 해당 객체를 가리키지 않는다. (밑 코드 참고)
function Human(name) {
this.name = name;
}
const human = Human('kim'); // new 연산자 사용하지 않은 경우
console.log(human); // undefined
console.log(human.name); // human이 undefined 이므로, human.name은 접근 불가능, Error 발생
3. bind, apply, call 사용하는 경우
function sayName() {
console.log(this.name);
}
sayName(); // 이때 this는 window이므로, this.name을 출력하면 undefined이다.
// 아래의 경우에 this.name을 출력하면 kim 이다.
sayName.bind({name : 'kim'})()
sayName.apply({name : 'kim'})
sayName.call({name : 'kim'})
bind, call, apply를 사용하여 인위적으로 this가 가리키는 것을 변경했을때도 this가 가리키는 객체는 바뀔 수 있다.
<bind와 call, apply의 문법적 차이>
bind는 bind 해준 새로운 함수를 만들어주는 역할이므로, 뒤에 () 를 통해서 함수 호출을 해줘야 한다.
apply, call은 함수 호출까지 한번에 시켜주는 것이다.
또한, call은 일반적인 함수처럼 매개변수를 받지만, apply는 배열형태의 매개변수를 받는다.
문제 1
아래와 같은 코드에서는 어떠한 값이 출력될까?
const obj = {
name : 'kim',
sayName() {
console.log(this.name); // 이때의 this는 obj이므로 kim이 출력
function inner() {
console.log(this.name) // 이때의 this는 window이므로 undefined 또는 빈 값이 출력
}
inner();
}
}
obj.sayName();
일단 첫번째 console.log는 sayName 메서드 내부의 첫째 줄에 위치한다. this는 함수가 호출될 때 결정된다 하였고, sayName 메서드는 obj.sayName(); 의 형태로 호출되기 때문에, 이때의 this는 obj이고, console에는 kim이 출력된다.
두번째 console.log는 inner 일반 함수 내부에 위치하고, 이 inner 함수는 inner(); 의 형태로 호출되었으므로, 일반적인 함수의 호출 형태이다. 즉, 앞에서 언급했던 3가지 방법에 해당되지 않으므로, this는 window를 가리키며, console에는 제대로 된 값(kim)이 출력되지 않는다.
화살표 함수에서의 this는 조금 다르다.
화살표 함수의 this는 그 함수의 부모의 this와 같다.
심지어 화살표 함수가 위의 3가지의 방법으로 호출이 된다 하더라도, 부모의 this를 받아온다는 규칙은 유지된다.
const obj = {
name : 'kim',
sayName : () => {
console.log(this.name); // this는 obj 가 아니라 window 이다.
}
}
obj.sayName();
위 소스코드에서 sayName이 obj.sayName(); 의 형식으로 호출되었지만 (위 3가지 방법 중 첫번째 방법),
sayName이 화살표 함수이기 때문에 this는 부모의 this를 따라간다. sayName의 부모는 anonymous (최상위) 이므로, this는 window 객체를 가리킨다.
문제 2
const obj = {
name : 'kim',
sayName() {
console.log(this.name); // kim
const inner = () => {
console.log(this.name) // kim
}
inner();
}
}
obj.sayName();
앞에서 보았던 문제 1과 거의 같은 코드이다. inner 함수가 화살표 함수의 형태로 바뀌었다는 차이점만 있을 뿐이다.
inner 함수가 inner(); 의 형태로 호출되었기 때문에, 두번째 console.log 내부의 this는 window를 가리키는 것으로 생각할 수 있지만, 현 상황에서 inner 함수는 화살표 함수로 선언이 되었으므로, inner 함수의 부모인 sayName 메서드의 this가 그대로 inner 함수의 this가 된다. sayName 메서드는 obj.sayName(); 의 형태로 호출이 되었으므로 this는 obj이고, inner 함수의 this 또한 obj가 된다. 그러므로 두번째 console.log 역시 kim이 출력이 되는 것이다!
영상 출처 : https://www.youtube.com/watch?v=pgo6URFz8tc
'Javascript' 카테고리의 다른 글
콜백 지옥과 Promise, 그리고 async/await (0) | 2022.04.27 |
---|