JS Deep Dive - this

이승윤·2022년 12월 9일
3

this 키워드


자신이 속한 객체 또는 자신이 생성할 인스턴스를 가리키는 자기 참조 변수.

this를 통해 자신이 속한 객체 또는 자신이 생성할 인스턴스의 프로퍼티나 메서드를 참조할 수 있다.
this는 자바스크립트 엔진에 의해 암묵적으로 생성되며, 코드 어디서든 참조할 수 있다.

const circle = {
  // 프로퍼티: 객체 고유의 상태 데이터
  radius: 5,
  // 메서드: 상태 데이터를 참조하고 조작하는 동작
  getDiameter() {
    // this는 메서드를 호출한 객체를 가리킨다.
    return 2 * this.radius;
  }
};

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

자바스크립트의 this는 함수가 호출되는 방식에 따라 this에 바인딩될 값, 즉 this 바인딩이 동적으로 결정된다. 또한 strict mode(엄격 모드) 역시 this 바인딩에 영향을 준다.

// this는 어디서든지 참조 가능하다.
// 전역에서 this는 전역 객체 window를 가리킨다.
console.log(this); // window

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

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

함수 호출 방식과 this 바인딩


this 바인딩(this에 바인딩 될 값)은 함수 호출 방식, 즉 함수가 어떻게 호출되었는지에 따라 동적으로 결정된다.

일반 함수 호출

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

function foo() {
  console.log("foo's tihs: ", this); // window
  function bar() {
    console.log("bar's this: ", this); // window
  }
  bar();
}
foo();

메서드 내에서 정의한 중첩 함수도 일반 함수로 호출되면 중첩 함수 내부의 this에는 전역 객체가 바인딩된다.

const obj = {
  value: 100,
  foo() {
    console.log("foo's this: ", this); // {value: 100, foo: f}
    console.log("foo's this.value: ", this.value); // 1
    
    // 메서드 내에서 정의한 중첩 함수
    function bar() {
      console.log("bar's this: ", this); // window
    }
    bar();
  }
};

obj.foo();

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

해결법

var value = 1; // window.value: 1

const obj = {
  value: 100,
  foo() {
    // 콜백 함수에 명시적으로 this를 바인딩한다.
    setTimeout(function() {
      console.log(this.value); // 100
    }.bind(this), 100);
  }
};

obj.foo();

메서드 호출

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;

// getName 메서드를 일반 함수로 호출
console.log(getName()); // ''
// 일반 함수로 호출된 getName 함수 내부의 this.name은 브라우저 환경에서 window.name과 같다.

생성자 함수 호출

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

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

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 메서드는 호출할 함수의 인수를 배열로 묶어 전달한다.
console.log(getThisBinding.apply(thisArg, [1, 2, 3]));
// Arguments(3) [1, 2, 3, callee: f, Symbol(Symbol.iterator): f]
// {a: 1}

// call 메서드는 호출할 함수의 인수를 리스트 형식으로 전달한다.
console.log(getThisBinding.call(thisArg, 1, 2, 3));

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

function getThisBinding() {
  return this;
}

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

// bind 메서드는 첫 번째 인수로 전달한 thisArg로 this 바인딩이 교체된
// getThisBinding 함수를 새롭게 생성해 반환한다.
console.log(getThisBinding.bind(this.Arg)); // getThisBinding
// bind 메서드는 함수를 호출하지는 않으므로 명시적으로 호출해야 한다.
console.log(getThisBinding.bind(thisAr)()); // {a: 1}

This 심화


this는 '일반 함수'와 '화살표 함수'에 따라 다르게 정의된다.
일반 함수는 호출 위치에서 정의되고,
화살표 함수는 this가 자신이 선언된 렉시컬(함수) 범위에서 정의된다.

let newC
let newD

// 객체 리터럴 선언!
const literal = {
  a: 1,
  b: 2,
  // 일반 함수는 호출 위치에서 따라 this 정의!
  c: function () {
    console.log(this.a)
  },
  // 화살표 함수는 this가 자신이 선언된 렉시컬(함수) 범위에서 this 정의!
  d: () => {
    console.log(this.a)
  }
}
literal.c() // 1
literal.d() // undefined
newC = literal.c
newD = literal.d
newC() // undefined
newD() // undefined
newC.call(literal) // 1
newD.call(literal) // undefined
profile
항상 꿈꾸는 개발자

4개의 댓글

comment-user-thumbnail
2022년 12월 10일

오,, this 심화 예시코드까지 잘 정리해주셔서 화살표 함수와 일반함수 차이점에 대해 더 잘 이해할 수 있었어요!!! ㅎㅅㅎ

답글 달기
comment-user-thumbnail
2022년 12월 11일

메서드랑 심화가 정리가 잘 되어 있어서 좋았어요

답글 달기
comment-user-thumbnail
2022년 12월 11일

책에서 렉시컬 범위 어쩌고,,, 써놓은거 이해가 잘 안 갔는데 심화 버전이라고 명명해서 써두니 이해가 잘 되네요! 👏

답글 달기
comment-user-thumbnail
2022년 12월 11일

잘 봤습니다 👍🏼

답글 달기