JavaScript-this

정지훈·2020년 12월 8일
0

객체지향 프로그래밍에서 살펴보았듯이 객체는 상태를 나타내는 프로퍼티와 프로퍼티의 동작을 나타내는 메서드를 하나의 논리적인 단위로 묵은 복합적인 자료구조 이다.

이때 동작을 나타내는 메서드는 자신이 속한 객체의 프로퍼티에 참조하고 변경할 수 있어야 한다.
즉 자신이 속한 객체를 가리키는 식별자를 참조해야 한다.

객체 리터럴로 만든 메서드는 자신이 속한 객체인 circle을 참조해야한다.

const circle = {
	radius: 5,
  getDiameter() {
  	return 2 * circle.radius;
  }
}
console.log(circle.getDiameter()) // 10

위에 처럼 사용할 수도 있고 this를 통해 사용을 할 수 있다.

const circle = {
	radius: 5,
  getDiameter() {
  	return 2 * this.radius;
  }
}
console.log(circle.getDiameter()) // 10

이것처럼 this는 자신의 객체 프로퍼티를 참조할 수 있다.

하지만 이러한 경우는 radius값이 변할 때 마다 다시 리터럴을 생성해야 하는 단점이 있다.

그래서 생성자 함수를 써서 메서드를 사용할 때 생성자 함수의 프로토타입에다 메서드를 만드는 것이 더 좋은 예다.

// 생성자 함수
function Circle(radius) {
  // this는 생성자 함수가 생성할 인스턴스를 가리킨다.
  this.radius = radius;
}

Circle.prototype.getDiameter = function () {
  // this는 생성자 함수가 생성할 인스턴스를 가리킨다.
  return 2 * this.radius;
};

// 인스턴스 생성
const circle = new Circle(5);
console.log(circle.getDiameter()); // 10

위에 처럼 Circle.prototype에 getDiameter메서드를 만들었다.

this 바인딩의 결정은 함수 호출시점에 결정된다.

렉시컬 스코프를 결정하는건 소스 코드 평가때 함수 정의가 평가되어 함수 객체가 만들어 지면서 결정이 되지만 this 바인딩의 결정은 함수 호출 시점에 결정됩니다.

함수는 다양한 방식으로 호출할 수 있다.

  1. 일반 함수 호출
  2. 메서드 호출
  3. 생성자 함수 호출
  4. Function.prototype.apply/call/bind에 메서드의 간접 호출

각각 마다 this 바인딩이 달라진다.

일반 함수 호출

일반 함수로 호출 했을때는 전역 객체에 바인딩 되어진다.

function foo() {
  console.log(this)
}
foo(); // window

메서드 호출

메서드로 호출했을때는 메서드 앞 (.)의 객체에 바인딩 되어 진다.
메서드로 호출했을때의 예시를 보면

const obj = { foo };
obj.foo();

이렇게 호출하는데 .앞에 객체에 바인딩 된다.

이때 메서드 . 앞에는 무조건 객체다.(string, number ..등 원시값도 메서드가 있는데?? 이럴경우는 레퍼객체가 만들어 져서 메서드를 이용할 수 있는 것이다.)

생성자 함수 호출

생성자 함수로 호출했을 때는 내부 this는 미래에 만들어질 인스턴스에 바인딩 된다.

// 생성자 함수
function Circle(radius) {
  // 생성자 함수 내부의 this는 생성자 함수가 생성할 인스턴스를 가리킨다.
  this.radius = radius;
  this.getDiameter = function () {
    return 2 * this.radius;
  };
}

// 반지름이 5인 Circle 객체를 생성
const circle1 = new Circle(5);
// 반지름이 10인 Circle 객체를 생성
const circle2 = new Circle(10);

console.log(circle1.getDiameter()); // 10
console.log(circle2.getDiameter()); // 20

이것처럼 this는 new가 있는 것과 없는 것에 바인딩하는 차이가 있다.

apply, call, bind에 의한 간접 호출

apply,call,bind 메서드는 Function.prototype의 메서드이고 모든 함수가 사용할 수 있다.

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

apply는 바인딩할 객체와 배열 또는 유사 배열 형태로 인수를 줄수 있다.

Function.prototype.call (thisArg[, arg1[, arg2[, ...]]])

call은 바인딩할 객체와 함수에게 전달한 인수를 그대로 줄 수 있다.

function getThisBinding() {
  return this;
}

// this로 사용할 객체
const thisArg = { a: 1 };

console.log(getThisBinding()); // window

// getThisBinding 함수를 호출하면서 인수로 전달한 객체를 getThisBinding 함수의 this에 바인딩한다.
console.log(getThisBinding.apply(thisArg)); // {a: 1}
console.log(getThisBinding.call(thisArg)); // {a: 1}

apply와 call은 호출도 하지만 bind는 호출을 안한다.

보통 이런 간접 호출 방식을 왜 쓰는 거냐면 만약 arguments객체와 같이 유사 배열은 배열 메서드를 사용하지 못한다. 그래서 사용 하기 위해

function convertArgsToArray() {
  console.log(arguments);

  // arguments 객체를 배열로 변환
  // Array.prototype.slice를 인수없이 호출하면 배열의 복사본을 생성한다.
  const arr = Array.prototype.slice.call(arguments);
  // const arr = Array.prototype.slice.apply(arguments);
  console.log(arr);

  return arr;
}

convertArgsToArray(1, 2, 3); // [1, 2, 3]

이런식으로 간접적으로 호출을 해서 사용할 수 있다. 하지만 위에 방식은 옛날 방식이고
배열에서 디스트럭처링 할당을 하면 더 쉽게 복사할 수 있다.

bind메서드는 다른 객체로 바인딩을 시켜 주지만 호출은 하지 않는다.

function getThisBinding() {
  return this;
}

// this로 사용할 객체
const thisArg = { a: 1 };

// bind 메서드는 함수에 this로 사용할 객체를 전달한다.
// bind 메서드는 함수를 호출하지는 않는다.
console.log(getThisBinding.bind(thisArg)); // getThisBinding
// bind 메서드는 함수를 호출하지는 않으므로 명시적으로 호출해야 한다.
console.log(getThisBinding.bind(thisArg)()); // {a: 1}

이때 바인딩이라는 말이 계속 나오는데 바인딩은 어떤 값이 저장된 메모리주소와 식별자를 연결해서 그 식별자를 통해 값에 접근 할 수 있도록 하는 것이다.

0개의 댓글