this

</>·2022년 2월 10일
4
post-thumbnail

목표

  • 22장의 내용을 최대한 이해하고 정리하기

22. this


22-1.   this 키워드

  • this 키워드를 알아보기 전 객체에 대해 짚고 넘어가야 한다.
  • 객체는 상태를 나타내는 프로퍼티와 동작을 나타내는 메서드를 하나의 논리적인 단위로 묶은 복합적인 자료구조이다.
  • 이 때, 메서드는 자신이 속한 객체의 프로퍼티를 참조하고 변경할 수 있어야 한다. 그러기 위해서는 자신이 속한 객체를 가리키는 식별자를 참조할 수 있어야 한다.
  • 자바스크립트는 이를 위해 this라는 특수한 식별자를 제공한다.
  • this는 자신이 속한 객체 또는 자신이 생성할 인스턴스를 가리키는 자기 참조 변수(self-referencing variable)이다.
  • 이 this 키워드를 사용하면 객체의 메서드와 인스턴스에 접근할 수 있다.
function Circle(radius) {
  // this는 생성자 함수가 생성할 인스턴스를 가리킨다.
  this.radius = radius;
}

Circle.prototype.getDiameter = function () {
  return 2 * this.radius;
};

const cir = new Circle(5);
  • 위의 코드에서 this는 메서드를 호출한 객체, 즉 cir을 가리킨다.

22-1-1. this는 누가 만들까?

  • this는 자바스크립트 엔진에 의해 암묵적으로 생성되는데 생성자 함수를 호출하면 arguments 객체와 this가 암묵적으로 함수 내부에 전달된다.

22-2. this 바인딩

  • 바인딩이란 식별자와 값을 연결하는 과정을 말한다.
  • 변수 선언은 변수 이름(식별자)와 확보된 메모리 공간의 주소를 바인딩하는 것이라면 this 바인딩은 this와 this가 가리킬 객체를 연결하는 것이다.
  • this 바인딩은 함수 호출방식에 의해 동적으로 결정된다.
  • 예를 들어, 생성자 함수객체 리터럴을 통해 객체를 생성해 보면
// 생성자 함수
function Circle(radius) {
  this.radius = radius;
}

const cir = new Circle(5);
// 객체 리터럴
const circle = {
  radius = 5,
  getDiameter() {
    return 2 * this.radius;
  }
};
  • 생성자 함수의 this는 인스턴스인 cir을 가리키고 객체 리터럴의 this는 circle을 가리킨다.

22-3. 함수 호출 방식에 따른 this 바인딩

  • 동일한 함수도 다양한 방식으로 호출할 수 있다.
  1. 일반 함수 호출
  2. 메서드 호출
  3. 생성자 함수 호출
  4. Function.prototype.apply/call/bind 메서드에 의한 간접 호출

22-3-1. 일반 함수 호출

  • 기본적으로 this에는 전역 객체(global object)가 바인딩된다.
function foo() {
  console.log(`foo's this: ${this}`);
  function bar() {
    console.log(`bar's this: ${this}`);
  }
  bar();
}
foo();

// 결과
"foo's this: [object Window]"
"bar's this: [object Window]"
  • 객체를 생성하지 않는 일반 함수 내부의 this에는 전역 객체가 바인딩된다.
  • 참고로 strict mode가 적용되면 this에는 undefined가 바인딩된다.

var value = 1;

const obj = {
  value: 100,
  foo() {
    console.log(`foo's this: ${this}`);				// { value: 100, foo: f }
    
    setTimeout(function () {
      console.log(`callback's this: ${this}`);  	// window
      console.log(this.value);						// 1
    }, 1000);
  },
};

obj.foo();
  • 또한, 콜백함수가 일반 함수로 호출된다면 콜백 함수 내부의 this에도 전역 객체가 바인딩된다.
  • 위의 코드에서 setTimeout 함수의 콜백함수의 this는 window를 가리키고 this.value는 전역 변수를 가리킨다.

setTimeout

  • setTimeout 함수는 두 번째 인수로 전달한 시간(ms)만큼 반복하여 첫 번째 인수로 전달한 콜백 함수를 호출하는 타이머 함수이다.
  • 하지만, 콜백 함수 내부의 this에 전역 객체가 바인딩되는 것은 문제가 있다.
  • 위 코드에서 콜백 함수의 this.value는 외부 함수인 foo의 value가 아닌 전역 객체의 value를 참조하고 있으므로 콜백 함수를 외부 함수의 헬퍼 함수로 동작하기 어렵게 만든다.
  • 이는 Function.prototype.bind 메서드나 화살표 함수를 사용하여 문제를 해결할 수 있다.
