이번 항목에서는 'This' 의 정의, 활용 방법, 바인딩 Call/Apply/Bind 에 대해서 학습한다.
먼저 이 This 란 무엇일까.
코드에서의 This 는 어떠한 객체를 가리킬 때 사용한다.
C/C++ 에서 This 는 자기 자신, 클래스 등 스스로를 가리킬 때 사용되었지만 JS 에서는 그 활용이 조금 다르다.
이 This 는 항상 런타임에 결정된다. 런타임이랑 코드가 돌아가는 환경으로, JS에는 1. 노드, 2. 브라우저 의 두가지 환경이 있다.
먼저 브라우저에서의 this 를 찾아보면
다음과 같이 Window 를 가리키는 것을 알 수 있다.
저 console.log(this) 는 특정한 함수 내부나 함수 표현식으로 구현된 코드가 아니니 전역 컨텍스트 항목이라고 볼 수 있고 그 말은 전역환경에서의 this 는 코드에서는 (global) 객체, 브라우저에서는 (Window) 객체를 나타낸다는 것을 뜻한다.
코드에서의 this는 또 다시 두가지 경우로 나눌 수 있다.
함수와 메서드 두 경우로 나눠야 하는데, 이 둘은 비슷해보이지만 '독립성' 개념에서 차이가 있다.
1. 함수 : 스스로 실행 가능 - 독립적, this = 전역 객체가 된다.
2. 메서드 : 객체 . 메서드명() 과 같이 이 메서드를 호출할 객체가 있어야 한다 - 의존적, this = 호출 객체가 된다.
코드로 살펴보면 다음과 같다.
// CASE1 : 함수
var func = function (x) {
console.log(this, x);
};
func(1); // Window { ... } 1 <- this : 전역객체
// CASE2 : 메서드
var obj = {
method: func,
};
obj.method(2); // { method: ƒ } 2 <- this : obj
JS에서의 this 는 이렇게 호출을 '누가' 했는지에 대한 정보가 담긴다.
함수는 누군가 호출하는 것이 아니라 스스로 시행되는 것이므로 전역 객체이며 메서드 내부에서 시행되는 함수의 this 또한 마찬가지로 전역객체이다.
함수로서의 this 호출은 이 함수가 어디에서 시행되냐에 상관없이 무조건 전역객체라고 보면 될 것이다.
코드를 하나 더 들여다보자.
var obj1 = {
outer: function()
{
console.log("TEST -> ", this);
var innerFunc = function()
{
console.log("TEST -> ", this);
};
innerFunc();
var obj2 =
{
innerMethod : innerFunc
};
obj2.innerMethod();
}
};
obj1.outer();
실행 결과는 다음과 같이 나온다.
첫번째 this 는 obj1.outer() 로 obj1 의 내부 메서드로서 호출된다. 해서 이 this는 outer 를 가리킨다.
두번째 this 는 innerFunc() 로 함수가 자체적으로 호출한다. 해서 이 this 는 전역객체를 가리킨다.
이러한 this 의 구조가 이해는 가지만, 개발자 입장에서는 메서드 호출 방식이 아니다 라는 이유만으로 무조건 전역객체를 가리킨다는 것이 납득하기 어려울 것이다.
해서 JS에서는 이 this 지칭을 우회하는 방법을 알려준다.
내부 스코프에 이미 존재하는 this 를 별도의 변수에 할당하여 이 변수를 활용하는 방법이다.
var obj1 = {
outer: function() {
console.log(this); // (1) { outer: ƒ }
// AS-IS
var innerFunc1 = function() {
console.log(this); // (2) 전역객체
}
innerFunc1();
// TO-BE
var self = this;
var innerFunc2 = function() {
console.log(self); // (3) { outer: ƒ }
};
innerFunc2();
}
};
// 메서드 호출 부분
obj1.outer();
코드를 보면 this 를 self 변수에 할당하고, 이후 출력을 this 가 아니라 self 로 하고 있다.
이 self 는 객체를 가키리는 것을 확인할 수 있다.
앞서 언급했던 ES6 업데이트에서 이 this가 전역객체를 가리키는 문제 때문에 화살표 함수를 도입했다고 봐도 무방하다고 한다.
일반 함수와 화살표 함수의 차이가 무엇이길래? 라고 한다면 'This Binding' 여부 라고 답할 수 있다.
일반 함수는 바인딩 과정을 거친다면, 화살표 함수는 이를 진행하지 않는다.
var obj = {
outer: function() {
console.log(this); // (1) obj
var innerFunc = () => {
console.log(this); // (2) obj
};
innerFunc();
}
}
obj.outer();
(1) 에서의 this 는 obj 가 맞다.
(2) 에서의 this 는 화살표 함수를 사용해, this 바인딩 과정을 거치지 않아서 이전의 this 객체를 그대로 유지한다
해서 이전 (1) 에서 가리켰던 객체, obj 를 그대로 가리킨다.
콜백 함수도 결국에 함수다. 따라서 콜백 함수에서의 this 호출 또한 전역 객체를 가리킨다.
다만 예외가 존재하는데, 콜백 함수에 별도로 this 를 지정한 경우가 있다.
// 별도 지정 없음 : 전역객체
setTimeout(function () { console.log(this) }, 300);
// 별도 지정 없음 : 전역객체
[1, 2, 3, 4, 5].forEach(function(x) {
console.log(this, x);
});
// addListener 안에서의 this는 항상 호출한 주체의 element를 return하도록 설계되었음
// 따라서 this는 button을 의미함
document.body.innerHTML += '<button id="a">클릭</button>';
document.body.querySelector('#a').addEventListener('click', function(e) {
console.log(this, e);
});
여기서 addEventListener() 함수가 이 지정의 역할을 한다. 이 함수를 호출한 주체를 this 에 할당하며, 이 코드에서는 Html 코드에 따라 <button> 을 가리킨다.
이 경우에는 이 인스턴스, 그 객체를 가리킨다.
var Cat = function (name, age) {
this.bark = '야옹';
this.name = name;
this.age = age;
};
var choco = new Cat('초코', 7); //this : choco
var nabi = new Cat('나비', 5); //this : nabi
이 this 바인딩 과정에서 직접 this 가 가리켜야 할 객체를 알리는, 명시적 this 바인딩이 있다.
그 함수에 Call, Apply, Bind 가 있는데 하나씩 살펴보자.
var func = function (a, b, c) {
console.log(this, a, b, c);
};
// no binding
func(1, 2, 3); // Window{ ... } 1 2 3
// 명시적 binding
// func 안에 this에는 {x: 1}이 binding
func.call({ x: 1 }, 4, 5, 6}; // { x: 1 } 4 5 6
func(1,2,3) 의 경우, this에 따로 바인딩을 하지 않아 전역 객체 window { ~ } 를 가리킨다.
이후 func.call() 의 경우, bind 하고 싶은 객체를 call 함수의 매개변수 중 첫번째, { x : 1 } 이라고 명시적으로 알렸다.
따라서 이때의 this 는 { x : 1 } 이 된다.
호출 주체가 있는 경우에도 바인딩이 가능하다.
var obj = {
a : 1,
method : function(x, y)
{
console.log(this.a, x, y);
}
};
obj.method(2, 3);
이 method 함수는 obj 의 메소드로 사용되어 이때의 this 는 항상 obj 를 가리킨다
따라서 출력은 1, 2, 3 으로 찍히는데, 여기서도 call 함수 사용을 통해 명시적 바인딩이 가능하다.
obj.method.call({a : 4}, 5, 6);
//출력 : 4, 5, 6
method 함수에 call 함수를 더 사용하여 this 를 { a : 4 } 라고 명시적으로 알렸다.
따라서 출력은 4, 5, 6 이 찍히게 된다.
이 call 과 같은 기능을 하는 함수로 두번째 Apply 가 있다.
모든 동작은 전부 call 함수와 동일하다.
차이는 this 로 할당되는 매개변수 뒤의 값들을 [ ] 대괄호로 묶어주기만 하면 된다.
obj.method.apply({a : 4}, [5, 6]);
이러한 this binding 을 사용하면 더 유용한 것들이 많이 있는데, 가장 먼저 '유사배열객체' 에서의 경우를 말할 수 있다.
말 그대로 배열과 유사한 객체를 말한다.
배열의 특성이라고 하면 요소들의 순번인 Index, 요소들의 총 길이인 Length 를 가지는데, 이 유사배열객체 또한 이 값들을 필요로 한다.
//객체에는 배열 메서드를 직접 적용할 수 없어요.
//유사배열객체에는 call 또는 apply 메서드를 이용해 배열 메서드를 차용할 수 있어요.
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' ]
기존 객체처럼 단순 push 나 slice 같은 메서드를 그냥 사용할 수는 없고, 예시 코드처럼 그 뒤에 call 이나 apply 메서드를 한번 더 붙이는 방식으로 사용한다.
이러한 call / apply 함수들을 '즉시 실행 함수' 라고 부르며 이 때문에 push 와 같은 객체 메서드들을 활용할 수 있게 해준다.
아니면 이 유사배열객체를 진짜 배열로 바꿀 수도 있다.
// 유사배열
var obj = {
0: 'a',
1: 'b',
2: 'c',
length: 3
};
// 객체 -> 배열
var arr = Array.from(obj);
// 찍어보면 배열이 출력됩니다.
console.log(arr);
간단하게, this 를 바인딩해주는 메서드이다.
call, apply 와는 다르게 즉시 호출 함수는 아니라 새 함수에 넣어서 return 하는 형식으로 사용한다.
이 메서드는 함수에 this 를 미리 적용하거나, 함수의 부분만을 적용하고자 함에 따라 사용한다.
1. This 미리 적용
var func = function (a, b, c, d)
{
console.log(this, a,b,c,d);
};
//함수에 this 를 미리 적용
var bindFunc1 = func.bind({x:1});
bindFunc1(5,6,7,8);
//this = { x : 1 }
2. 부분 적용
// this 뿐만 아니라 인자 중 일부에 4, 5를 미리 할당
var bindFunc2 = func.bind({x:1}, 4, 5);
bindFunc(6,7);
//출력 : {x:1}, 4,5,6,7
#11. 콜백 함수 (Call Back Func) (6) | 2024.11.12 |
---|---|
#10. 3주차 숙제 (1) | 2024.11.08 |
#8. 실행 컨텍스트 (3) | 2024.11.07 |
#7. 데이터 타입 심화 (6) | 2024.11.06 |
#6. 숙제 - 문자열 임의대로 정렬 (0) | 2024.11.05 |