22 this

이재철·2021년 9월 30일
0

javaScript

목록 보기
11/19
post-thumbnail

this 키워드

  • 자신이 속한 객체 또는 자신이 생성할 인스턴스를 가르키는 자기 참조 변수(self-referencing variable)
    • this를 통해 자신이 속한 객체 또는 자신이 생성할 인스턴스의 프로퍼티나 메서드를 참조
    • this는 자바스크립트 엔진에 의해 암묵적으로 생성, 어디서든 참조 가능
    • this가 가리키는 값, this 바인딩은 함수 호출 방식에 의해 동적으로 결정

💡 this 바인딩(this binding)
바인딩 : 식별자와 값을 연결하는 과정을 의미

  • 변수 선언은 변수 이름(식별자)과 확보된 메모리 공간의 주소를 바인딩하는 것을 뜻함
// 객체 리터럴
const circle = {
 radius: 5,
 getDiameter() {
  // this 메서드를 호출한 객체를 가르킴
  return 2 * this.radius;
 }
}

console.log(circle.getDiameter()); // 10

// 생성자 함수
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는 어디서든 참조 가능
// 전역에서 this는 전역 객체 window을 가리킴
console.log(this); // window

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

const person = {
  name: 'Lee',
  getname() {
    // 메서드 내부에서 this는 메서드를 호출한 객체를 가리킴
    console.log(this); // {name: "Lee", getName: f}
    return this.name;
  }
};
console.log(person.getName()); // Lee

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

함수 호출 방식과 this 바인딩

  • this 바인딩을 함수 호출 방식, 함수가 어떻게 호출되었는지에 따라 동적으로 결정

💡 렉스컬 스코프와 this 바인딩은 결정 시기가 다름

  • 함수 상위 스코프를 결정하는 방식인 렉시컬 스코프는 함수 정의가 평가되어 함수 객체가 생성되는 시점에 상위 스코프를 결정,
  • this 바인딩은 함수 호출 시점에 결정

일반 함수 호출

  • 기본적으로 this에는 전역 객체(global object) 바인딩
  • 일반 함수로 호출된 모든 함수(중첩 함수, 콜백 함수 포함) 내부의 this에는 전역 객체가 바인딩
 function foo() {
  console.log("foo's this: " , this); // window
   function bar() {
     console.log("bar's this: ", this); // window
   }
   bar();
}
foo();
// var 키워드로 선언한 전역 변수 value는 전역 객체의 프로퍼티
var value = 1;
// const 키워드로 선언한 전역 변수 value는 전역 객체의 프로퍼티가 아님
// const value = 1;

const obj = {
  value: 100,
  foo() {
    console.log("foo this: ", this); // { value: 100, foo:f}
    console.log("foo this.value: ", this.value); // 100
    
    // 메서드 내에서 정의한 중첩 함수
    function bar() {
      console.log("bar this: ", this); // window
      console.log("bar this.value: ", this.value); // 1
    }
    // 메서드 내에서 정의한 중첩 함수도 일반 함수로 호출하면 중첩 함수의 내부의 this는 전역 객체가 바인딩
  bar();
  }
};
var value = 1;
const obj = {
  value: 100,
  foo() {
    console.log("foo this: ", this); // {value: 100, foo: f}
    // 콜백 함수 내부의 this에는 전역 객체가 바인딩
    setTimeout(function () {
      console.log("callback this: ", this); // window
      console.log("callback this.value: ", this.value); // 1
    }, 100);
  }
};
obj.foo();

💡 setTimeout 함수

  • 두 번째 인수로 전달한 시간(ms)만큼 대기 후 첫 번째 인수로 전달한 콜백 함수를 호출하는 타이머 함수
  • 메서드 내부의 중첩 함수나 콜백 함수의 this 바인딩을 메서드의 this 바인딩과 일치하기 위한 방법
    • 변수에 할당
    • 명시적으로 바인딩 메서드 : Function.prototype.apply, Function.prototype.call, Function.prototype.bind
    • 화살표 함수를 통해 this 바인딩
var value = 1;
const obj = {
  value: 100,
  foo() {
    
    // this 바인딩(obj)을 변수 that에 할당
    const that = this;
    setTimeout(function() {
      // 콜백 함수 내부에서 this 대신 that 참조
      console.log(that.value); // 100
    }, 100);
    
    // 콜백 함수에 명시적으로 this를 바인딩
    setTimeout(function () {
      console.log(this.value); // 100
    }.bind(this), 100);
    
    // 화살표 함수 내부의 this는 상위 스코프 this를 가리킴
    setTimeout(() => console.log(this.value), 100); // 100
  }
};
obj.foo()

