•
다른 언어와 다르게 JavaScript에서는 this는 어디서나 사용이 가능하다. (혼란의 원인)
상황에 따라 달라지는 this
•
자바스크립트의 this는 기본적으로 실행 컨텍스트가 생성될 때 함께 결정
◦
즉, 함수를 호출할 때 결정
1. 전역 공간에서의 this
•
전역 객체를 가리킨다.
◦
브라우저 → window
◦
Node.js → global
•
전역에서 변수를 선언하면 자바스크립트 엔진은 이를 전역객체의 프로퍼티로 할당
var a = 1;
console.log(a); // 1
console.log(window.a); // 1
console.log(this.a); // 1
JavaScript
복사
•
자바스크립트의 모든 변수는 실은 특정 객체의 프로퍼티로서 동작
•
특정 객체란 실행 컨텍스트의 Lexical Environment를 의미
•
실행 컨텍스트는 변수를 수집하여 Lexical Environment의 프로퍼티로 저장
var a = 1;
window.b = 2;
console.log(a, window.a, this.a); // 1 1 1
console.log(b, window.b, this.b); // 2 2 2
JavaScript
복사
•
But, 삭제는 다르다.
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); // c is not defined
window.d = 4;
delete d; // true
console.log(d, window.d, this.d); // d is not defined
JavaScript
복사
→ 전역객체의 프로퍼티로 할당한 경우에는 삭제가 된다.
→ 전역변수로 선언한 경우에는 삭제가 되지 않는다.
2. 메서드로서 호출할 때 그 메서드 내부에서의 this
•
함수 vs 메서드
1.
함수 → 그 자체로 독립적인 기능을 수행
2.
메서드 → 자신을 호출한 객체에 관한 동작을 수행
a.
메서드는 객체의 메서드로서 호출할 경우에만 메서드로 동작, 아니면 함수로 동작
var func = function (x) {
console.log(this, x);
};
func(1); // window { ... } 1
var obj = {
method: func
};
obj.method(2); // { method: f } 2
JavaScript
복사
•
점 표기법, 대괄호 표기법으로 호출한 함수는 메서드로 동작한다.
메서드 내부에서의 this에는 호출한 주체에 대한 정보가 담긴다.
var obj = {
methodA: function () { console.log(this); },
inner: {
methodB: function () { console.log(this); },
}
};
obj.methodA(); // obj
obj['methodA'](); // obj
obj.inner.methodB(); // obj.inner
obj.inner['methodB'](); // obj.inner
JavaScript
복사
3. 함수로서 호출할 때 그 함수 내부에서의 this
•
함수 내부에서의 this → 전역 객체를 가리킨다.
•
메서드의 내부함수에서의 this → 메서드를 호출한 객체를 가리킨다.
var obj1 = {
outer: function () {
console.log(this); // obj1
var innerFunc = function () {
console.log(this);
}
innerFunc(); // window
var obj2 = {
innerMethod: innerFunc
};
obj2.innerMethod(); // obj2
}
};
obj1.outer();
JavaScript
복사
→ 함수 실행 당시의 주변 환경은 중요하지 않고, 오직 해당 함수를 호출하는 구문 앞에 점, 대괄호 표기가 있는지가 관건
•
메서드 내부 함수에서의 this를 우회하는 방법
◦
외부 환경에서 this를 변수에 저장하고 이를 함수 내부에서 호출한다.
•
this를 바인딩하지 않는 함수 → 화살표 함수
•
화살표 함수는 상위 스코프의 this를 활용할 수 있다.
var obj = {
outer: function () {
console.log(this); // obj
var innerFunc = () => {
console.log(this);
};
innerFunc(); // obj
}
};
obj.outer();
JavaScript
복사
4. 콜백 함수 호출 시 그 함수 내부에서의 this
•
함수 A의 제어권을 다른 함수 B에 넘겨주는 경우 함수 A를 콜백 함수라고 한다.
•
addEventListener 메서드는 콜백 함수를 호출할 때 자신의 this를 상속하도록 정의
5. 생성자 함수 내부에서의 this
•
생성자 함수는 어떤 공통된 성질을 지니는 객체들을 생성하는 데 사용하는 함수
•
생성자 함수로서 호출된 경우는 내부에서의 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);
JavaScript
복사
명시적으로 this를 바인딩하는 방법
•
상황별로 this에 다르게 값이 바인딩되는 것을 의도적으로 바꾸는 방법!
1. call 메서드
•
메서드의 호출 주체인 함수를 즉시 실행하도록 하는 명령
•
call 메서드의 첫 번째 인자를 this로 바인딩
•
이후의 인자들은 호출할 함수의 매개변수
•
call 메서드를 이용하면 임의의 객체를 this로 지정이 가능하다.
Function.prototype.call(thisArg[, arg1[, arg2[, ...]]])
JavaScript
복사
// 일반 함수의 경우 원래 this는 전역, call을 사용하면 첫 번째 인자
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을 사용하면 첫 번째 인자
var obj = {
a: 1,
method: function (x, y) {
console.log(this.a, x, y);
}
};
obj.method(2, 3); // 1 2 3
obj.method.call({ a: 4 }, 5, 6); // 4 5 6
JavaScript
복사
2. apply 메서드
•
call 메서드와 기능적으로 완전 동일
•
call 메서드는 나머지 모든 인자들을 호출할 함수의 매개변수로 지정
•
apply 메서드는 두 번째 인자를 배열로 받아 그 배열의 요소들을 호출할 함수의 매개변수로 지정
var func = function (a, b, c) {
console.log(this, a, b, c);
};
func.apply({ x : 1 }, [4, 5, 6]); // { x : 1 } 4 5 6
JavaScript
복사
3. call / apply 메서드 활용
•
유사배열객체에 배열 메서드 적용 (Array.from 으로도 가능)
•
유사배열객체 - 키가 0 또는 양의 정수인 프로퍼티 존재 (배열의 성질을 다 가지고 있지만 객체)
•
length 프로퍼티의 값이 0 또는 양의 정수
var obj = {
0: 'a',
1: 'b',
2: 'c',
length: 3
};
Array.prototype.push.call(obj, 'd');
console.log(obj); // { 0: 'a', 1: 'b', 2: 'c', 3: 'd', length: 4 }
var arr = Array.prototype.slice.call(obj);
console.log(arr); // ['a', 'b', 'c', 'd']
// slice 메서드에 매개변수를 넘기지 않으면 얕은 복사
JavaScript
복사
•
함수 내부의 arguments 객체와 NodeList에도 활용 가능
// arguments 객체에 배열 메서드 사용
function a () {
var argv = Array.prototype.slice.call(arguments);
argv.forEach(function (arg) {
console.log(arg);
});
}
a(1, 2, 3);
document.body.innerHTML = '<div>a</div><div>b</div><div>c</div>';
var nodeList = document.querySelectorAll('div');
var nodeArr = Array.prototype.slice.call(nodeList);
// nodeList에 배열 메서드 사용
nodeArr.forEach(function (node) {
console.log(node);
});
JavaScript
복사
•
단, 문자열의 경우 length 프로퍼티가 읽기 전용이므로 사용이 불가능하다.
•
유사 배열 객체에 배열 메서드를 활용하기 위해 es6에서는 Array.from이 도입
•
생성자 내부에서 다른 생성자를 호출
•
여러 인수를 묶어 하나의 배열로 전달하고 싶을 때 apply 활용
→ apply 메서드는 두 번째 인자로 전달한 배열을 펼치는 역할을 한다.
→ es6에서는 스프레드 연산자를 통해 간편하게 작성 가능
bind 메서드
•
call과 비슷하지만 즉시 호출하지는 않고 넘겨받은 this 및 인수들을 바탕으로 새로운 함수를 반환하기만 하는 메서드
•
bind 메서드를 통해 영원히 커스텀 this를 가지는 함수를 만들 수 있다. → 새로운 함수를 반환하기 때문
var func = function (a, b, c, d) {
console.log(this, a, b, c, d);
};
func(1, 2, 3, 4); // window 1 2 3 4
var bindFunc1 = func.bind({ x: 1 });
bindFunc1(5, 6, 7, 8); // { x : 1 } 5 6 7 8
var bindFunc2 = func.bind({ x: 1 }, 4, 5);
bindFunc2(6, 7); // {x : 1 } 4 5 6 7
JavaScript
복사
•
name 프로퍼티 → bind 메서드를 적용해서 새로 만든 함수는 name 프로퍼티에 bound 접두어가 붙는다.
•
상위 컨텍스트의 this를 내부함수나 콜백 함수에 전달할 수 있다.
1.
call 메서드
var obj = {
outer: function () {
console.log(this);
var innerFunc = function () {
console.log(this);
};
innerFunc.call(this);
}
};
obj.outer();
JavaScript
복사
2.
bind 메서드
var obj = {
outer: function () {
console.log(this);
var innerFunc = function () {
console.log(this);
}.bind(this);
// 아예 커스텀 this를 지니는 새로운 함수를 반환했다.
innerFunc();
}
};
obj.outer();
JavaScript
복사
화살표 함수의 예외사항
•
화살표 함수 내부에는 this가 아예 없으며, 접근하고자 하면 스코프체인상 가장 가까운 this에 접근한다.
•
call / apply / bind 메서드 사용할 필요 없어짐 (편리)
var obj = {
outer: function () {
console.log(this);
var innerFunc = () => {
console.log(this); // outer
};
innerFunc();
}
};
obj.outer();
JavaScript
복사
별도의 인자로 this를 받는 경우 (콜백 함수 내에서의 this)
var report = {
sum: 0,
count: 0,
add: function () {
var args = Array.prototype.slice.call(arguments);
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()); // 240 3 80
JavaScript
복사