[모던 자바스크립트 Deep Dive]22장 this
21.1 this
키워드
- 객체: 상태를 나타내는 프로퍼티와 동작을 나타내는 메서드를 하나의 논리적 단위로 묶은 복합 자료구조
this
탄생 배경
- 메서드가 자신이 속한 객체를 가리키는 식별자를 참조할 방법이 필요
- 재귀적으로 참조하는 경우 ⇒ 바람직하지 않다.
1 2 3 4 5 6 7 8 9 10 11 12
const circle = { // 프로퍼티: 객체 고유의 상태 데이터 radius: 5, // 메서드: 상태 데이터를 참조하고 조작하는 동작 getDiameter() { // 이 메서드가 자신이 속한 객체의 프로퍼티나 다른 메서드를 참조하려면 // 자신이 속한 객체인 circle을 참조할 수 있어야 한다. return 2 * circle.radius; } }; console.log(circle.getDiameter()); // 10
생성자 함수가 만든 인스턴스 ⇒ 인스턴스 생성 전이므로 정의 불가 ⇒
this
탄생1 2 3 4 5 6 7 8 9 10 11 12
function Circle(radius) { // 이 시점에는 생성자 함수 자신이 생성할 인스턴스를 가리키는 식별자를 알 수 없다. ????.radius = radius; } Circle.prototype.getDiameter = function () { // 이 시점에는 생성자 함수 자신이 생성할 인스턴스를 가리키는 식별자를 알 수 없다. return 2 * ????.radius; }; // 생성자 함수로 인스턴스를 생성하려면 먼저 생성자 함수를 정의해야 한다. const circle = new Circle(5);
this
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// this는 어디서든지 참조 가능하다.
// 전역에서 this는 전역 객체 window를 가리킨다.
console.log(this); // window
function square(number) {
// 일반 함수 내부에서 this는 전역 객체 window를 가리킨다.
console.log(this); // window
return number * number;
}
square(2);
const person = {
name: 'Lee',
getName() {
// 메서드 내부에서 this는 메서드를 호출한 객체를 가리킨다.
console.log(this); // {name: "Lee", getName: ƒ}
return this.name;
}
};
console.log(person.getName()); // Lee
function Person(name) {
this.name = name;
// 생성자 함수 내부에서 this는 생성자 함수가 생성할 인스턴스를 가리킨다.
console.log(this); // Person {name: "Lee"}
}
const me = new Person('Lee');
- 자신이 속한 객체 또는 자신이 생성할 인스턴스를 가리키는 자기 참조 변수
- 자바스크립트 엔진이 암묵적으로 생성
- 지역 변수인 것처럼 사용 가능
this
바인딩은 함수 호출 방식에 의해 동적으로 결정this
는 코드 어디에서든 참조 가능
22.2 함수 호출 방식과 this
바인딩
- 렉시컬 스코프 결정 시기: 함수 정의가 평가되어 함수 객체가 생성되는 시점
this
바인딩 결정 시기: 함수 호출 시점함수 호출 방법
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
// this 바인딩은 함수 호출 방식에 따라 동적으로 결정된다. const foo = function () { console.dir(this); }; // 동일한 함수도 다양한 방식으로 호출할 수 있다. // 1. 일반 함수 호출 // foo 함수를 일반적인 방식으로 호출 // foo 함수 내부의 this는 전역 객체 window를 가리킨다. foo(); // window // 2. 메서드 호출 // foo 함수를 프로퍼티 값으로 할당하여 호출 // foo 함수 내부의 this는 메서드를 호출한 객체 obj를 가리킨다. const obj = { foo }; obj.foo(); // obj // 3. 생성자 함수 호출 // foo 함수를 new 연산자와 함께 생성자 함수로 호출 // foo 함수 내부의 this는 생성자 함수가 생성한 인스턴스를 가리킨다. new foo(); // foo {} // 4. Function.prototype.apply/call/bind 메서드에 의한 간접 호출 // foo 함수 내부의 this는 인수에 의해 결정된다. const bar = { name: 'bar' }; foo.call(bar); // bar foo.apply(bar); // bar foo.bind(bar)(); // bar
- 일반 함수 호출
- 메서드 호출
- 생성자 함수 호출
Function.prototype.apply/call/bind
메서드에 의한 간접 호출
22.2.1 일반 함수 호출
일반 함수로 호출된 모든 함수(중첩 함수, 콜백 함수 포함) 내부
this
에는 전역 객체 바인딩1 2 3 4 5 6 7 8
function foo() { console.log("foo's this: ", this); // window function bar() { console.log("bar's this: ", this); // window } bar(); } foo();
strict mode에서 일반 함수 내부의
this
에는undefined
바인딩1 2 3 4 5 6 7 8 9 10
function foo() { 'use strict'; console.log("foo's this: ", this); // undefined function bar() { console.log("bar's this: ", this); // undefined } bar(); } foo();
- 일반 함수에서
this
는 의미가 없다.
- 일반 함수에서
메서드에서 정의한 중첩 함수도 일반 함수로 호출하면 전역 객체가 바인딩 된다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
// var 키워드로 선언한 전역 변수 value는 전역 객체의 프로퍼티다. var value = 1; // const 키워드로 선언한 전역 변수 value는 전역 객체의 프로퍼티가 아니다. // const value = 1; const obj = { value: 100, foo() { console.log("foo's this: ", this); // {value: 100, foo: ƒ} console.log("foo's this.value: ", this.value); // 100 // 메서드 내에서 정의한 중첩 함수 function bar() { console.log("bar's this: ", this); // window console.log("bar's this.value: ", this.value); // 1 } // 메서드 내에서 정의한 중첩 함수도 일반 함수로 호출되면 // 중첩 함수 내부의 this에는 전역 객체가 바인딩된다. bar(); } }; obj.foo();
콜백 함수도 일반 함수로 호출하면 전역 객체가 바인딩 된다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
var value = 1; const obj = { value: 100, foo() { console.log("foo's this: ", this); // {value: 100, foo: ƒ} // 콜백 함수 내부의 this에는 전역 객체가 바인딩된다. setTimeout(function () { console.log("callback's this: ", this); // window console.log("callback's this.value: ", this.value); // 1 }, 100); } }; obj.foo();
this
바인딩을 바꾸기 위해서this
를 변수에 할당하거나,Function.prototype.apply/call/bind
메서드를 사용하거나, 화살표 함수를 사용할 수 있다.1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
var value = 1; const obj = { value: 100, foo() { // this 바인딩(obj)을 변수 that에 할당한다. const that = this; // 콜백 함수 내부에서 this 대신 that을 참조한다. setTimeout(function () { console.log(that.value); // 100 }, 100); } }; obj.foo();
1 2 3 4 5 6 7 8 9 10 11 12 13
var value = 1; const obj = { value: 100, foo() { // 콜백 함수에 명시적으로 this를 바인딩한다. setTimeout(function () { console.log(this.value); // 100 }.bind(this), 100); } }; obj.foo();
1 2 3 4 5 6 7 8 9 10 11
var value = 1; const obj = { value: 100, foo() { // 화살표 함수 내부의 this는 상위 스코프의 this를 가리킨다. setTimeout(() => console.log(this.value), 100); // 100 } }; obj.foo();
22.2.2 메서드 호출
- 메서드 이름 앞의 마침표 연산자 앞에 기술한 객체를 바인딩
메서드를 소유한 객체가 아닌 호출한 객체를 바인딩
1 2 3 4 5 6 7 8 9 10
const person = { name: 'Lee', getName() { // 메서드 내부의 this는 메서드를 호출한 객체에 바인딩된다. return this.name; } }; // 메서드 getName을 호출한 객체는 person이다. console.log(person.getName()); // Lee
# 22-15
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
const anotherPerson = { name: 'Kim' }; // getName 메서드를 anotherPerson 객체의 메서드로 할당 anotherPerson.getName = person.getName; // getName 메서드를 호출한 객체는 anotherPerson이다. console.log(anotherPerson.getName()); // Kim // getName 메서드를 변수에 할당 const getName = person.getName; // getName 메서드를 일반 함수로 호출 console.log(getName()); // '' // 일반 함수로 호출된 getName 함수 내부의 this.name은 // 브라우저 환경에서 window.name과 같다. // 브라우저 환경에서 window.name은 브라우저 창의 이름을 나타내는 // 빌트인 프로퍼티이며 기본값은 ''이다. // Node.js 환경에서 this.name은 undefined다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
function Person(name) { this.name = name; } Person.prototype.getName = function () { return this.name; }; const me = new Person('Lee'); // getName 메서드를 호출한 객체는 me다. console.log(me.getName()); // Lee Person.prototype.name = 'Kim'; // getName 메서드를 호출한 객체는 Person.prototype이다. console.log(Person.prototype.getName()); // Kim
22.2.3 생성자 함수 호출
생성자 함수가 미래에 생성할 인스턴스를 바인딩
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
// 생성자 함수 function Circle(radius) { // 생성자 함수 내부의 this는 생성자 함수가 생성할 인스턴스를 가리킨다. this.radius = radius; this.getDiameter = function () { return 2 * this.radius; }; } // 반지름이 5인 Circle 객체를 생성 const circle1 = new Circle(5); // 반지름이 10인 Circle 객체를 생성 const circle2 = new Circle(10); console.log(circle1.getDiameter()); // 10 console.log(circle2.getDiameter()); // 20 // new 연산자와 함께 호출하지 않으면 생성자 함수로 동작하지 않는다. // 즉, 일반적인 함수의 호출이다. const circle3 = Circle(15); // 일반 함수로 호출된 Circle에는 반환문이 없으므로 암묵적으로 undefined를 반환한다. console.log(circle3); // undefined // 일반 함수로 호출된 Circle 내부의 this는 전역 객체를 가리킨다. console.log(radius); // 15
22.2.4 Function.prototype.apply/call/bind
메서드에 의한 간접 호출
Function.prototype.apply/call/bind
메서드는Function.prototype
의 메서드이므로 모든 함수가 상속받아 사용 가능
Function.prototype.apply/call
메서드
this
로 사용할 객체를 첫 번째 인수로 받고, 객체를this
에 바인딩하여 함수를 호출1 2 3 4 5 6 7 8 9 10 11 12 13
function getThisBinding() { return this; } // this로 사용할 객체 const thisArg = { a: 1 }; console.log(getThisBinding()); // window // getThisBinding 함수를 호출하면서 인수로 전달한 객체를 // getThisBinding 함수의 this에 바인딩한다. console.log(getThisBinding.apply(thisArg)); // {a: 1} console.log(getThisBinding.call(thisArg)); // {a: 1}
- 두 번째 인수로 인수 리스트를 전달
Function.prototype.apply
에는 인수 리스트를 배열 또는 유사 배열 객체에 담아서 넘긴다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
function getThisBinding() { console.log(arguments); return this; } // this로 사용할 객체 const thisArg = { a: 1 }; // getThisBinding 함수를 호출하면서 인수로 전달한 객체를 // getThisBinding 함수의 this에 바인딩한다. // apply 메서드는 호출할 함수의 인수를 배열로 묶어 전달한다. console.log(getThisBinding.apply(thisArg, [1, 2, 3])); // Arguments(3) [1, 2, 3, callee: ƒ, Symbol(Symbol.iterator): ƒ] // {a: 1} // call 메서드는 호출할 함수의 인수를 쉼표로 구분한 리스트 형식으로 전달한다. console.log(getThisBinding.call(thisArg, 1, 2, 3)); // Arguments(3) [1, 2, 3, callee: ƒ, Symbol(Symbol.iterator): ƒ] // {a: 1}
arguments
객체와 같은 유사 배열 객체에 배열 메서드를 사용할 수 있게 한다.1 2 3 4 5 6 7 8 9 10 11 12 13
function convertArgsToArray() { console.log(arguments); // arguments 객체를 배열로 변환 // Array.prototype.slice를 인수없이 호출하면 배열의 복사본을 생성한다. const arr = Array.prototype.slice.call(arguments); // const arr = Array.prototype.slice.apply(arguments); console.log(arr); return arr; } convertArgsToArray(1, 2, 3); // [1, 2, 3]
Function.prototype.bind
메서드
첫 번째 인수로 전달한 값을
this
에 바인딩하도록 함수를 새로 생성해 반환1 2 3 4 5 6 7 8 9 10 11 12
function getThisBinding() { return this; } // this로 사용할 객체 const thisArg = { a: 1 }; // bind 메서드는 첫 번째 인수로 전달한 thisArg로 this 바인딩이 교체된 // getThisBinding 함수를 새롭게 생성해 반환한다. console.log(getThisBinding.bind(thisArg)); // getThisBinding // bind 메서드는 함수를 호출하지는 않으므로 명시적으로 호출해야 한다. console.log(getThisBinding.bind(thisArg)()); // {a: 1}
메서드의
this
와 메서드 내부 중첩 함수 또는 콜백 함수의this
가 불일치하는 문제 해결 가능1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
const person = { name: 'Lee', foo(callback) { setTimeout(callback, 100); } }; person.foo(function () { console.log(`Hi! my name is ${this.name}.`); // ② Hi! my name is . // 일반 함수로 호출된 콜백 함수 내부의 this.name은 // 브라우저 환경에서 window.name과 같다. // 브라우저 환경에서 window.name은 브라우저 창의 이름을 나타내는 // 빌트인 프로퍼티이며 기본값은 ''이다. // Node.js 환경에서 this.name은 undefined다. });
1 2 3 4 5 6 7 8 9 10 11
const person = { name: 'Lee', foo(callback) { // bind 메서드로 callback 함수 내부의 this 바인딩을 전달 setTimeout(callback.bind(this), 100); } }; person.foo(function () { console.log(`Hi! my name is ${this.name}.`); // Hi! my name is Lee. });
This post is licensed under CC BY 4.0 by the author.