프로그래밍 언어/자바스크립트

자바스크립트 this 바인딩에 대한 이야기

초보군붕이 2023. 5. 19. 14:08
반응형

● this 키워드?

this 키워드는 자신이 속한 객체또는 생성자 함수를 통해 생성할 인스턴스 를 가르킨다.

 

예를 들어 메소드에서 this 키워드를 사용하면 어떤 객체에서 호출이 되었는지 확인이 가능하고 생성자 함수를 통해 생성될 인스턴스 또한 참조가 가능하다.

 

 

 

● 객체에서 재귀호출로 참조하기

const obj = {
    data: 10,
    getData() {
        return obj.data;
    }
};

console.log(obj.getData());

하지만 위 코드는 일반적이지 않고 바람직하지 않다고 한다.

객체에서는 재귀호출로 참조가 가능하지만 생성자 함수에서는 위 방법이 불가능하다.

그 이유는 생성자 함수의 경우 new 키워드로 인스턴스가 생성되어야 참조가 가능하기 때문이다.

 

 

● 메소드에서의 this 바인딩

객체 내부 메소드에서 this 바인딩은 메소드를 호출한 객체를 바인딩 한다. 즉 객체를 가르킨다.

const obj = {
  data: 10,
  getData() {
    console.log("getData 메서드 내부 this : ", this);
  },
};

obj.getData(); // { data: 10, getData: [Function: getData] }

메소드를 호출한 객체는 obj 이므로 obj.getData()를 하게될 경우 obj 자체가 출력된다.

 

 

● 일반 함수에서의 this 바인딩

일반함수로 호출된 모든 함수 내부의 this 에는 전역객체가 바인딩 된다.

일반함수로 호출된 콜백함수, 중첩함수 등 모든 함수가 이에 적용된다.

 

- 일반함수로 호출한 콜백 함수

var data = 1;

const obj = {
    data: 100,
    getData() {
        setTimeout(function () {
            console.log('obj의 data : ', this.data);
        })
    }
};

obj.getData();

출력결과

이처럼 obj 객체 내부에서 정의한 data가 전역객체에 존재하는 data 값이 출력된다.

 

 

 

- 일반함수로 호출된 중첩함수

var age = 10;

function person() {
    let age = 20;

    function findPerson() {
        console.log(`findPerson의 this.age : ${this.age}`);
    }

    findPerson();
}

person();

출력결과

동일하게 전역객체 age의 값을 가져온다.

 

 

 

● 생성자함수에서의 this 바인딩

생성자함수에서의 this 바인딩은 생성자함수로 생성될 인스턴스를 가르키게 된다.

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

Person.prototype.getInfo = function() {
    console.log(`${this.name}의 나이는 ${this.age} 입니다.`);
}

const firstPerson = new Person('Kim', 23);
const secondPerson = new Person('Lee', 25);

firstPerson.getInfo();
secondPerson.getInfo();

출력결과

각각 생성된 firstPerson 과 secondPerson 의 인스턴스에 정의된 this.name, this.age 를 출력한다.

 

 

● 화살표함수에서의 this 바인딩

화살표함수의 경우 this는 상위스코프의 this를 바인딩하게 된다.

var age = 10;

const person = {
    age: 20,
    getAge() {
        setTimeout(() => {
            console.log(this.age);
        });
    },
}

person.getAge();

 

출력결과

위 결과처럼 전역객체의 프로퍼티인 age(10)가 아닌 person 객체 내부에서 정의된 age(20)를 가져오게 된다.

 

 

● function의 메서드 apply, call, bind

일반함수에서는 this 바인딩이 항상 전역객체를 가르키기 때문에 원하는 로직으로 동작을 안할수도 있다.

이때 Function.prototype 내부에 정의된 apply, call, bind 메서드를 통해 이를 해결할 수 있다.

 

 

- Function.prototype.apply

/**
 * Function.prototype.apply
 * 이 메서드는 this 객체와 인수 리스트를 배열로 받아서 전달해준다.
 * @param thisArg - this에 바인딩할 객체
 * @param argsArray - 배열로된 인수 리스트
 */
function getThis() {
  console.log("arguments: ", Array.from(arguments));
  return this;
}

const obj = { x: 1 };

console.log(getThis()); // Window {...}
console.log(getThis.apply(obj, [1, 2, 3])); // arguments:  [ 1, 2, 3 ], { x: 1 }

 

일반함수로 호출하게되면 전역객체를 바인딩하게 된다.

하지만 apply 메소드를 사용하여 obj를 thisArg로 넘기게되면 this에는 obj({x: 1})가 바인드된다.

또한 apply 메소드의 2번쨰 인자로 파라미터값을 넘겨주면 arguments로 확인이 가능하다.

 

 

 

- Function.prototype.call

/**
 * Function.prototype.call
 * 이 메서드는 this 객체와 인수를 콤마로 구분하여 전달해준다.
 * @param thisArg - this에 바인딩할 객체
 * @param arg1, arg2, ... - 콤마로 구분된 인수 리스트
 */

function getThis() {
  console.log("arguments: ", Array.from(arguments));
  return this;
}

const obj = { x: 1 };

console.log(getThis()); // Window {...}
console.log(getThis.call(obj, 1, 2, 3)); // arguments:  [ 1, 2, 3 ], { x: 1 }

apply 메소드와 유사하나 인수목록을 배열형태가 아닌 콤마로 구분해서 받는다는 차이점이 있다.

 

 

 

- Function.prototype.bind

apply, call 메서드는 매개변수를 넘겨줬지만 bind는 this객체만 넘겨주는 메서드이다.

또한 apply, call과는 다르게 함수호출을 하지 않으니 명시적으로 호출을 해줘야한다.

 

/**
 * Function.prototype.bind
 * 이 메서드는 this 객체와 인수를 콤마로 구분하여전 달해준다.
 * @param thisArg - this에 바인딩할 객체
 */

function getThis() {
  return this;
}

const obj = { x: 1 };

console.log(getThis()); // Window {...}
console.log(getThis.bind(obj)); // [Function: ...]
console.log(getThis.bind(obj)()); // { x : 1 }

위 결과처럼 명시적으로 호출을 해주지 않으면 함수 선언부를 그대로 출력하게 된다.

반응형