this
this 키워드
메서드로 프로퍼티를 참조하고 변경하기 위해서는 우선 자신이 속한 객체를 가리키는 식별자를 참조해야만 가능한 일이다.
1 | const circle = { |
- 객체 리터럴은 circle 변수에 할당되기 직전에 평가된다?
= 할당 연산자에 의해서 피연산자를 할당해주기 위해서는 우측의 객체 리터럴이 평가된 값으로 존재해야 할당을 해줄 수 있기 때문이다.
하지만 위처럼 재귀적으로 자신이 속한 객체를 참조하는 것은 바람직하지 않다.
그 예시를 생성자 함수를 통해 설명해보자
1 | // 생성자 함수 |
- 생성자 함수 내부에서 프로퍼티나 메서드를 추가하기 위해서는 자신이 생성할 인스턴스를 참조할 수 있어야 하는데, 인스턴스를 생성하려면 생성자 함수가 존재해야한다.
따라서 자신이 속한 객체, 자신이 생성할 인스턴스를 가리킬 특별한 식별자가 필요하다.
this란, 자신이 속한 객체 또는 자신이 생성할 인스턴스를 가리키는 자기 참조 변수이다. this를 통해 자신이 속한 객체나 자신이 생성할 인스턴스의 프로퍼티나 메서드를 참조할 수 있다.
- this는 코드 어디서든 참조할 수 있다. (전역에서도 가능)
this는 객체의 프로퍼티나 메소드를 참조하기 위한 자기 참조 변수이므로 객체의 메서드 내부 또는 생성자 함수 내부에서만 의미가 있다. 따라서 strict mode가 선언된 일반 함수 내부의 this는 undefined가 바인딩된다. (일반함수에선 필요 없다)
함수 호출방식과 this 바인딩
this에 바인딩될 값은 함수 호출 방식에 의해 동적으로 결정된다.
1. 일반 함수 호출
전역 객체에 바인딩된다.
중첩 함수 또한 일반 함수로 호출 시 함수 내부의 this는 전역 객체에 바인딩 된다.
매서드 내에서 정의된 중첩함수도 일반 함수로 호출되면 역시 전역 객체에 바인딩 된다.
1 | // var 키워드로 선언한 전역 변수 value는 전역 객체의 프로퍼티다. |
콜백함수가 일반함수로 호출된다면 콜백함수 내부의 this에도 전역객체가 바인딩된다.
1 | var value = 1; |
하지만 메서드 내의 중첩함수와 콜백함수는 외부함수를 돕는 헬퍼 함수의 역할을 하는데 외부함수인 메서드와 중첩함수 또는 콜백함수의 this가 일치하지 않는다는 것은 중첩함수 또는 콜백함수가 헬퍼 함수로 동작하는 것을 어렵게 만든다.
화살표함수 내부에서 this
1 | var value = 1; |
2. 메서드 호출
메서드 내부의 this는 메서드를 소유한 객체가 아닌 메서드를 호출한 객체에 바인딩된다.
1 | const person = { |
- person 객체의 getName 프로퍼티가 가리키는 함수 객체는 person 객체에 포함된 것이 아니라 독립적으로 존재하는 별도의 객체이다? 내 생각에는 this가 가리키는 것이 메서드를 소유한 객체라고 생각해도 맞지 않나?
person 객체에 getName 프로퍼티 키가 가리키는 함수 객체를 소유하고 있는 것이 아니라 참조값을 가지므로 독립적으로 존재하는 객체를 가리키고 있는 것이 맞다.
1 | const anotherPerson = { |
- 새로운 객체의 프로퍼티에 person.getName 프로퍼티를 할당해줄 수 있다.
this는 getName 메서드를 호출한 객체에 바인딩된다.
프로토타입 메서드 내부에서도 마찬가지로 바인딩된다.
3. 생성자 함수 호출
생성자 함수 내부의 this에는 생성자 함수가 (미래에) 생성할 인스턴스가 바인딩 된다.
1 | // 생성자 함수 |
4. Function.prototype.apply/call/bind 메서드에 의한 간접 호출
apply, call, bind 메서드는 Function.prototype의 메서드이다. 이들 메서드는 모든 함수가 상속받아 사용 가능하다.
1 | function getThisBinding() { |
call,apply 메서드는 함수를 호출하면서 첫번째 인수로 전달한 객체를 호출한 함수의 this에 바인딩한다.
- 위 예제에서는 getThisBinding() 함수에 인수를 전달해주지 않는다.
- call, apply의 반환값은 호출한 함수의 반환값이다.
1 | function getThisBinding() { |
call,apply 메서드로 함수를 호출하면서 호출한 함수에 인수를 전달해줄 수 있다.
유사배열 객체에 배열 메서드 사용
1 | function convertArgsToArray() { |
- arguments 객체는 배열이 아니므로 배열 메서드를 사용할 수 없지만 apply, call 메서드를 사용하면 가능하다.
새로 나온 Array.from() 정적 메서드를 사용할 수 있다. 하지만 arguments 객체를 잘 안쓴다.
bind
1 | function getThisBinding() { |
- bind 메서드는 함수를 호출하지 않고 인수로 전달받은 객체로 this 바인딩이 교체된 함수를 새롭게 생성하여 반환한다.
1 | const person = { |
- person.foo의 콜백함수가 호출되기 전 1의 시점에서 this는 foo 메서드를 호출한 객체(person)를 가리킨다.
- 그러나 person.foo의 콜백함수가 일반 함수로서 호출된 2의 시점에서 this는 전역객체 windows를 가리킨다.
- person.foo의 콜백함수는 헬퍼함수로 person.foo를 돕는 역할을 하기 때문에 서로의 this가 같아야한다.
이 때, bind 메서드를 사용하여 this를 일치시킨다.
1 | const person = { |
- callback 함수에 this가 바인딩된 새로운 함수를 반환
코드해설
즉, foo안의 this는 person 객체를가리키는데, 콜백함수 호출하면 this가 window를 가리킨다. 그러므로 bind함수를 사용하여 foo 메서드가 가리키는 this를 callback 함수에 바인딩해줘서 콜백함수가 가리키는 this와 일치 시켜준다.
bind, call, apply, that으로 this 바인딩을 일치 시켜주는 것 보다 화살표 함수를 사용하는 것이 간편하다. 하지만 여러 가지 방식에 대해서도 알아두자.
함수 호출 방식 | this 바인딩 |
---|---|
일반 함수 호출 | 전역 객체 |
메서드 호출 | 메서드를 호출한 객체 |
생성자 함수 호출 | 생성자 함수가 (미래에) 생성할 인스턴스 |
Function.prototype.apply/call/bind 메서드에 의한 간접호출 | Function.prototype.apply/call/bind 메서드에 첫번째로 전달한 객체 |