Web/모던 자바스크립트 Deep Dive

[모던 자바스크립트 Deep Dive] 22. this

동띵 2023. 9. 22. 13:14

this 키워드


  • 객체는 프로퍼티와 메서드를 하나의 논리적인 단위로 묶은 복합적인 자료구조
  • 메서드는 자신이 속한 객체의 상태 (프로퍼티)를 참조하고 변경할 수 있어야 함 ⇒ 메서드가 자신이 속한 객체의 프로퍼티를 참조하려면 자신이 속한 객체를 가리키는 식별자를 참조할 수 있어야 함
    • 객체 리터럴 방식으로 생성한 객체의 경우, 메서드 내부에서 자신이 속한 객체를 가리키는 식별자를 재귀적으로 참조 가능
      • 객체 리터럴은 circle 변수에 할당되기 직전에 평가되므로, getDiameter 메서드가 호출되는 시점에는 이미 객체 리터럴의 평가가 완료되어 객체가 생성됨 → circle 식별자에 생성된 객체가 할당된 이후이기 때문에 메서드 내부에서 circle 식별자 참조 가능
      • 자신이 속한 객체를 재귀적으로 참조하는 방식은 일반적이지 X, 바람직하지 X ⇒ 생성자 함수 방식으로 인스턴스 생성
  • 생성자 함수로 인스턴스 생성하려면 생성자 함수가 존재해야 함 but 생성자 함수 정의 시점에는 아직 인스턴스 생성하기 이전이므로 생성자 함수가 생성할 인스턴스를 가리키는 식별자를 알 수 X ⇒ 자바스크립트에서 자신이 속한 객체 또는 자신이 생성할 인스턴스를 가리키는 특수한 식별자 this제공
    • this : 자신이 속한 객체 또는 자신이 생성할 인스턴스를 가리키는 자기 참조 변수
      • this를 통해 자신이 속한 객체나 자신이 생성할 인스턴스의 프로퍼티 및 메서드 참조 가능
      • this는 자바스크립트 엔진에 의해 암묵적으로 생성되며, 코드 어디서든 참조 가능
      • 함수 호출 시 this가 암묵적으로 함수 내부에 전달되어 지역 변수처럼 사용 가능
      • 단, this가 가리키는 값 (this 바인딩)은 함수 호출 방식에 의해 동적으로 결정
        • this 바인딩 : this와 this가 가리킬 객체를 바인딩하는 것 (바인딩 : 식별자와 값을 연결하는 과정)
  • 객체 리터럴과 this 사용
// 객체 리터럴
const circle = {
	radius: 5,
	getDiameter() {
		// this는 메서드 호출한 객체를 가리킴
		return 2 * this.radius;
	}
}

console.log(circle.getDiameter()); // 10
  • 생성자 함수와 this 사용
// 생성자 함수
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
  • 자바스크립트의 this는 함수가 호출되는 방식에 따라 this에 바인딩될 값, 즉 this 바인딩이 동적으로 결정
    • 전역에서 this 사용 시 전역 객체 window를 가리킴
    • 일반 함수 내부에서 this 사용 시 전역 객체 window를 가리킴
    • 메서드 내부에서 this 사용 시 메서드를 호출한 객체를 가리킴
    • 생성자 함수에서 this 사용 시 생성자 함수가 생성할 인스턴스 가리킴
    • strict mode가 적용된 일반 함수 내부에서 this 사용 시 undefined가 바인딩 됨
      • this는 자기 참조 변수여서 객체의 메서드 내부 또는 생성자 함수 내부에서만 의미가 있기 때문

함수 호출 방식과 this 바인딩


  • this 바인딩은 함수 호출 방식에 따라 동적으로 결정
  • 렉시컬 스코프는 함수 객체 생성 시 결정하고, this 바인딩은 함수 호출 시점에 결정됨
  • 함수 호출 방식
    • 일반 함수 호출
    • 메서드 호출
    • 생성자 함수 호출
    • Function.prototype.apply/call/bind 메서드에 의한 간접 호출
    // this 바인딩은 함수 호출 방식에 따라 동적으로 결정
    const foo = function() {
    	console.dir(this);
    }
    
    // 1. 일반 함수 호출
    foo(); // window (전역 객체인 window를 가리킴)
    
    // 2. 메서드 호출
    const obj = {foo};
    obj.foo(); // obj (메서드를 호출한 객체 obj 가리킴)
    
    // 3. 생성자 함수 호출
    new foo(); // foo{} (생성자 함수가 생성한 인스턴스 가리킴)
    
    // 4. Function.prototype.apply/call/bind 메서드에 의한 간접 호출
    // foo 함수 내부의 this는 인수에 의해 결정 (apply/call/bind 메서드 호출 시 첫 번째 인수로 전달한 객체)
    const bar = {name : 'bar'};
    
    foo.call(bar); // bar
    foo.apply(bar); // bar
    foo.bind(bar)(); // bar
    

