03 this
대부분의 객체지향 언어에서 this
는 클래스로 생성한 인스턴스 객체를 의미한다.
그러나 자바스크립트에서 this
는 어디서든 사용할 수 있고 상황에 따라 달라진다.
3.1 상황에 따라 달라지는 this
this
는 실행 컨텍스트가 생설될 때 결정되는데 실행 컨텍스트는 함수를 호출할 때 생성되므로,
this
는 함수를 호출할 때 결정된다고 할 수 있다.
3.1.1 전역 공간에서의 this
전역 공간에서 this
는 전역 객체를 가리킨다. 이 전역 객체는 런타임 환경에 따라 다른데 브라우저는 window
, nodejs는 global
var a = 1;
console.log(a); // 1
console.log(window.a); // 1
console.log(this.a); // 1
자바스크립트의 모든 변수는 실은 특정 객체의 프로퍼티로 동작한다. 특정 객체란 실행 컨텍스트의 LE이다. 그럼 var로 변수를 선언하는 대신 window의 프로퍼티로 직접 할당해도 똑같이 동작할까? 대부분은 그렇다
그런데 삭제 명령은 다르다.
var a = 1;
delete window.a; // false
console.log(a, window.a, this.a) // 1, 1, 1
var b = 2;
delete b; // false
console.log(b, window.b, this.b) // 2 2 2
window.c = 3;
delete window.c; // true
console.log(c, window.c, this.c) // Uncaught ReferenceError: c is not defined
처음부터 전역객체의 프로퍼티로 할당한 경우는 삭제가 되는데 전역변수로 선언한 경우에는 삭제가 되지 않는다. 이는 의도치 않게 삭제되는 것을 막고자 엔진에서 전역 변수를 선언하면 전역 객체의 프로퍼티로 할당하면서 해당 프로퍼티의 속성(변경 및 삭제)을 false로 정의한다.
3.1.2 메서드로서 호출할 때 그 메서드 내부에서의 this
함수 vs 메서드
이 둘을 구분하는 유일한 차이는 독립성이다. 함수는 그 자체로 독립적인 기능을 하고, 메서드는 자신을 호출한 대상 객체에 관한 동작을 수행한다.
var func = function (x) {
console.log(this, x);
}
func(1); // Window , 1
var obj = {
method: func
};
obj.method(2); // { method: f } 2
함수를 변수에 담아 호출한 경우와 객체의 프로퍼티에 할당해서 호출한 경우 this가 달라진다. 그럼 어떻게 구분할까? 함수 앞에 점(.)으로 구분한다. (진짜로) 그 함수 이름 앞에 객체가 명시돼 있는 경우 메서드이고 그렇지 않으면 모두 함수로 호출한거다.
메서드 내부에서의 this
this
는 호출 주체에 대한 정보가 담긴다. 어떤 함수를 메서드로서 호출하면 호출 주체는 함수명 앞의 객체이다.
var obj = {
methodA: function () { console.log(this); },
inner: {
methodB: function () { console.log(this); },
}
};
obj.methodA(); // { methodA: f, inner: { ... } }
obj.inner.methodB(); // { methodB: f }
3.1.3 함수로서 호출할 때 그 함수 내부에서의 this
함수 내부에서의 this
함수로서 호출할 경우 this
가 지정되지 않는다. 지정되지 않은 경우 this
는 전역 객체를 바라본다고 했다. 그래서 함수에서의 this
는 전역 객체를 가리킨다.
더글라스 크락포드는 이는 설계상 오류라고 지적한다.
메서드 내부함수에서의 this
어떤 함수를 메서드로서 호출했는지 함수로서 호출했는지 파악하면 this
의 값을 맞출 수 있다.
var obj1 = {
outer: function() {
console.log(this); // obj1
var innerFunc = function() {
console.log(this);
}
innerFunc(); // global
var obj2 = {
innerMethod: innerFunc
};
obj2.innerMethod(); // obj2
}
}
obj1.outer();
같은 함수임에도 innerFunc()
과 obj2.innerMethod()
로 호출할 때 바인딩 되는 this가 다르다.
this
바인딩에는 함수를 어떻게 호출했는지 (함수인지 메서드인지)만 중요하다.
메서드 내부 함수에서의 this를 우회하는 방법
호출 주체가 없을 때 자동으로 전역 객체를 바인딩하지 않고 호출 당시 주변 환경의 this를 그대로 상속받을 수는 없나? ES5까지는 내부 함수에 this를 상속할 방법이 없고 변수를 활용해 우회할 수 있다.
var obj = {
outer: function() {
console.log(this); // obj1
var innerFunc1 = function() {
console.log(this);
}
innerFunc(); // 전역 객체
var self = this;
var innerFunc2 = function() {
console.log(self);
}
innerFunc2(); // obj1
}
}
obj.outer();
this를 바인딩하지 않는 함수
ES6에서 this
를 바인딩하지 않는 화살표 함수를 도입했다. 화살표 함수는 실행 컨텍스트를 생성할 때 this
바인딩 과정 자체가 빠지고 상위 스코프의 this를 그대로 활용한다.
var obj = {
outer: function() {
console.log(this); // obj
var innerFunc = () => {
console.log(this); // obj
};
innerFunc();
}
};
obj.outer();
3.1.4 콜백 함수 호출 시 그 함수 내부에서의 this
함수 A의 제어권을 다른 함수 B에게 넘겨주는 경우 함수 A를 콜백 함수라고 한다. 함수 A는 함수 B의 내부 로직에 따라 실행되고 this
역시 함수 B 내부로직에서 정한 규칙에 따라 결정된다.
setTimeout(function () { console.log(this); }, 300); // (1)
[1,2,3,4,5].forEach(function (x) {
console.log(this, x); // (2)
})
document.body.innerHTML += '<button id="a">클릭</button>';
document.body.querySelector('#a')
.addEventListener('click', function (e) {
console.log(this, e); // (3)
});
- (1), (2) - 내부에서
this
를 지정하지 않는다. 전역객체 - (3)
addEventListener
메서드는 콜백 함수를 호출할 때 자신의this
를 상속하도록 정의되어 있다. 그래서 점 앞 부분(document.body.querySelector('#a')
)이this
가 된다.
3.1.5 생성자 함수 내부에서의 this
생성자 함수는 어떤 공통된 성질을 지니는 객체들을 생성하는 데 사용하는 함수이다.
자바스크립트는 함수에 생성자로서의 역할을 함께 부여했다. new
와 함께 함수를 호출하면 해당 함수가 생성자로서 동작한다.
그리고 생성자 함수로 호출된 경우 내부에서 this
는 인스턴스 자신이 된다.
생성자 함수를 호출하면 생성자의 prototype
프로퍼티를 참조하는 __proto__
라는 프로퍼티가 있는 객체를 만들고 공통 속성과 개성을 해당 객체(this)에 부여한다.
var Cat = function (name, age) {
this.bark = '야옹';
this.name = name;
this.age = age;
}
var choco = new Cat('초코', 1);
var nabi = new Cat('나비', 5);
3.2 명시적으로 this를 바인딩하는 방법
3.2.1 call 메서드
Function.prototype.call(thisArg[, arg1[, arg2[, ...]]])
call
메서드는 호출 주체인 함수를 즉시 실행하도록 하는 명령이다. 이때 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
3.2.2 apply 메서드
Function.prototype.apply(thisArg[, argsArray])
apply
는 call
과 동일한데 두번째 인자를 배열로 호출할 함수의 매개변수로 지정한다.
3.2.3 call/apply 메서드의 활용
유사배열객체에 배열 메서드를 적용
var obj = {
0: 'a',
1: 'b',
2: 'c',
length: 3
};
Array.prototype.push.call(obj, 'd');
console.log(obj);
var arr = Array.prototype.slice.call(obj);
객체에는 배열 메서드를 직접 적용할 수 없지만, 키가 0또는 양의 정수인 프로퍼티가 존재하고 length 프로퍼티의 값이 0 또는 양의정수인 객체를
call
또는 apply
메서드를 이용해 배열 메서드를 차용할 수 있다.
ES6에서는 유사배열객체 또는 순회 가능한 모든 종류의 데이터 타입을 배열로 전환하는 Array.from
메서드를 새로 도입했다.
생성자 내부에서 다른 생성자를 호출
function Person(name, gender) {
this.name = name;
this.gender = gender;
}
function Student(name, gender, school) {
Person.call(this, name, gender);
this.school = school;
}
function Employee(name, gender, company) {
Person.apply(this, [name, gender]);
this.company = company;
}
var by = new Student('보영', 'female', '단국대');
var jn = new Employee('재난', 'male', '구골');
3.2.4 bind 메서드
Function.prototype.bind(thisArg[, arg1[, arg2[, ...]]])
bind
메서드는 ES5에서 추가된 기능, call
과 비슷하지만 즉시 호출하지는 않고 this
와 인수들을 바탕으로 새로운 함수를 반환한다.
name 프로퍼티
bind
메서드로 새로 만든 함수는 name
프로퍼티에 bound
접두어가 붙는다.
상위 컨텍스트의 this를 내부함수나 콜백 함수에 전달하기
var obj = {
outer: function () {
console.log(this);
var innerFunc = function () {
console.oog(this);
};
innerFunc.call(this);
}
};
obj.outer();
var obj1 = {
outer: function() {
console.log(this);
var innerFunc = function() {
console.log(this);
}.bind(this);
innerFunc();
}
}
obj1.outer();
3.2.5 화살표 함수의 예외사항
ES6에 도입된 화살표 함수는 실행 컨텍스트 생성 시 this
를 바인딩하는 과정이 제외되었다. 이 함수 내부에는 this
가 없고, 접근 시 스코프 체인 상 가장 가까운 this
에 접근한다.
var obj = {
outer: function() {
console.log(this); // obj
var innerFunc = () => {
console.log(this); // obj
};
innerFunc();
}
}
obj.outer();
3.2.6 별도의 인자로 this를 받는 경우 (콜백 함수 내에서의 this)
var report = {
sum: 0,
count: 0,
add: function() {
var args = Array.prototype.slice.call(arguments); // args = [60, 85, 95]
args.forEach(function (entry) {
this.sum += entry;
++this.count;
}, this);
},
average: function() {
return this.sum / this.count;
}
};
report.add(60, 85, 95);
console.log(report.sum, report.count, report.average());
3.3 정리
- 전역공간에서의
this
는 전역객체를 참조한다. - 어떤 함수를 메서드로서 호출한 경우
this
는 메서드 호출 주체를 참조한다. - 어떤 함수를 함수로서 호출한 경우
this
는 전역객체를 참조한다. 메서드의 내부함수도 동일하다. - 콜백 함수 내부에서의
this
는 해당 콜백 함수의 제어권을 넘겨받은 함수가 정의한 바에 따르며, 정의하지 않은 경우 전역객체 - 생성자 함수에서의
this
는 생설될 인스턴스를 참조한다.
명시적인 this
바인딩
call
,apply
메서드는this
를 명시적으로 지정하면서 함수 또는 메서드를 호출한다.bind
메서드는this
및 함수에 넘길 인수를 일부 지정해서 새로운 함수를 만든다.- 요소를 순회하면서 콜백 함수를 반복 호출하는 일부 메서드는 별도 인자로
this
를 받기도 한다.