Before Start
JS에서 this 키워드가 가리키는 것은 현재 실행되고 있는 컨텍스트, 즉 execution context(global or function)의 프로퍼티 중 하나이다. this 키워드가 어디에서 작성되었는지와 strict 모드인지, 아닌지에 따라 이 프로퍼티는 달라진다. 이것은 다른 프로그래밍 언어에서는 보기 드문 자바스크립트 언어만의 특징이다.
+) execution context에는 lexical environment, variable environment가 있다. 다시 lexical environment에는 enviornment record, outer, this binding이 저장되어 있다.
This keyword
1. Global context
global 실행 컨텍스트(=함수 바깥)에서 this는, strict 모드인지 아닌지에 관계 없이, global object를 가리킨다. browser 환경에서 global object는 "window"를 가리킨다.
this === window; // true
a = 37;
console.log(window.a); // 37
console.log(a) // 37
2. Function context
함수 실행 컨텍스트 내에서는 this 키워드가 여러 상황에 따라 복잡하게 동작한다.
Strict vs. Sloppy
어떤 함수를 메서드로서 호출하는 것이 아니라 일반 함수로 호출한다면, strict mode인지 아닌지에 따라서도 this가 가리키는 것이 달라진다. 먼저 아래 코드는 strict모드가 아닐 때의 경우로, this가 global object인 window(browser)와 globalThis(node)를 가리키고 있는 것을 알 수 있다.
function f1() {
return this;
}
// In browser:
f1() === window; // true
// In Node:
f1() === globalThis; // true
strict mode에서는 함수 실행 컨텍스트의 this 값을 설정해 주지 않으면 undefined으로 남아있는다.
function f2() {
'use strict';
return this;
}
f2() === undefined;
// true
// because f2 was not called as a method but a function
window.f2(); // this => Window { ... }
f2.call(this); // this => Window { ... }
+) call이라는 JS 내장 함수를 이용하면, 인자를 전달함으로써 함수가 가리키는 this를 지정할 수 있다.
method
함수 실행 컨텍스트에서는, 함수를 어떻게 호출하고 있느냐에 따라서도(invocation context에 따라) this가 가리키는 것이 달라진다. 즉 this가 정적으로 고정되는 것이 아니라, 함수를 호출할 때 마다 동적으로 결정된다는 의미이다. 아래의 예제 코드를 통해서 이게 어떤 의미인지 자세히 살펴보자.
const cat = {
name: 'chris',
meow() {
console.log(`${this.name}: meow~`);
},
};
const chrisMeow = cat.meow;
cat.meow(); // it prints out "chris: meow~" (*)
chrisMeow(); // in strict mode, this === undefined, error! (**)
chriwMeow(); // in sloppy mode, this === window, it prints out "undefined: meow~" (***)
- 앞서 살펴본 바와 같이, meow를 cat의 메서드로 호출할 때에는(*) this가 cat 객체를 가리킨다.
- strict mode에서 cat 객체에 정의되어있던 meow method를 chrisMeow 변수에 할당한 뒤에, 함수를 실행하면 에러가 발생한다. chrisMeow를 그냥 함수로 호출할 때에는(**) this가 아무것도 가리키지 않기 때문이다.
- sloppy mode에서는 this가 window 객체(browser 환경)를 가리키지만, 그 객체에 name이라는 데이터가 없기 때문에 undefined을 출력한다.
+) 대부분의 객체 지향 프로그래밍 언어에서는 this가 정적으로 고정된다.
arrow function
const person = {
first: 'Taylor',
last: 'Swift',
full: () => {
return `${this.first} ${this.last}`;
},
};
console.log(person.full()); // Taylor Swift? undefiend undefined?
person 객체 내부에 full 메서드가 정의되어 있고, person을 이용해서 full 함수를 호출하고 있으므로 this가 가리키는 것은 person 객체일 것 같다는 생각이 든다. 하지만 full 함수가 function 키워드를 이용한 일반 함수가 아니라, 화살표 함수이기 때문에 코드의 실행 결과 undefined undefined이 출력된다.
const person = {
first: 'Taylor',
last: 'Swift',
fullName: () => {
return `${this.first} ${this.last}`;
},
shoutName: function() {
setTimeout(function() {
console.log(this.name);
}, 1000);
},
};
person.shoutName(); // Taylor Swift? undefiend?
이번에는 function 키워드를 이용한 일반 함수인 shoutName을 살펴보자. 이름 없는 함수 내부에 작성된 this 키워드는 person 객체를 가리키고 있을까? 안타깝게도 person 객체를 가리키고 있지 않아 undefined이 출력된다. 왜 이런 일이 발생하는 걸까?
arrow functions don't bind their own scope, but inherit it from the parent one,
- 화살표 함수 내부에 작성된 this는 그 함수가 만들어진 scope의 this와 같은 값을 가리킨다.
- 즉 fullName 함수에 사용된 this는 fullName이 만들어진 { scope }인 person의 this와 같은 값을 가리키고, person의 this는 global object(window or globalThis)를 가리킨다.
- this가 가리키는 것이 변하지 않고 고정된다.
- 반면 일반적인 함수 내부에서 작성된 this는 그 함수를 실행하는 방법(실행 컨텍스트)과 관련이 있다.
- ??? shoutName에 사용된 this는, shoutName이 아니라 setTimeout에 의해 실행된다.
- ??? 즉 이 때의 this는 setTimeout의 실행 컨텍스트인 global object(window or globalThis)를 가리킨다.
- 함수를 실행할 때 마다 this가 가리키는 게 무엇인지 신경써야 한다.
따라서 우리가 의도하는 대로 코드를 동작시키려면 다음과 같이 작성해야 한다.
const person = {
first: 'Taylor',
last: 'Swift',
fullName: function() {
return `${this.first} ${this.last}`;
},
shoutName: function() {
setTimeout(()=> { // arrow function's higher scope is { shoutname }, not setTimeout
console.log(this.name);
}, 1000);
},
};
Class & Constructor function context
method에서와 유사하게, 클래스와 생성자 함수의 실행 컨텍스트에서 this는 앞으로 만들어질 instance 객체 자체를 가리킨다.
This Binding
1. arrow function
앞서 살펴본 바와 같이, arrow 함수의 this는 함수가 만들어지는 순간 가장 인접한 상위 스코프의 this와 바인딩 된다. 즉 어떻게 함수를 호출하더라도 this가 변하지 않는다.
let obj = {
bar: function() {
let x = (() => this);
return x;
}
};
let fn = obj.bar(); // x는 () => this 이다.
console.log(fn() === obj); // true
let fn2 = obj.bar; // bar is not arrow function
console.log(fn2()() === undefined); // true in strict mode
- fn에는 화살표 함수 () => this가 저장된다.
- 화살표 함수는 만들어지는 순간, 인접한 상위 scope의 this와 binding 된다. 즉, bar의 this인 obj를 가리킨다.
- ??? fn2
// 1. function expression
let dog = {
name: 'puppy',
play: function () {
console.log(`강아지가 놀고있어`);
},
};
//2. function declaration
dog = {
name: 'puppy',
play() {
console.log(`강아지가 놀고있어`);
},
};
//3. arrow function
dog = {
name: 'puppy',
play: () => {
console.log('강아지가 놀고있어');
},
};
함수 expression으로 작성할 경우, prototype이라는 무거운 객체가 생성된다.
함수 declaration과 화살표 함수에서는 prototype 객체가 생성되지 않는다. 화살표 함수에서 사용되는 this는 함수에서 제일 근접한 상위 스코프의 this에 영원히 바인딩 된다. 위의 예제 코드에서는, play함수가 가리키는 this는 undefined에 바인딩 된다(this 출력해 보면 텅빈 객체가 나옴).
+) example
class Printer {
message = 'you clicked me';
print() {
console.log(this.message);
}
}
const testPrinter = new Printer();
const btn = document.querySelector('button')!;
btn.addEventListener('click', testPrinter.print) // undefined
btn.addEventListener('click', () => testPrinter.print()) // you clicked me
btn.addEventListener('click', testPrinter.print.bind(testPrinter)) // you clicked me
btn.addEventListener('click', () => console.log(this)) // window, 화살표 함수의 this는 상위 scope의 this와 같다
- testPrinter.print
- test.print는 btn에 등록된 eventListener에 의해 호출된다. 즉, testPrinter.print 내부에 작성된 this는 testPrinter가 아니다.
- this.message의 this는 testPrinter가 아니기 때문에 message가 존재하지 않으므로 undefined이 출력된다.
- () => testPrinter.print()
- print 함수를 호출하고 있는 것은 testPrinter이므로 this는 testPrinter가 된다.
- testPrinter.print.bind(testPrinter)
- bind 함수는 자신을 호출한 대상(testPrinter.print)이 나중에 실행될 때 가리킬 this를 첫번째 인자로 받는다.
- () => testPrinter.print() === testPrinter.print.bind(testPrinter)
'Study > JavaScript' 카테고리의 다른 글
Var (0) | 2022.05.25 |
---|---|
Request & Response | API, JSON, fetch... (0) | 2022.04.17 |
OOP | Prototype, Class (0) | 2022.04.05 |
Scope, Execution context, Closure (0) | 2022.04.04 |
Asynchronous | callBack, promise, async & await (0) | 2022.04.02 |