일반 함수 호출

  • 기본적으로 this에 전역 객체가 바인딩 됨
  • this는 자기 참조 변수이므로 객체를 생성하지 않는 일반 함수에서는 의미 X ⇒ strict mode 적용된 일반 함수 내부의 this는 undefined가 바인딩 됨
  • 어떠한 함수라도 일반 함수로 호출되면 중첩 함수 내의 this에도 전역 객체가 바인딩 됨 (ex. 메서드 내의 중첩 함수, 콜백 함수)
    • 중첩 함수와 콜백 함수는 외부 함수를 돕는 헬퍼 함수 역할을 하는데, 외부 함수인 메서드와 가리키는 this가 일치하지 않으면 헬퍼 함수로 동작하기 어려움
    • 메서드 내부의 중첩 함수나 콜백 함수의 this 바인딩을 메서드의 this 바인딩과 일치시키는 방법
      • this 바인딩을 다른 변수에 할당 후 헬퍼 함수 내부에서 this 대신 그 변수를 참조하는 것
      • var value = 1; const obj = { value: 100, foo() { // this 바인딩(obj)을 변수 that에 할당 const that = this; // 콜백 함수 내부에서 this 대신 that 참조 setTimeout(function() { console.log(that.value); }, 100); } } obj.foo();
      • Function.prototype.apply/call/bind 메서드 사용하여 this 바인딩 일치
      • 화살표 함수 사용하여 this 바인딩 일치
      • var value = 1; const obj = { value: 100, foo() { // 화살표 함수 내부의 this는 상위 스코프의 this를 가리킴 setTimeout(() => console.log(this.value), 100); } } obj.foo();

메서드 호출

  • 메서드 호출 시 메서드 이름 앞에 마침표(.) 연산자 앞에 기술한 객체가 바인딩 됨
  • 메서드 내부의 this는 메서드를 소유하고 있는 객체가 아닌 메서드를 호출한 객체에 바인딩 됨
const person = {
	name: 'Lee',
	getName() {
		// this는 메서드를 호출한 객체에 바인딩 됨
		return this.name;
	}
}

console.log(person.getName()); // Lee
  • 메서드를 다른 변수에 할당하여 일반 함수로 호출 시 this는 전역 객체 가리킴
const anotherPerson = {
	name: 'Kim'
}
// 위 person 객체의 getName 메서드를 anotherPerson 객체의 메서드로 할당
anotherPerson.getName = person.getName;
console.log(anotherPerson.getName()); // Kim

// 위 person 객체의 getName 메서드를 변수에 할당
const getName = person.getName;
// 일반 함수로 호출된 getName 함수 내부의 this.name은 window.name과 같음
console.log(getName()); // ''

생성자 함수 호출

  • 생성자 함수가 생성할 인스턴스에 바인딩 됨 (new 연산자 사용하여 생성자 함수로 호출해야 됨)
// 생성자 함수
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()); // 10
console.log(circle2.getDiameter()); // 20

// new 연산자 없이 호출하면 일반 함수로 호출됨
const circle3 = Circle(15);
console.log(circle3); // undefined

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

  • apply, call, bind 메서드는 Function.prototype의 메서드 (모든 함수가 상속받아 사용 가능)
  • Function.prototype.apply와 Function.prototype.call 메서드는 this로 사용할 객체와 인수 리스트를 인수로 전달받아 함수 호출
    • apply와 call의 본질적인 기능 : 함수 호출
    • apply는 두 번째 인수에 인수 리스트의 배열 또는 유사 배열 객체를 전달하고, call은 두 번째 인수에 인수 리스트를 전달함
    function getThisBinding() {
    	return this;
    }
    const thisArg = {a:1};
    
    // apply는 인수 리스트의 배열 또는 유사 배열 객체 전달
    console.log(getThisBinding.apply(thisArg, [1, 2, 3])); // {a:1}
    
    // call은 인수 리스트를 쉼표로 구분하여 전달
    console.log(getThisBinding.call(thisArg, 1, 2, 3)); // {a:1}
    
    • apply와 call의 대표적인 용도 : arguments 객체와 같은 유사 배열 객체배열 메서드 사용하는 용도
  • Function.prototype.bind는 함수 호출 X, 첫 번째 인수로 전달한 값으로 this 바인딩이 교체된 함수를 새롭게 생성해 반환
    • bind 메서드는 메서드의 this헬퍼 함수 (메서드 내부의 중첩 함수, 콜백 함수)의 this가 불일치하는 문제 해결
// apply, call
function convertArgsToArray() { 
	console.log(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
 const person = { 
 	name: 'Lee', 
    foo(callback) { 
        // bind 메서드로 콜백 함수 내부의 this 바인딩을 전달 
        setTimeout(callback.bind(this), 100); 
    } 
 } 
 
 person.foo(function() { 
 	console.log(`Hi! my name is ${this.name}.`);  // Hi! my name is Lee. 
 )}