JS (22) - this

최조니·2022년 7월 14일
0

JavaScript

목록 보기
19/36

22.1 this 키워드

  • 동작을 나타내는 메서드는 자신이 속한 객체의 상태, 프로퍼티를 참조하고 변경할 수 있어야 함
    • 자신이 속한 객체의 프로퍼티를 참조하려면 자신이 속한 객체를 가리키는 식별자를 참조할 수 있어야 함

객체 리터럴 방식으로 생성한 객체의 경우 ❓

  • 매서드 내부에서 메서드 자신이 속한 객체를 가리키는 식별자를 재귀적으로 참조할 수 있음
const circle = {
  radius: 5,
  getDiameter() {
    // 메서드가 자신이 속한 객체의 프로퍼티나 다른 메서드를 참조하려면, 
    // 자신이 속한 객체인 circle을 참조할 수 있어야 한다
    return 2 * circle.radius;
  }
};

console.log(circle.getDiameter());	 // 10
  • 참조 표현식이 평가되는 시점은 getDiameter 메서드가 호출되어 함수 몸체가 실행되는 시점

  • 자기 자신이 속한 객체를 재귀적으로 참조하는 방식은 일반적이지도 바람직하지도 않음


생성자 함수 방식으로 인스턴스를 생성하는 경우 ❓

function Circle(radius) {
  // 이 시점에는 생성자 함수 자신이 생성할 인스턴스를 가리키는 식별자를 알 수 없음
  ????.radius = radius;
}

Circle.prototype.getDiameter = function() {
  // 이 시점에는 생성자 함수 자신이 생성할 인스턴스를 가리키는 식별자를 알 수 없음
  return 2 * ????.radius;
};

// 생성자 함수로 인스턴스를 생성하려면 먼저 생성자 함수를 정의해야 함
const circle = new Circle(5);
  • 생성자 함수 내부에서는 프로퍼티 또는 메서드를 추가하기 위해 자신이 생성할 인스턴스를 참조할 수 있어야 하지만,
    생성자 함수에 의한 객체 생성 방식은 먼저 생성자 함수를 정의한 후 new 연산자와 함께 생성자 함수를 호출하는 단계가 추가로 필요

  • 즉, 생성자 함수로 인스턴스를 생성하려면 먼저 생성자 함수가 필요

  • 생성자 함수를 정의하는 시점에는 아직 인스턴스를 생성하기 이전이므로 생성자 함수가 생성할 인스턴스를 가리키는 식별자를 알 수 없음


→ 자바스크립트는 특수한 식별자 this 제공

this : 자신이 속한 객체 또는 자신이 생성할 인스턴스를 가리키는 자기 참조 변수
즉, this를 통해 자신이 속한 객체 또는 자신이 생성할 인스턴스의 프로퍼티나 메서드를 참조할 수 있음

  • this는 자바스크립트 엔진에 의해 암묵적으로 생성되며, 코드 어디서든 참조 가능

  • 단, this가 가리키는 값은 함수 호출 방식에 의해 동적으로 결정됨

함수 호출 방식this가 가리키는 값
일반 함수로서 호출전역 객체
메서드로서 호출메서드를 호출한 객체
생성자 함수로서 호출생성자 함수가 생성할 인스턴스

객체 리터럴에서의 this

const circle = {
  radius: 5,
  getDiameter() {
    return 2 * this.radius;
  }
};
console.log(circle.getDiameter());		// 10
  • 객체 리터럴의 메서드 내부에서의 this는 메서드를 호출한 객체 (circle)를 가리킴

생성자 함수 내부에서의 this

function Circle(radius) = {
  this.radius = radius;
}

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

const Circle = new Circle(5);
console.log(circle.getDiameter());		// 10
  • 생성자 함수 내부의 this는 생성자 함수가 생성할 인스턴스를 가리킴

strict mode에서의 this 바인딩

  • strict mode가 적용된 일반 함수 내부의 this에는 undefined가 바인딩
    → 일반 함수 내부에서는 this를 사용할 필요가 없기 때문❗️

