-
함수 호출 방식에 따른 this 바인딩Studying/JavaScript 2021. 9. 28. 17:18
0. 자기 참조 변수 this
this는 자바스크립트 엔진이 암묵적으로 생성해 주는 자기 참조 변수이다.
자기 참조 변수란 자신이 속한 객체, 혹은 자신이 생성할 인스턴스를 가리키는 식별자라는 뜻이다.
생성자 함수가 인스턴스를 생성할 때, 생성자 함수를 정의하는 시점에는 아직 인스턴스를 생성하기 전이므로
인스턴스를 가리키는 식별자가 존재하지 않는다.
이러한 경우에 미래에 자신이 생성할 인스턴스를 가리키기 위해 다음과 같이 this를 사용할 수 있다.
function Square(side) { this.side = side; // this는 미래에 생성할 인스턴스(즉, 아래에서 생성하게 될 square객체)를 가리킨다. } const square = new Square(8); console.log(square.side); // 8
식별자와 값을 연결하는 것을 바인딩이라고 한다.
위의 예시에서는 this에 생성자함수 Square가 미래에 생성할 인스턴스가 바인딩되었다.
하지만 상황에 따라 this에 바인딩되는 객체가 달라진다.
렉시컬 스코프가 함수 호출과 관계없이 함수가 정의되는 시점에 정적으로 결정되는 반면
this에 바인딩되는 객체는 함수가 호출되는 시점에, 호출되는 방식에 따라 동적으로 결정된다.
함수가 호출되는 몇 가지 상황에 따라 this 바인딩이 어떻게 결정되는지 살펴보자.
1. 생성자 함수로 호출하는 경우의 this 바인딩
위에서 살펴본 예시에서 함수를 new 연산자와 함께 생성자 함수로 호출할 경우 this에 미래에 생성할 인스턴스가 바인딩됨을 알 수 있었다.
하지만 new 연산자와 함께 호출하지 않으면 생성자 함수로 동작하지 않고 일반 함수로 호출된다.
이때는 this에 인스턴스가 바인딩되지 않으므로 주의해야 한다.
다음 단락에서 이 경우를 살펴보자.
2. 일반 함수로 호출하는 경우의 this 바인딩
함수 getSquareArea를 정의하고 일반 함수로 호출해 보자.
const getSquareArea = function (side) { console.log('this: ', this); // 전역 객체 return side ** 2; }; console.log(getSquareArea(8)); // 64
일반 함수로 호출한 getSquareArea의 this에 전역 객체가 바인딩되어 다음과 같이 브라우저 환경에서는 Window가, Node.js 환경에서는 global이 출력된다.
브라우저에서 일반함수로 호출한 this에 전역 객체 Window가 바인딩된다. Node.js에서 일반함수로 호출한 this에 전역 객체 global이 바인딩된다. 3. 메서드로 호출할 경우의 this 바인딩
다음과 같이 객체 리터럴로 객체를 생성하고 메서드를 정의해 보자.
const square = { side: 8, getSquareArea() { console.log('this: ', this); return this.side ** 2; }, }; console.log(square.getSquareArea()); // 64
위 상황에서 this에는 square 객체가 바인딩되었는데, 메서드를 소유한 square 객체에 바인딩된 것인지 메서드를 호출한 square 객체에 바인딩된 것인지 알기 어렵다.
다시 다음과 같이 프로토타입 메서드를 정의하고 호출해 보자.
const Square = function (side) { this.side = side; }; Square.prototype.getSquareArea = function () { console.log('this: ', this); return this.side ** 2; }; const square1 = new Square(4); const square2 = new Square(8); console.log(square1.getSquareArea()); console.log(square2.getSquareArea());
이때 this는 메서드를 호출한 객체인 square1과 square2 객체에 각각 바인딩되었다.
즉, 메서드 내부의 this에 메서드를 호출한 객체가 바인딩된다.
하지만 메서드 내에서 중첩함수를 정의하거나 메서드에 콜백함수를 전달할 때, 일반함수로 호출된 중첩함수와 콜백함수의 this에는 전역 변수가 바인딩된다. 이는 외부함수와 중첩함수, 콜백함수의 this가 일치하지 않는다는 문제를 발생시킨다.
이 문제를 해결하는 방법을 알아보자.
4. 외부함수와 중첩함수, 콜백함수의 this를 일치시키는 방법
위에서 언급한 바와 같이 중첩함수, 콜백함수를 일반 함수로 호출시킬 경우 중첩함수와 콜백함수의 this에 전역 객체에 바인딩되어 외부함수의 this와 서로 다르다는 문제가 발생한다. 이 문제를 해결할 수 있는 방법을 알아보자.
4-1. Function.prototype.apply/call/bind 메서드로 간접 호출하기
Function.prototype.apply는 함수를 호출하기 위한 메서드이다.
다음과 같이 이 메서드의 첫 번째 인수로 this로 사용할 객체를 전달할 수 있다.
const square = { side: 8, getSquareArea(callback) { console.log('this: ', this); return callback.apply(this, [2]); } }; const power = function (n) { console.log('Callback this: ', this); return this.side ** n; }; console.log(square.getSquareArea(power));
이와 같이 this를 명시적으로 바인딩할 수 있는 메서드로 Function.prototype.call/bind가 있다.
이 메서드들의 사용법은 다음 포스팅에서 알아보도록 하자.4-2. this를 다른 변수에 할당하여 회피시키기
다음과 같이 this를 that과 같은 다른 변수에 할당시킨 후 이 변수를 콜백 함수에 전달하여 사용할 수 있다.
const square = { side: 8, getSquareArea(callback) { console.log('this: ', this); const that = this; return callback(that, 2); } }; const power = function (that, n) { console.log('that: ', that); return that.side ** n; }; console.log(square.getSquareArea(power));
4-3. Array.prototype.map(every/filter/find 등)의 두 번째 인수로 this 전달하기
Array.prototype.map의 구문을 mdn에서 찾아 보면 다음과 같다.
arr.map(callback(currentValue[, index[, array]])[, thisArg])
여기서 마지막 인수 thisArg를 통해 map에 this로 사용할 객체를 전달할 수 있다.
4-4. 화살표 함수 사용하기
이 문제를 해결하기 위한 가장 단순한 방법은 ES6에서 도입된 화살표 함수를 사용하는 방법이다.
화살표 함수는 함수 자체의 this 바인딩을 갖지 않으므로 화살표 함수 내에서 this를 참조하면 상위 스코프의 this를 참조하게 되므로 외부함수와 this 바인딩이 달라지는 문제를 해결할 수 있다.
다만 메서드를 화살표 함수로 정의하는 것은 피하고, ES6 메서드 축약 표현으로 정의한 ES6 메서드를 사용하는 것이 좋다.
반응형'Studying > JavaScript' 카테고리의 다른 글
Date 생성자함수, 프로토타입 객체의 프로퍼티 (0) 2021.10.19 표준 빌트인 객체 Date (0) 2021.10.12 .filter().map()과 .reduce() (0) 2021.10.04 Array.prototype.sort() 메서드 (0) 2021.10.03 함수 이름과 기명함수, 익명함수 (2) 2021.09.13