[JavaScript] this (정의, 활용, 바인딩, call, apply, bind)

this가 가리키는 것

JS의 this는 런타임 상황에서 고려해야한다. 따라서 두가지 상황을 고려할 수 있다. node, 브라우저!

 

1) 브라우저: window 객체

2) 노드: global 객체

 

 

메서드 내부에서의 this

메서드와 함수

함수는 그 자체로서 독립적인 기능을 수행하지만, 메서드는 자신을 호출한 대상 객체에 대한 동작을 수행한다.

1) 함수의 this: 전역 객체

2) 메서드의 this: 호출의 주체

 

this의 할당

function exFunc() {
    console.log("this: ", this);
}

exFunc();  // this:  <ref *1> Object [global]

const obj = {
    method: exFunc,
}

obj.method();  // this:  { method: [Function: exFunc] } -> exFunc가 포함된 객체 obj

같은 exFunc()를 사용하더라도 독립적으로 호출했을 때, obj의 메서드로서 호출했을 때 this가 의미하는 바가 다르다.

 

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

메서드 내부에서 함수를 호출하더라도, 그 함수는 독립적으로 호출된 것이기 때문에 this는 전역객체(global, window)다.

 

const obj1 = {
    outer: function() {
        console.log(this);  // obj1
        const innerFunc = function() {
            console.log(this);
        }

        innerFunc();  // global (주체없이 독립적으로 호출됐기 때문에)

        const obj2 = {
            innerMethod: innerFunc,
        };

        obj2.innerMethod();  // obj2
    }
};

obj1.outer();

 

 

메서드 내부 함수에서의 this 우회

변수를 활용하는 방법 (잘 사용하지 않음)

내부스코프에 이미 존재하는 this를 별도의 변수에 할당하는 방법

const obj1 = {
    outer: function() {
        const self = this;
        const innerFunc = function() {
            console.log(self);  // obj1
        }
        innerFunc();
    }
};

obj1.outer();

 

화살표 함수 (this를 바인딩하지 않음)

const obj1 = {
    outer: function() {
        const innerFunc = () => {
            console.log(this);  // obj1
        }
        innerFunc();
    }
};

obj1.outer();

 

 

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

콜백함수도 함수다. 따라서 this는 전역객체를 참조하지만, 콜백함수를 넘겨받은 함수에서 별도로 this를 지정하는 경우는 예외적으로 그 대상을 참조한다.

ex. addEventListener → 콜백함수에 이벤트가 발생한 element가 this로 지정됨

 

 

생성자함수 내부에서의 this

생성자

구체적인 인스턴스를 만들기 위한 일종의 틀

 

const Cat = function (name, age) {
    this.bark = '야옹';
    this.name = name;
    this.age = age;
};

const choco = new Cat('초코', 7);  // this: choco
const nabi = new Cat('나비', 5);  // this: nabi

생성자 함수 내부에서의 this는 생성하는 인스턴스를 의미한다!

 

 

명시적 this binding

this === global 인 경우

const func = function (a, b, c) {
    console.log(this, a, b, c);
};

func(1, 2, 3);  // global

바인딩하지 않은 경우 this === global인 func

 

const func = function (a, b, c) {
    console.log(this, a, b, c);
};

func.call({ x: 1 }, 1, 2, 3);  // { x: 1 }

const obj = { name: 'obj' }

func.call(obj, 1, 2, 3);  // obj

.call을 사용해 this로 지정하고싶은 객체를 첫번째 인자로 넘겨준다!

 

호출 주체가 있는 경우

const obj = {
    a: 1,
    method: function () {
        console.log("this: ", this);
    },
};

obj.method();  // obj
const obj = {
    a: 1,
    method: function () {
        console.log("this: ", this);
    },
};

const otherObj = { }

obj.method.call(otherObj);  // otherObj

 

 

apply

.call()과 동일한 역할! 뒤의 매개변수들만 배열로 묶어서 보내주면 된다.

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

const otherObj = { }

obj.method.apply(otherObj, [1, 3]);  // otherObj 1 3

 

 

call, apply 활용해 유사배열객체 활용하기

유사 배열 객체

const obj = {
    0: 'a',
    1: 'b',
    2: 'c',
    length: 3,
};