※ 정리

// 전역에서의 this : 전역 객체 window
console.log(this);				// window


// 일반 함수 내부에서의 this: 전역 객체 window
function square(number) {
  console.log(this);			// window
  return number * number;
}
square(2);


// 객체 리터럴 메서드에서의 this : 메서드를 호출한 객체
const person = {
  name: 'Lee',
  getName() {
    console.log(this);		// {name: "Lee", getName: f}
    return this.name;
  }
};
console.log(person.getName());		// Lee


// 생성자 함수 내부에서의 this : 생성자 함수가 생성할 인스턴스
function Person(name) {
  this.name = name;			
  console.log(this);		// Person {name: "Lee"}
}
const me = new Person('Lee');

22.2 함수 호출 방식과 this 바인딩

  • this 바인딩은 함수 호출 방식에 따라 동적으로 결정됨

  • 다양한 함수 호출 방식
    • 일반 함수 호출
    • 메서드 호출
    • 생성자 함수 호출
    • Function.prototype.apply/call/bind 메서드에 의한 간접 호출

1) 일반 함수 호출

기본적으로 this에는 전역 객체가 바인딩

  • 일반 함수로 호출하면 함수 내부의 this에는 전역 객체가 바인딩
function foo() {
  console.log("foo's this: ", this);		// window
  function bar() {
    console.log("bar's this: ", this);		// window
  }
  bar();
}
foo();
  • 객체를 생성하지 않는 일반 함수에서 this 의미 X

  • strict mode가 적용된 일반 함수 내부의 this에는 undefined 바인딩

function foo() {
  'use strict';
  
  console.log("foo's this: ", this);		// undefined
  function bar() {
    console.log("bar's this: ", this);		// undefined
  }
  bar();
}
foo();
  • 메서드 내에서 정의한 중첩함수도 일반 함수로 호출되면 중첩 함수 내부의 this에는 전역 객체가 바인딩
var value = 1;

const obj = {
  value: 100,
  foo() {
    console.log("foo's this: ", this);
    console.log("foo's this.value: ", this.value);
    
    function bar() {
      console.log("bar's this: ", this);
      console.log("bar's this.value: ", this.value);
    }
    
    bar();
  }
};

obj.foo();
  • 콜백 함수가 일반 함수로 호출된다면 콜백 함수 내부의 this에도 전역 객체가 바인딩
var value = 1;

const obj = {
  value: 100,
  foo() {
    console.log("foo's this: ", this);
    
    setTimeout(function () {
      console.log("callback's this: ", this);
      console.log("callback's this.value: ", this.value);
    }, 100);
  }
};

obj.foo();

→ 일반 함수로 호출된 모든 함수 (중첩 함수, 콜백 함수 포함) 내부의 this에는 전역 객체가 바인딩


2) 메서드 호출

메서드를 호출한 객체(메서드 이름 앞의 마침표 연산자 앞에 기술한 객체)가 바인딩

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

console.log(person.getName());		// Lee
  • 다른 객체의 메서드가 될 수도 있고, 일반 함수로 호출될 수 있다.
const anotherPerson = {
  name: 'Kim'
};

// getName 메서드를 anotherPerson 객체의 메서드로 할당
anotherPerson.getName = person.getName;

// getName 메서드를 호출한 객체는 anotherPerson
console.log(anotherPerson.getName());	// Kim

// getName 메서드를 변수에 할당
const getName = person.getName;

// getName 메서드를 일반 함수로 호출
console.log(getName());				// '' - window.name
  • 메서드 내부의 this는 프로퍼티로 메서드를 가리키고 있는 객체와는 관계가 없고 메서드를 호출한 객체에 바인딩

  • 프로토타입 메서드 내부에서 사용된 this도 일반 메서드와 마찬가지로 해당 메서드를 호출한 객체에 바인딩

function Person(name) {
  this.name = name;
}

