[JS] IIFE와 this, 그리고 call(), apply(), bind()
IIFE와 this, 그리고 call(), apply(), bind()
1) IIFE (즉시실행함수)
IIFE는 즉시실행 함수로 전역공간을 오염시키지 않고 즉시실행된 후 사라지는 함수이다. 사용하는 방법은
(function() {
let firstVar;
let secVar;
//실행할 코드 입력
})();
// 화살표함수 이용했을 시
(() => {
//실행할 코드 입력
console.log("This is IIFE");
})();
즉시실행 함수에서 할당된 변수들은 함수실행 후에 파기된다. 즉시실행함수는 전역스코프에 불필요한 변수를 추가해서 전역공간이 오염, 충돌되는 것을 방지할 수 있을 뿐만 아니라 IIFE 내부 안으로 다른 변수들이 접근하는 것을 막을 수 있는 방법이다. 만약 단한번만 호출될 코드를 만든다면 되도록 IIFE를 사용하는 것이 좋다.
2) this의 동작방식
함수를 어떻게 호출하냐, 즉 함수호출방식에 따라 this에 바인딩되는 객체가 달라진다.
console.log(this);
// window
- this함수는 전역객체(브라우저라면 window, 노드라면 global)를 참조한다.
- strict mode라면 this는 undefined 이다.
- 이벤트 안에서 this는 수신받은 이벤트의 요소를 참조한다.
this의 헷갈리는 사례 1)
const a = function() {
console.log(this)
const b = function() {
console.log(this)
const c = {
hi: function() {
console.log(this)
}}
c.hi()
}
b()
}
a()
// 결과
// window
// window
// c
a, b 함수는 전역객체의 함수이다. 그렇기 때문에 두 함수의 this는 전역객체를 반환한다. 그런데 c는 c의 메서드인 c.hi()로 호출되었기 때문에 객체 c에 바인딩되어서 c 를 반환한다.
this의 헷갈리는 사례 2)
const obj = {
name: 'Pranav',
sing: function() {
console.log(this)
var anotherFunc = function() {
console.log(this)
}
anotherFunc()
}
}
obj.sing()
// 결과
// obj
// window
obj.sing()을 호출했을때 첫번째 콘솔은 obj 객체에 바인딩되어 obj를 반환하지만 내부함수의 콘솔은 window객체를 반환한다. 메서드내의 내부함수호출
이 어떤 객체에도 붙어있는 것이 아니기 때문에 언제나 전역객체를 반환하는 것이다.
this의 헷갈리는 사례 3)
var b = {
name: 'Pranav',
say() {console.log(this)}
}
var c = {
name: 'Pranav',
say() {return function() {console.log(this)}}
}
var d = {
name: 'Pranav',
say() {return () => console.log(this)}
}
b.say()
c.say()()
d.say()()
//결과
//b
//window
//d
- b.say() 는 b의 메서드안의 this기 때문에 b에 바인딩된 것이 맞다.
- 그런데 c.say()()는 렉시컬스코프가 형성된 곳에서 불린 익명함수이기 때문에 window객체를 반환한다. (렉시컬스코프는 함수가 선언된 위치에 따라 처리되지만 동적스코프는 함수가 어디에서 불렸느냐에 따라 처리된다.)
- d.say()()는 동적스코프가 지정되고 this키워드가 d 객체에 바인딩된 화살표함수를 호출했기 때문에 d객체가 반환된다.
this 객체의 헷갈리는 사례 4)
const phone = function (model, brand){
this.model = model,
this.brand = brand
}
// regular anonymous function used
phone.prototype.clickPicture = function(){
console.log(`${this.brand} ${this.model} clicks picture!`)
}
// arrow function used here
phone.prototype.powerOn = () => {
console.log(`${this.brand} ${this.model} boots up!`)
}
const iphone = new phone("Iphone 12", "Apple")
console.log(iphone.clickPicture())
console.log(iphone.powerOn())
// 결과
//"Apple Iphone 12 clicks picture!"
//"undefined undefined boots up!"
화살표함수는 렉시컬스코프가 지정되고 일반적인 익명함수는 동적으로 스코프가 지정된다. this 때문에 생기는 이상한 현상들은 렉시컬스코프로 범위가 잡히지 않고 동적스코프로 범위가 지정되는 현상 때문이다.
이런 현상들을 해결하려면
- 화살표함수 사용하기
- this를 해당 객체에 바인딩하기
3) call(), apply(), bind()
1.Call()
func.call(thisArg[, arg1[, arg2[, ...]]])
call()
메소드는 주어진 this
값 및 각각 전달된 인수와 함께 함수를 호출한다. apply()와 거의 비슷하지만, call()은 인수목록을, apply()는 인수배열 하나를 받는다.
function Product(name, price) {
this.name = name;
this.price = price;
}
function Food(name, price) {
Product.call(this, name, price); // call() 사용
this.category = 'food';
}
console.log(new Food('cheese', 5).name);
// expected output: "cheese"
call(`바인딩할 객체`, 인자, 인자) 이런 식으로 사용한다. 첫번째인자는 this를 바인딩해줄 객체 그 다음인 두번째 인자부터는 함수에 넘겨줄 매개변수를 지정할 수 있다.
2.apply()
fun.apply(thisArg, [argsArray])
apply()는 call()과 거의 비슷하지만, 매개변수 처리 방법에서 차이가 있다. apply() 역시 첫번째인자는 바인딩해줄 객체를 넣고, 그 다음 매개변수를 배열의 형태로 넣어주어야한다. (배열 또는 유사배열 객체)
const wizard = {
name: 'Pranav',
health: 100,
heal: function(num1, num2) {
this.health += num1 + num2;
}
}
const archer = {
name: 'Robin',
health: 50
}
wizard.heal.apply(archer, [20, 30])
console.log(archer);
// 결과값
// {
// health: 100,
// name: "Robin"
//}
3. bind()
func.bind(thisArg[, arg1[, arg2[, ...]]])
call()과 apply()로 객체에 바인딩하는 함수를 사용할 수 있지만 그런식으로 특정 객체에 묶이는 것은 일시적이다. 이것을 영구적으로 할 수 있는 방법은 없을까? 그럴때 bind()를 사용하게 된다. bind()는 새롭게 바인딩한 함수를 만들어서 반환한다. 바인딩한 함수는 원본함수 객체를 감싸는 함수이다.
const wizard = {
name: 'Pranav',
health: 100,
heal: function(num1, num2) {
this.health += num1 + num2;
}
}
const archer = {
name: 'Robin',
health: 50
}
const healArcher = wizard.heal.bind(archer, 50, 60);
healArcher()
console.log(archer)
//결과
//{
// health: 160,
// name: "Robin"
//}
bind() 함수는 this를 바인딩하지만 함수를 바로 실행하지는 않기 때문에 따로 함수를 호출하여 실행시켜줘야한다.
call(), apply()는 함수의 유사배열인 arguments 객체를 활용할 수 있도록 해준다. 유사배열은 보통의 배열처럼 생겼지만 배열이 아니라서 배열메소드를 사용할 수 없다. 그러나 call(), apply()를 이용하면 arguments 객체에 배열메소드를 사용할 수 있게 된다.