위와 같이 실제 배열은 아니지만 인덱스를 갖고있고, 길이(length) 속성을 갖고있는 객체

유사배열객체는 형태만 유사할뿐 당연히 배열의 메서드들을 사용할 수 없다. 하지만 call, apply를 활용하면 배열 메서드를 차용할 수 있다!

 

Array.prototype.push.call(obj, 'd');
console.log(obj);  // { 0: 'a', 1: 'b', 2: 'c', 3: 'd', length: 4 }

 

Array.from()

사실 위의 방법은 바인딩의 목적에 들어맞지는 않는다. 따라서 ES6에서 위 동작을 수행할 수 있는 메서드가 제안됐다!

const arr = Array.from(obj);
console.log(arr);  // ['a', 'b', 'c', 'd']

 

 

Call, apply 활용

1) 공통된 코드 효율적으로 사용하기

function Student(name, age, school) {
    this.name = name;
    this.age = age;
    this.school = school;
}

function Employee(name, age, company) {
    this.name = name;
    this.age = age;
    this.company = company;
}

이와 같이 사람이라는 큰 맥락에서 세부적으로 학생, 직장인으로 나뉠 수 있고 구체적인 내용까지 유사한 생성자가 있다.

 

function Person(name, age) {
    this.name = name;
    this.age = age;
}

function Student(name, age, school) {
    Person.call(this, name, age);
    this.school = school;
}

function Employee(name, age, company) {
    Person.call(this, name, age);
    this.company = company;
}

let st = new Student('Kim', 10, 'ㅇㅇ초등학교');
let em = new Employee('Lee', 30, 'ㅇㅇ기업');

call을 사용해 생성자 내부에서 this를 바인딩해준다. 생성자에서의 this는 새로 만들어지는 인스턴스이므로 st, em의 이름과 나이를 설정할 수 있다.

 

2) 배열에서 최소, 최대값 구하기

const numbers = [10, 2, 8, 16, 3];

const max = Math.max.apply(null, numbers);
const min = Math.min.apply(null, numbers);

// ---- 추가 (spread operator)
// Math.max와 Math.min은 매개변수로 들어온 수들의 최대/최소를 구하는 것이므로 배열 형태를 벗어나 값들만 남도록
// spread operator를 사용할 수도 있다.
const spreadMax = Math.max(...numbers);
const spreadMin = Math.min(...numbers);

 

 

bind()

this를 바인딩하는 다른 메서드. call, apply와의 차이는 즉시 호출하지 않는다는 점이다.

 

활용 목적

1) 함수에 this 를 미리 적용

const func = function () {
    console.log(this);
};

const bind1 = func.bind({x : 1});

// 생략

bind1();  // { x : 1 }

this를 미리 적용해두고 사용할 수 있다!

 

 

2) 부분 적용 함수

const func = function (a, b, c, d) {
    console.log(this, a, b, c, d);
};

const bind2 = func.bind({x : 1}, 10, 11);

// 생략

bind1(5, 6);  // { x : 1 } 10 11 5 6

매개변수의 일부만 지정해줄 때 사용할 수 있다.

 

name property

this binding한 함수는 bound라는 name property 값을 갖고있다.

 

 

상위 컨텍스트의 this를 내부함수나 콜백함수에 전달하기

위의 변수를 활용한 우회적 전달도 가능하지만, call/apply/bind를 활용해 함수 호출시 바인딩 해주는 것이 좋다!

 

const obj = {
   outer: function() {
       console.log(this);  // obj
       const innerFunc = function() {
           console.log(this);
       }
   }
   
   // 즉시 실행하며 바로 this 전달
   innerFunc.call(this);  // obj
}

obj.outer();

 

const obj = {
    outer: function() {
        console.log(this);  // obj
        const innerFunc = function() {
            console.log(this);
        }.bind(this);  // innerFunc에 this를 결합한 새로운 함수 할당
        
        innerFunc();  // obj
    }
};

obj.outer();

 

화살표 함수도 활용할 수 있다~

 

const obj = {
    outer: function() {
        console.log(this);
        const innerFunc = () => {
            console.log(this);  // this 바인딩 과정이 없음
        };
        
        innerFunc();  // obj
    };
};

obj.outer();

화살표 함수는 마지막에 바인딩된 this를 가리킨다. 따라서 상위 컨텍스트의 this!