Person.prototype.getName = function() {
  return this.name;
};

const me = new Person('Lee');

// getName 메서드를 호출한 객체는 me
console.log(me.getName());			// Lee
Person.prototype.name = 'Kim';

// getName 메서드를 호출한 객체는 Person.prototype
console.log(Person.prototype.getName());	// Kim

3) 생성자 함수 호출

생성자 함수 내부의 this에는 생성자 함수가 (미래에) 생성할 인스턴스가 바인딩

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

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

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

4) Function.prototype.apply/call/bind 메서드에 의한 간접 호출

  • apply, call, bind 메서드는 Function.prototype의 메서드
    → 모든 함수가 상속받아 사용할 수 있음

applycall 메서드의 사용법

  • apply
    Function.prototype.apply(thisArg[, argsArray])
    @param thisArg - this로 사용할 객체
    @param argsArray - 함수에게 전달할 인수 리스트의 배열 또는 유사 배열 객체
    @returns - 호출된 함수의 반환값

  • call
    Function.prototype.call(thisArg[, arg1[, arg2[, ...]]])
    @param thisArg - this로 사용할 객체
    @param arg1, arg2, ... - 함수에게 전달할 인수 리스트
    @returns - 호출된 함수의 반환값
function getThisBinding() {
  return this;
}

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

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

console.log(getThisBinding.apply(thisArg));		// {a: 1}
console.log(getThisBinding.call(thisArg));		// {a: 1}
  • apply, call 메서드의 본질적인 기능은 함수를 호출하는 것

  • 함수를 호출하면 첫 번째 인수로 전달한 특정 객체를 호출한 함수의 this에 바인딩

function getThisBinding() {
  console.log(arguments);
  return this;
}

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

console.log(getThisBinding.apply(thisArg, [1, 2, 3]));		// {a: 1}
console.log(getThisBinding.call(thisArg, 1, 2, 3));		// {a: 1}
  • apply 메서드는 호출할 함수의 인수를 배열로 묶어 전달

  • call 메서드는 호출할 함수의 인수를 쉼표로 구분한 리스트 형식으로 전달

bind 메서드

Function.prototype.bind 메서드는 apply와 call 메서드와 달리 함수를 호출하지 않고,
첫 번째 인수로 전달한 값으로 this 바인딩이 교체된 함수를 새롭게 생성해 반환

function getThisBinding() {
  return this;
}

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

// getThisBinding함수를 새롭게 생성해 반환
console.log(getThisBinding.bind(thisArg));		// getThisBinding
console.log(getThisBinding.bind(thisArg)());	// {a: 1}
  • 메서드 내부의 중첩 함수 혹은 콜백 함수의 this가 불일치하는 문제를 해결하기 위해 유용하게 사용

※ 정리

함수 호출 방식this가 가리키는 값
일반 함수로서 호출전역 객체
메서드로서 호출메서드를 호출한 객체
생성자 함수로서 호출생성자 함수가 생성할 인스턴스
Function.prototype.apply/call/bind 메서드에 의한 간접 호출Function.prototype.apply/call/bind 메서드에 첫번째 인수로 전달한 객체
const foo = function () {
  console.dir(this);
}

// 1. 일반 함수 호출
// foo 함수 내부의 this는 전역 객체 window를 가리킴
foo();


// 2. 메서드 호출
// foo 함수 내부의 this는 메서드를 호출한 객체 obj를 가리킴
const obj = { foo };
obj.foo();


// 3. 생성자 함수 호출
// foo 함수 내부의 this는 생성자 함수가 생성한 인스턴스를 가리킴
new foo();


// 4. Function.prototype.apply/call/bind 메서드에 의한 간접 호출
// foo 함수 내부의 this는 인수에 의해 결정
const bar = { name: 'bar' };

foo.call(bar);		// bar
foo.apply(bar);		// bar
foo.bind(bar);		// bar
profile
Hello zoni-World ! (◍ᐡ₃ᐡ◍)

0개의 댓글