kidk.kr 강그루 블로그

자바스크립트 함수의 컨텍스트 살펴보기

문제

다음 자바스크립트 구문의 출력은? (정답은 아래에)

1

const foo = 'Hello';
const bar = { baz: 'Hello' };
function getToString(obj) {
  return obj.toString;
}
console.log(getToString(foo)())
console.log(getToString(bar)())

2

const foo = 'Hello';
const bar = { baz: 'Hello' };
function getValueOf(obj) {
    return obj.valueOf
}
console.log(getValueOf(foo)())
console.log(getValueOf(bar)())

자바스크립트 함수의 this 컨텍스트

이것이 this이다

this는 바로 ‘이것’ 이다. ‘이것’은 특정 맥락(context)에서만 의미가 있다. 출처: https://openclipart.org/

자바스크립트 초보자에게 this만큼 헷갈리는 게 있을까? 만약 당신이 ECMAScript는 물론이고 TypeScript가 주류가 된 시점에 웹개발의 세계로 발을 들였다면 더더욱 그럴 것이다. 자바스크립트에서 함수의 this는 자신이 속한 컨텍스트를 따른다.# 예를들어 아래 Node.js 코드를 살펴보자.

global.price = Number.MAX_SAFE_INTEGER;

function getPrice() {
  return this.price;
}

const car = {
  price: 200,
  getPrice: function() {
    return this.price;
  }
};

const air = {
  price: 300,
  getPrice: () => this.price
};

console.log(getPrice());      // 9007199254740991
console.log(car.getPrice());  // 200
console.log(air.getPrice());  // undefined

전역으로 선언된 함수 getPrice()의 컨텍스트는 global이고, 변수 car에 선언된 getPrice()의 컨텍스트는 변수 car이다. arrow function은 this 컨텍스트를 갖지 않기 때문에, undefined를 반환한다.

Function.prototype.bind, Function.prototype.apply, Function.prototype.call을 통해서 this 컨텍스트를 바꿔줄 수 있는데, 굳이 함수 getPrice()를 중복 작성하지 않아도 다음과 같이 쓸 수 있다.

global.price = Number.MAX_SAFE_INTEGER;

function getPrice() {
  return this.price;
}

const bag = { price: 100 };
const car = { price: 200 };
const getCarPrice = getPrice.bind(car);

console.log(getPrice());          // 9007199254740991
console.log(getPrice.apply(bag)); // 100
console.log(getCarPrice()));      // 200
console.log(getPrice.call(car));  // 200

하지만 이건 Class 문법이 없던 시절의 이야기다. ES6에서 도입된 Class를 사용한다면 다음과 같이 작성할 수 있다. (아마 이 방법이 더 익숙할 것이다)

class Product {
  constructor(price) {
    this.price = price;
  }

  getPrice() {
    return this.price;
  }
}


class Bag extends Product {
  constructor() {
    super(100);
  }
}

class Car extends Product {
  constructor() {
    super(200);
  }
}

console.log(new Bag().getPrice()); //100
console.log(new Car().getPrice()); //200

apply, bind, call 비교

  • Function.prototype.apply(thisArg, [argsArray])

    this와 arguments를 지정하고 호출한다.
    foo.bind(thisArg, [argsArray])()foo.apply(thisArg, [argsArray])와 같다.

  • Function.prototype.bind(thisArgs, [argsArray])

    this와 argument를 고정시켜서 새로운 함수를 선언한다.
    foo.bind(thisArg, [argsArray]) 는 foo에서 this와 argument를 고정시킨 새로운 함수를 리턴한다.

  • Function.prototype.call(thisArg, arg1, arg2, …)

    apply와 동일하지만 argument를 배열이 아닌 argument list로 받는 것이 차이.
    foo.apply(thisArg, argsArray)foo.call(thisArg, ...argsArray)와 같다.

정답

1

/question1.js:6
console.log(getToString(foo)())
                            ^
TypeError: String.prototype.toString requires that 'this' be a String

2

/question2.js:6
console.log(getValueOf(foo)())
                           ^
TypeError: String.prototype.valueOf requires that 'this' be a String

오늘의 교훈: this가 없으면 apply(this)를 하자.