메서드 호출

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

// 메서드 getName을 호출한 객체는 person
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;
console.log(getName()); // ''
// 일반 함수로 호출된 getName 함수 내부의 this.name은 브라우저 환경에서 window.name과 같음


// ex2)

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

생성자 함수 호출

  • 생성자 함수 내부의 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

// new 연산자와 함께 호출하지 않으면 생성자 함수가 아닌 일반적인 함수 호출
const circle3 = Circle(15);
// 일반 함수로 호출된 Circle에는 반환문이 없으므로 암묵적으로 undefined를 반환
console.log(circle3); undefined
// 일반 함수로 호출된 Circle 내부의 this는 전역 객체를 가르킴
console.log(radius); // 15

💡 Function.prototpye.apply, Function.prototype.call 메서드 사용법

/**
* 주어진 this 바인딩과 인수 리스트 배열을 사용하여 함수를 호출
* @param thisArg - this로 사용할 객체
* @param argsArray - 함수에 전달할 인수 리스트의 배열 또는 유사 배열 객체
* @returns 호출된 함수의 반환값 
*/
Function.prototype.appy(thisArg[, argsArray])

/** 
* 주어진 this 바인딩과 ,로 구분된 인수 리스트를 사용하여 함수를 호출
* @param thisArg - this로 사용할 객체
* @param arg1, arg2, ... - 함수에게 전달할 인수 리스트
* @returns 호출된 함수의 반환값
*/
Function.prototype.call (thisArg[, arg1[, arg2[, ...]]])
  • apply와 call 메서드의 본질적인 기능은 함수를 호출하는 것
function getThisBinding() {
 return this;
}

// this로 사용할 객체
const thisArg = { a: 1 };
console.log(getThisBinding()); // window

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


console.log(getThisBinding.apply(thisArg, [1, 2, 3])); // Arguemnts(3) [1, 2, 3, callee:f, Symbol(Symbol.iterator): f]
console.log(getThisBinding.call(thisArg, 1, 2, 3)); // Arguemnts(3) [1, 2, 3, callee:f, Symbol(Symbol.iterator): f]
  • apply와 call 메서드의 대표적인 용도 arguments 객체와 같은 유사 배열 객체에 배열 메서드를 사용하는 경우
    • arguments 객체는 배열아 아니기 때문에 Array.prototype.slice 같은 배열 메서드를 사용할 수 없으나 apply, call 메서드를 통해 사용가능
function covertArgsToArray() {
  console.log(arguments);
  
  // arguments 객체를 배열로 변환
  // Array.prototype.slice를 인수 없이 호출하면 배열의 복사본을 생성
  const arr = Array.prototype.slice.call(arguments);
  console.log(arr);
  return arr;
}

convertArgsToArray(1, 2, 3); // [1, 2, 3]
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}
  • bind 메서드는 메서드의 this와 메서드 내부의 중첩 함수 또는 콜백 함수가 this가 불일치하는 문제를 해결하기 위해 사용
const person = {
  name: 'Lee',
  foo(callback) {
    setTimeout(callback, 100);
  }
};

person.foo(function () {
  console.log(`Hi! my name is ${this.name}.`); // Hi! my name is .
  // 일반 함수로 호출된 콜백 함수 내부의 this.name은 브라우저 환경에서 windwo.name과 같음
});


const person = {
  name: 'Lee',
  foo(callback) {
    // bind 메서드로 callback 함수 내부의 this 바인딩 전달
    setTimeout(callback.bind(this), 100);
  }
};

person.foo(function () {
  console.log(`Hi! my name is ${this.name}.`); // Hi! my name is Lee
});
함수 호출 방식this 바인딩
일반 함수 호출전역 객체
메서드 호출메서드를 호출한 객체
생성자 함수 호출생성자 함수가 (미래에) 생성할 인스턴스
Function.prototype.apply/call/bind 메서드에 의한 간접 호출Function.prototype.apply/call/bind 메서드에 첫번째 인수로 전달한 객체




📖 참고도서 : 모던 자바스크립트 Deep Dive 자바스크립트의 기본 개념과 동작 원리 / 이웅모 저 | 위키북스

profile
혼신의 힘을 다하다 🤷‍♂️

0개의 댓글