JavaScript

this 바인딩 - call, apply, bind

차돌박이츄베릅 2023. 5. 25. 08:19

실행 컨텍스트 : 실행할 코드에 제공할 환경 정보들을 모아놓은 객체
실행 컨텍스트의 구성요소 : VariableEnvironment, LexicalEnvironment, ThisBindings

 

this는 실행 컨텍스트가 생성될 때 결정돼서 즉, this를 bind한다(=묶는다) 라고 표현. 그렇기 때문에 this는 함수를 호출할 때 결정된다. 

 

전역 공간에서의 this

전역 공간에서 this는 전역 객체(런타임 환경에 따라 브라우저 환경에서는 window, node 환경에서는 global)를 가리킴

 

함수, 메서드로서 호출할 때 내부에서의 this

  1. 어떤 함수를 함수로서 호출할 경우, this는 지정되지 않는다(호출 주체가 알 수 없음)
  2. 실행컨텍스트를 활성화할 당시 this가 지정되지 않은 경우, this는 전역 객체를 의미
  3. 따라서, 함수로서 ‘독립적으로’ 호출할 때는 `this는 항상 전역객체를 가리킴`
  4. 메서드의 내부라고 해도, 함수로서 호출한다면 this는 전역 객체를 의미
// CASE1 : 함수
// 호출 주체를 명시할 수 없기 때문에 this는 전역 객체를 의미해요.
var func = function (x) {
    console.log(this, x);
};
func(1); // Window { ... } 1

// CASE2 : 메서드
// 호출 주체를 명시할 수 있기 때문에 this는 해당 객체(obj)를 의미해요.
// obj는 곧 { method: f }를 의미하죠?
var obj = {
    method: func,
};
obj.method(2); // { method: f } 2

 

콜백 함수 호출 시 그 함수 내부에서의 this

콜백 함수도 함수기 때문에 this는 전역 객체를 참조(호출 주체가 없기 때문)

콜백함수를 넘겨받은 함수에서 콜백 함수에 별도로 this를 지정한 경우는 예외적으로 그 대상을 참조할 수 있음

addListener 안에서의 this는 항상 호출한 주체의 element를 return함

// 별도 지정 없음 : 전역객체
setTimeout(function () { console.log(this) }, 300);

// 별도 지정 없음 : 전역객체
[1, 2, 3, 4, 5].forEach(function(x) {
    console.log(this, x);
});

// addEventListner 메서드는 콜백 함수 호출 시, 자신의 this를 상속
// 따라서 this는 button을 의미함
document.body.innerHTML += '<button id="a">클릭</button>';
document.body.querySelector('#a').addEventListener('click', function(e) {
    console.log(this, e);
});

 

생성자 함수 내부에서의 this

생성자 : 구체적인 인스턴스(어려우면 객체로 이해!)를 만들기 위한 일종의 틀로써, 공통 속성을 넣어두고 씀

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 바인딩

1. call 메서드

호출 주체인 함수를 즉시 실행함. call을 이용해서 즉시실행하면서 this를 넘겨줌

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


var obj = {
    a: 1,
    method: function (x, y) {
        console.log(this.a, x, y);
    }
};

obj.method(2, 3); // 1 2 3

// 예상되는 this가 있음에도 일부러 바꿀 수 있음
obj.method.call({ a: 4 }, 5, 6); // 4 5 6

 

2. apply 메서드

apply를 이용해서 즉시실행하면서 this를 넘겨줌

call과 동일하나 나머지 부분을 배열 형태로 넘겨줌

var func = function (a, b, c) {
    console.log(this, a, b, c);
};
func.apply({ x: 1 }, [4, 5, 6]); // { x: 1 } 4 5 6

var obj = {
    a: 1,
    method: function (x, y) {
        console.log(this.a, x, y);
    }
};

obj.method.apply({ a: 4 }, [5, 6]); // 4 5 6

 

3. call / apply 메서드 활용

유사배열객체(array-like-object)에 배열 메서드를 적용할 수 있다

