개발일지
코어 자바스크립트 3강 this 본문
3강 this
자바스크립트에서 가장 혼란스러운 개념을 고르라고 하면 많은 사람들이 this을 꼽을 것입니다. 다른 대부분의 객체지향 언어에서 this는 클래스로 생성한 인스턴스 객체를 의미합니다. 클래스에서만 사용할 수 있기 때문에 혼란의 여지가 없거나 많지 않습니다. 그러나 자바스크립트에서의 this는 어디서든 사용할 수 있습니다. 상황에 따라 this가 바라보는 대상이 달라지는데, 어떤 이유로 그렇게 되는지를 파악하기 힘든 경우도 있고 예상과 다르게 엉뚱한 대상을 바라보는 경우도 있습니다. 이런 경우에 문제를 해결하려면 원인을 추적해서 수정해야 하는데, 정확한 작동 방식을 이해하지 못하면 원인을 파악해서 해결할 수 없습니다.
함수와 객체의 구분이 느슨한 자바스크립트에서 this는 실질적으로 이 둘을 구분하는 거의 유일한 기능입니다. 그 원인을 효과적으로 추적하는 방법 등을 알아보겠습니다.
1. 상황에 따라 달라지는 this
자바스크립트에서 this는 기본적으로 실행 컨텍스트가 생성될 때 함께 결정됩니다. 실행 컨텍스트는 함수를 호출할 때 생성되므로, 바꿔 말하면 this는 함수를 호출할 때 결정된다고 할 수 있습니다.
3-1-1 전역 공간에서의 this
전역 공간에서의 this는 전역 객체를 가리킵니다. 개념상 전역 컨텍스트를 생성하는 주체가 바로 전역 객체이기 때문입니다. 전역 객체는 자바스크립트 런타임 환경에 따라 다른 이름과 정보를 가지고 있습니다. 브라우저 환경에서 전역객체는 window이고 Node.js 환경에서는 global입니다.
전역공간에서의 this(브라우저 환경)
console.log(this);
console.log(window);
console.log(this === window); // true
전역 공간에서의 this(Node.js 환경)
console.log(this);
console.log(golbal);
console.log(this === global); // true
3-1-2 메서드로서 호출할 때 그 메서드 내부에서의 this
가장 일반적인 방법 두 가지는 함수로서 호출하는 경우와 메서드로서 호출하는 경우입니다. 프로그래밍 언어에서 함수와 메서드는 미리 정의한 동작을 수행하는 코드 뭉치로, 이 둘을 구분하는 유일한 차이는 독입성에 있습니다. 함수는 그 자체로 독립적인 기능을 수행하는 반면, 메서드는 자신을 호출한 대상 객체에 관한 동작을 수행합니다. 자바스크립트는 상황별로 this 키워드에 다른 값을 부여하게 함으로써 이를 구현했습니다.
함수로서 호출, 메서드로서 호출
var func = function(x) {
console.log(this, x);
};
func(1); // Window {...} 1
var obj = {
method: func
};
obj.method(2); // {method: f} 2
1번째 줄에서 func라는 변수에 익명함수를 할당했습니다. 4번째 줄에서 func를 호출했더니 this로 전역객체 Window가 출력됩니다. 6번째 줄에서 obj라는 변수에 객체를 할당하는데, 그 객체의 method를 호출했더니, 이번에는 this가 obj라고 합니다. obj의 method 프로퍼티에 할당한 값과 func 변수에 할당한 값은 모두 1번째 줄에서 선언한 함수를 참조합니다.
함수로서 호출과 메서드로서 호출 구분방법 함수 앞의(.)의 여부로 구분이 가능합니다.
3-1-3 함수로서 호출할 때 그 함수 내부에서의 this
어떤 함수를 함수로서 호출할 경우에는 this가 지정되지 않습니다. this에는 호출한 주체에 대한 정보가 담긴다고 했습니다. 그런데 함수로서 호출하는 것은 호출 주체를 명시하지 않고 개발자가 코드에 직접 관여해서 실행한 것이기 때문에 호출 주체의 정보를 알 수 없습니다. 실행 컨텍스트를 활성화할 당시에 this가 지정되지 않은 경우 this는 전역 객체를 바라본다고 했습니다. 따라서 함수에서의 this는 전역 객체를 가리킵니다.
3-1-4 콜백 함수 호출 시 그 함수 내부에서의 this
setTimeout(function () { console.log(this); }, 300); // (1)
[1, 2, 3, 4, 5].forEach(function (x) { // (2)
console.log(this, x);
});
document.body.innerHTML += '<button id="a">클릭</button>';
document.body.querySelector('#a')
.addEventListener('click', function (e) { // (3)
console.log(this, e);
});
(1)의 setTimeout 함수와 (2)의 forEach 메서드는 그 내부에서 콜백 함수를 호출할 때 대상이 될 this를 지정하지 않습니다. 따라서 콜백 함수 내부에서의 this 는 전역객체를 참조합니다. 한편 (3)의 addEventListener 메서드는 콜백 함수를 호출할 때 자신의 this를 상속하도록 정의되어 있습니다. 그렇기 때문에 메서드명의 점(.) 앞부분이 곧 this가 됩니다.
3-1-5 생성 함수 내부에서의 this
생성자 함수는 어떤 공통된 성질을 지니는 객체들을 생성하는 데 사용하는 함수입니다. 프로그래밍적으로 '생성자'는 구체적인 인스턴스를 만들기 위한 일종 틀입니다. 이 틀에는 해당 클래스의 공통 속성들이 미리 준비되어 있고, 여기에 구체적인 인스턴스의 개성을 더해 개별 인스턴스를 만들 수 있는 것입니다.
var Cat = function (name, age) {
this.bark = '야옹';
this.name = name;
this.age = age;
};
var choco = new Cat('초코', 7);
var nabi = new Cat('나비', 5);
console.log(choco, nabi);
/* 결과
Cat { bark: '야옹', name: '초코', age: 7 }
Cat { bark: '야옹', name: '나비', age: 5 }
*/
Cat이란 변수에 익명함수를 할당했습니다. 이 함수 내부에서는 this에 접근해서 bark, name, age 프로퍼티에 각각 값을 대입합니다. 6번째와 7번째 줄에서는 new 명령어와 함께 Cat 함수를 호출해서 변수 choco, nabi에 각각 할당했습니다. 8번째 줄에서 choco와 nabi를 출력해 보니 각각 Cat 클래스의 인스턴스 객체가 출력됩니다. 즉 6번째 중에서 실행한 생성자 함수 내부에서의 this는 choco인스턴스를 7번째 줄에서 실행한 생성자 함수 내부에서의 this는 nabi 인스턴스를 가리킵니다.
2. 명시적으로 this를 바인딩하는 방법
3-2-1 call 메서드
Function.prototype.call(thisArg[, arg1[, arg2[, ...]]])
call 메서드는 메서드의 호출 주체인 함수를 즉시 실행하도록 하는 명령입니다. 이때 call 메서드의 첫 번째 인자를 this로 바인딩하고, 이후의 인자들을 호출할 함수의 매개변수로 합니다. 함수를 그냥 실행하면 this는 전역객체를 참조하지만 call 메서드를 이용하면 임의의 객체를 this로 지정할 수 있습니다.
var func = function (a, b, c) {
console.log(this, a, b, c);
};
func(1, 2, 3); // Window{...} 1 2 3
func.call({x: 1}, 4, 5, 6); // {x: 1} 4 5 6
메서드도 마찬가지로 객체의 메서드를 그냥 호출하면 this는 객체를 참조하지만 call 메서드를 이용하면 임의의 객체를 this로 지정할 수 있습니다.
3-2-2 apply 메서드
Function.prototype.apply(thisArg[, argArray])
apply 메서드는 call 메서드와 기능적으로 완전히 동일합니다. call 메서드는 첫 번째 인자를 제외한 나머지 모든 인자들을 호출할 함수의 매개변수로 지정하면 반면, apply 메서드는 두 번째 인자를 배열로 받아 그 배열의 요소들을 호출할 함수의 매개변수로 지정한다는 점에서만 차이가 있습니다.
3-2-4 bind 메서드
Functionprototype.bind(thisArg[, arg1[, arg2[, ...]]])
bind 메서드는 ES5에서 추가된 기능으로, call과 비슷하지만 즉시 호출하지는 않고 넘겨 받은 this 및 인수들을 바탕으로 새로운 함수를 반환하기만 하는 메서드입니다. 다시 새로운 함수를 호출할 때 인수를 넘기면 그 인수들은 기존 bind 메서드를 호출할 때 전달했던 인수들의 뒤에 이어서 등록됩니다. 즉 bind 메서드는 함수에 this를 미리 적용하는 것과 부분 적용 함수를 구현하는 두 가지 목적을 지닙니다.
3-2-5 화살표 함수의 예외사항
ES6에 새롭게 도입된 화살표 함수는 실행 컨텍스트 생성 시 this를 바인딩하는 과정이 제외됐습니다. 즉 이 함수 내부에는 this가 아예 없으면, 접근하고자 하면 스코프체인상 가장 가까운 this에 접근하게 됩니다.
(call) 내부함수에서 this 전달
var obj = {
outer: function () {
console.log(this);
var innerFunc = function () {
console.log(this);
};
innerFunc.call(this);
}
};
obj.outer();
화살표 함수 내부에서의 this
var obj = {
outer: function () {
console.log(this);
var innerFunc = () => {
console.log(this);
};
innerFunc();
}
};
obj outer();
위 함수를 화살표 함수로 변경한 것입니다. 이렇게 하면 별로의 변수로 this를 우회하거나 call/apply/bind를 적용할 필요가 없어 더욱 간결하고 편리합니다.
'코어 자바스크립트 스터디' 카테고리의 다른 글
코어 자바스크립트 2주차 발표 (1) | 2024.07.13 |
---|---|
코어 자바스크립트 4장 콜백 함수 (0) | 2024.07.11 |
코어 자바스크립트 1주자 발표 (0) | 2024.07.05 |
코어 자바스크립트 2강 실행 컨텍스트 (0) | 2024.07.04 |
코어 자바스크립트 1강 데이터 타입 (0) | 2024.07.02 |