JavaScript에서 this
의 값은 함수를 호출한 방법에 의해 결정됩니다. 그래서 함수를 호출할때마다 다를 수 있습니다.
this
는 context 객체라고도 불리는데, 실행된 문맥(context)에 따라 내부적으로 this
를 바꿔주기 때문입니다. (this
의 값은 런타임에 평가됩니다.)
const a = {
prop: 42,
func: function() { console.log(this.prop); }
};
console.log( a.func() ); // 42
여기서 func()
메소드의 this
는 객체 a
를 가리킵니다. 객체 a
를 통해 실행된 메소드이기 때문입니다.
const func = function() { console.log(this.prop) };
const a = { func, prop: '객체 a입니다.' }
const b = { func, prop: '객체 b입니다.' }
a.func(); // 객체 a입니다.
b.func(); // 객체 b입니다.
객체 a
와 b
는 모두 func 속성으로 func
함수를 받았습니다. 같은 함수이지만 어디서 실행되는지에 따라 this
는 다르게 평가됩니다.
반대로 아래와 같은 경우도 생각해볼 수 있습니다.
const b = {
prop: 84,
func: function() { console.log(this.prop); }
};
const c = b.func;
c(); // undefined
여기서는 새롭게 변수 c
를 만들어 객체 b
에 있던 메소드를 꺼내서 할당했습니다. 함수 c
를 호출할 때는 b.func()
같이 호출할 때와는 다르게 b
라는 context가 사라졌습니다. 그래서 this
는 문맥을 잃고 b
를 가리키지 못하게 된겁니다.
이 코드가 동작되게 하려면 메소드 func
의 정의부를 this.prop 에서 b.prop
으로 바꿔주셔야 합니다.
복잡한걸 싫어하시는 분들은 그냥 간단하게 변수.메소드()
관계에서 this
는 변수를 가리킨다고 외우시면 됩니다.
한가지 주의하실 점은, 화살표 함수에는 this
가 없습니다. 그래서 외부 렉시컬 환경에서 this
를 찾게 되고, 만약 외부에도 없고, 외부의 외부에도 없고.. 이런식으로 쭉 없다면 결국 window
나 global
이 this
가 될겁니다.
let user = {
firstName: 'John',
sayHi() { alert(`Hello, ${this.firstName}`); }
};
setTimeout(user.sayHi, 1000); // Hello, undefined
브라우저 환경에서 setTimeout
은 콜백함수를 호출할 때 this
에 window
를 할당합니다. Node.js 환경이라면 global
을 할당하겠죠.
user.sayHi()
가 Callback Queue로 넘어가는 과정에서 함수의 정의부만 Callback Queue로 넘어갑니다.
따라서 함수가 Call Stack으로 올라와 실행되려 할 때 이미 문맥정보를 잃어버리게 됩니다.
'Hello, John'이 출력될걸 기대했다면 이 코드가 제대로 작동하기 위해 조금의 과정을 거쳐야 합니다.
우선 렉시컬 환경을 이용하는 방법입니다.
let user = {
firstName: 'John',
sayHi() { alert(`Hello, ${this.firstName}`); }
};
setTimeout(function() { user.sayHi(); }, 1000); // Hello, undefined
setTimeout()
에 주어지는 콜백함수 부분에서 user.sayHi()
를 함수로 한번 래핑해줬습니다.
그 결과 Callback Queue로 들어가고 Call Stack으로 나오게 되는 부분은 user.sayHi()
가 되고, user
는 전역 스코프에 존재하기 때문에 접근이 가능해집니다. 따라서 문맥도 생기고 this
를 포함한 코드가 제대로 작동하게 됩니다.
두번째 방법은 bind
를 사용하는 겁니다.
bind()
메소드가 호출되면 문맥이 묶인 새로운 함수가 하나 생성됩니다. bind()
메소드는 첫번째 인자로 문맥으로 사용할 객체를 받고, 그 뒤로 두번째 인자부터는 새롭게 생성된 함수의 인자에 제공됩니다.
const module = {
x: 42,
getX: function() { return this.x; }
};
const 바인드되지않은getX = module.getX;
const 바인드된getX = module.getX.bind(module);
console.log(바인드되지않은getX()); // undefined
console.log(바인드된getX()); // 42
여기서 바인드된getX
는 바인드되지않은getX
와 마찬가지로 메소드를 외부로 꺼냈기 때문에 호출할 때 context를 잃어야 하지만, bind()
를 통해 함수 내부적으로 this
가 무엇인지를 정해줬습니다.
따라서 바인드된getX
를 호출할 때는 this
가 바인드한 module
로 간주되어 42가 제대로 찍힐 수 있게 됩니다.
const module = {
x: 42,
getX : function(a, b, c) { return this.x + a + b + c; };
}
const 인자까지바인드한getX = module.getX.bind(module, 1, 2, 3);
console.log(인자까지바인드한getX()); // 48
위에서 언급했듯 bind
는 인자까지 묶어버릴 수 있습니다.
bind
메소드의 인자로 1, 2, 3을 추가로 넘겨주었고, 이는 module.getX()
의 인자 a, b, c에 각각 매핑되어 고정됩니다.
결국 인자까지바인드한getX
를 호출한다는 것은 return module.x + 1 + 2 + 3;
으로 값이 고정된 함수를 호출하는 것과 같습니다.
bind()
가 context를 묶어 새로운 함수를 생성해준다면, call()
과 apply()
는 함수에 문맥을 주입해서 실행을 시켜버립니다.
const _bind = func.bind(obj);
const _apply = func.apply(obj);
const _call = func.call(obj);
_bind
는 obj
가 context로 묶인 새로운 함수입니다.
_apply
와 _call
은 func
에 obj
를 넣고 실행시킨 결과(평가된 값)입니다.
apply()
와 call()
은 기본적으로 그 기능이 같지만, 차이점은 인자를 고정시키는 방식입니다.
const _call = func.apply(obj, 1, 2, 3, 4, 5);
const _apply = func.apply(obj, [1, 2, 3, 4, 5]);
위와 같이 call()
은 인자를 고정시킬 때 bind()
에서 그랬듯 쭉 나열시켜 고정하고하고, apply()
는 배열에 고정시킬 인자들을 담아 함수에 고정시킵니다.
좋아요! 구독!