//객체에는 배열 메서드를 직접 적용할 수 없어요.
//유사배열객체에는 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); // call을 이용해서 즉시실행하면서 this를 넘겨주었습니다
console.log(arr); // [ 'a', 'b', 'c', 'd' ]
// call/apply를 통해 this binding을 하는 것이 아니라 객체 → 배열로의 형 변환 만을 위해서도 쓸 수 있지만 원래 의도와는 거리가 먼 방법


// Array.from 메서드(ES6)
// 객체 -> 배열
var arr = Array.from(obj);

// 찍어보면 배열이 출력됩니다.
console.log(arr);

 

생성자 내부에서 다른 생성자를 호출

function Person(name, gender) {
    this.name = name;
    this.gender = gender;
}
function Student(name, gender, school) {
    Person.call(this, name, gender); // 여기서 this는 student 인스턴스!
    this.school = school;
}
function Employee(name, gender, company) {
    Person.apply(this, [name, gender]); // 여기서 this는 employee 인스턴스!
    this.company = company;
}
var kd = new Student('길동', 'male', '서울대');
var ks = new Employee('길순', 'female', '삼성');

 

4. bind 메서드

  • 함수에 this를 미리 적용해요!
  • 부분 적용 함수 구현할 때 용이합니다.
  • function에 this를 결합한 새로운 함수를 할당
var func = function (a, b, c, d) {
    console.log(this, a, b, c, d);
};
func(1, 2, 3, 4); // window객체

// 함수에 this 미리 적용
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); // 4와 5를 미리 적용
bindFunc2(6, 7); // { x: 1 } 4 5 6 7
bindFunc2(8, 9); // { x: 1 } 4 5 8 9

// name 프로퍼티
// bind 메서드를 적용해서 새로 만든 함수는 name 프로퍼티에 ‘bound’ 라는 접두어가 붙습니다(추적하기가 쉽죠!)
console.log(func.name); // func
console.log(bindFunc1.name); // bound func
console.log(bindFunc2.name); // bound func

 

콜백함수에 bind 활용

var obj = {
    logThis: function () {
        console.log(this);
    },
    logThisLater1: function () {
        // 0.5초를 기다렸다가 출력해요. 정상동작하지 않아요.
        // 콜백함수도 함수이기 때문에 this를 bind해주지 않아서 잃어버렸어요!(유실)
        setTimeout(this.logThis, 500); // obj.logThis 호출하면 setTimeout안에 function~이 들어오는데 콜백함수이므로 그 안의 this는 window
        // 위 setTimeout의 callback은 obj를 this로 하는 메서드를 그대로 전달한게 아니에요
        // 단지, obj.logThis가 가리키는 함수만 전달한거에요(obj 객체와는 연관이 없습니다)
    },
    logThisLater2: function () {
        // 1초를 기다렸다가 출력해요. 정상동작해요.
        // 콜백함수에 this를 bind 해주었기 때문이죠.
        setTimeout(this.logThis.bind(this), 1000); //  // obj.logThis 호출하면 setTimeout안에 function~이 들어오는데 콜백함수이므로 그 안의 this는 window여야하나 .bind(obj)로 this를 obj로 바인딩해주었기 떄문에 obj를 console에 찍어줌
    }
};

obj.logThisLater1();
obj.logThisLater2();

 

5. 화살표 함수

  • 화살표 함수는 실행 컨텍스트 생성 시, this를 바인딩하는 과정이 제외됨
  • 화살표 함수 내부에는 this의 할당과정(바인딩 과정)이 아에 없으며, 접근코자 하면 스코프체인상 가장 가까운 this에 접근하게 됨
  • this우회, call, apply, bind보다 편리한 방법
var obj = {
    outer: function () {
        console.log(this); // obj
        var innerFunc = () => {
            console.log(this); // obj
        };
        innerFunc(); // 함수로서 독립적으로 호출해서 this==window를 받아야하나 화살표함수라 상위 obj를 받아옴?
    }
};
obj.outer();

 

'JavaScript' 카테고리의 다른 글

비동기 작업의 동기적 표현 - Promise, Generator, Async/await  (1) 2023.05.28
콜백 함수  (0) 2023.05.28
실행컨텍스트(스코프, 변수, 객체, 호이스팅)  (2) 2023.05.25
깊은 복사  (0) 2023.05.25
Map, Set  (0) 2023.05.24