var value = 1;

// Function.prototype.bind
const obj = {
  value: 100,
  foo() {
    setTimeout(function () {
      console.log(this.value);		// 100
    }.bind(this), 1000);
  },
};

obj.foo();
var value = 1;

// 화살표 함수
const obj = {
  value: 100,
  foo() {
    setTimeout(() => console.log(this.value), 1000);	// 100
  },
};

obj.foo();

22-3-2. 메서드 호출

  • 메서드 내부의 this는 메서드를 호출한 객체에 바인딩된다.
const person = {
  name: "Lee",
  getName() {
    return this.name;
  },
};

console.log(person.getName());

// 결과
"Lee"
  • 메서드 getName은 프로퍼티에 바인딩된 함수이다.
  • 따라서, getName 프로퍼티가 가리키는 함수 객체는 person 객체에 포함된 것이 아닌 독립적으로 존재하는 별도의 함수 객체를 가리키게 된다.
  • 이를 응용하면 getName 메서드는 다른 객체의 프로퍼티에 할당하는 것으로 다른 객체의 메서드가 될 수도 있고 일반 변수에 할당하여 일반 함수로 호출될 수도 있다.
const person = {
  name: "Lee",
  getName() {
    return this.name;
  },
};

const otherPerson = {
  name: "Jung",
};

otherPerson.getName = person.getName;
console.log(otherPerson.getName());			// "Jung"

const getName = person.getName;
console.log(getName());						// 브라우저 환경: "", Node.js 환경: undefined


22-3-3. 생성자 함수 호출

  • 생성자 함수 내부의 this에는 생성자 함수가 생성할 인스턴스가 바인딩된다.
function Circle(radius) {
  this.radius = radius;
  this.getDiameter = function () {
    return 2 * this.radius;
  };
}

const circle1 = new Circle(5);
const circle2 = new Circle(10);

console.log(circle1.getDiameter());
console.log(circle2.getDiameter());

// 결과
10
20

22-3-4. 간접 호출

  • apply, call, bind 메서드는 Function.prototype의 메서드이다.
function getThis() {
  return this;
}

const arg = { a: 1 };
console.log(getThis());					// window

console.log(getThis.apply(arg));		// { a: 1 }
console.log(getThis.call(arg));			// { a: 1 }
  • apply와 call 메서드의 본질적인 기능은 함수를 호출하는 것이다.
  • 함수를 호출하면서 첫 번째 인수로 전달한 특정 객체를 호출함 함수의 this에 바인딩한다.
  • 인수를 전달하는 방식에 따라 apply와 call을 구분하여 사용하는데
function getThis() {
  console.log(arguments);
  return this;
}

const arg = { a: 1 };

console.log(getThis.apply(arg, [1, 2, 3]));
// Arguments(3) [1, 2, 3, callee: ƒ, Symbol(Symbol.iterator): ƒ]
// { a: 1 }
console.log(getThis.call(arg, 1, 2, 3));
// Arguments(3) [1, 2, 3, callee: ƒ, Symbol(Symbol.iterator): ƒ]
// { a: 1 }
  • apply 메서드는 호출할 함수의 인수를 배열로 묶어 전달한다.
  • call 메서드는 호출할 함수의 인수를 쉼표로 구분하여 전달한다.
  • apply와 call 메서드의 대표적인 용도는 arguments 객체와 같은 유사 배열 객체에 배열 메서드를 사용하는 경우이다.
  • arguments 객체는 배열이 아니기 때문에 slice와 같은 배열 메서드를 사용할 수 없지만 apply와 call 메서드를 이용하면 가능하다.
function getThis() {
  const arr = Array.prototype.slice.apply(arguments);
  console.log(arr);			// [Array(3)]
  
  return arr;
}

getThis([1, 2, 3]);

  • bind 메서드는 apply와 call 메서드와는 달리 함수를 호출하지 않고 this로 사용할 객체만 전달한다.
  • bind 메서드는 this와 메서드 내부의 중첩 함수 또는 콜백 함수의 this가 불일치하는 문제를 해결하기 위해 유용하게 사용된다.
const person = {
  name: 'Lee',
  getName(callback) {
    // setTimeout(callback, 100);
    setTimeout(callback.bind(this), 100);
  }
};

person.getName(function() {
  console.log(`My name is ${this.name}`);
});

// 결과
"My name is Lee"

22-3-5. 정리

  • 함수 호출 방식에 따라 this 바인딩이 동적으로 결정되는 것을 정리해보면 다음과 같다.

profile
개발자가 되고 싶은 개발자

0개의 댓글