JavaScript의 bind, apply, call
this
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 가 될겁니다.
bind
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; 으로 값이 고정된 함수를 호출하는 것과 같습니다.
call과 apply
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() 는 배열에 고정시킬 인자들을 담아 함수에 고정시킵니다.
참고
- 모던 JavaScript 튜토리얼 (https://ko.javascript.info/)
- mdn
- 제로초님 블로그(https://www.zerocho.com/)
Author And Source
이 문제에 관하여(JavaScript의 bind, apply, call), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@imnotmoon/JavaScript의-bind-apply-call저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념
(Collection and Share based on the CC Protocol.)