[JS] IIFE와 this, 그리고 call(), apply(), bind()

dev 2022년 3월 30일

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 객체에 배열메소드를 사용할 수 있게 된다.

참고: https://dev.to/pranav016/advanced-javascript-series-part-61-everything-in-js-is-an-object-primitive-non-primitive-types-1d8c

태그