22장. this

유준상·2022년 2월 8일
1
  • this 키워드

    * 객체: 상태를 나타내는 프로퍼티와 동작을 나타내는 메서드를 하나의 논리적인 단위로 묶은 복합적인 자료구조
    * 동작을 나타내는 메서드는 프로퍼티를 참조하고 변경할 수 있어야 한다.
    --> 자신이 속한 객체를 가리키는 식별자를 참조할 수 있어야 한다.

    const circle = {
       // 프로퍼티: 객체 고유의 상태 데이터
       radius: 5,
       // 메서드: 상태 데이터를 참조하고 조작하는 동작
       getDiameter() {
           return 2 * circle.radius; // 자신이 속한 객체인 circle을 참조
       }
    };
    console.log(circle.getDiameter()); // 10

    --> 재귀적으로 객체를 참조하는 방식은 일반적이지 않다.
    * 생성자 함수 방식으로 인스턴스 생성하는 경우

    function Circle(radius) {
       ????.radius = radius;
    }
    Circle.prototype.getDiameter = function () {
       return 2 * ????.radius;
    }
    // 생성자 함수로 인스턴스를 생성하려면 먼저 생성자 함수를 정의해야 한다.
    const circle = new Circle(5);

    --> 생성자 함수가 먼저 존재해야 한다. 생성자 함수를 정의하는 시점에는 아직 인스턴스를 생성하기 이전이므로 생성자 함수가 생성할 인스턴스를 가리킬 식별자를 알 수 없다!! -> this : 자신이 속한 객체, 자신이 생성할 인스턴스를 가리키는 식별자 (자기 참조 변수)
    * this 바인딩은 함수 호출 방식에 의해 동적으로 결정
    --> 바인딩 : 식별자와 값을 연결하는 과정

    1. 객체 리터럴

    const circle = {
       radius: 5,
       getDiameter() {
           return 2 * this.radius; // this는 메서드를 호출한 객체
       }
    };
    console.log(circle.getDiameter()); // 10

    --> this는 메서드를 호출한 객체 : circle

    2. 생성자 함수

    // 생성자 함수
    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는 생성자 함수가 생성할 인스턴스
    --> 상황에 따라 바인딩이 다르게 됨. (동적으로 결정)

    3. this 참조

    1) 전역 -> window
    2) 일반 함수 내부 -> window (일반함수에서는 의미 x)
    3) 메서드 내부 -> 메서드를 호출한 객체
    4) 생성자 함수 내부 -> 생성자 함수가 생성할 인스턴스

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

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

    렉시컬 스코프 vs this 바인딩

    1) 렉시컬 스코프: 함수 정의가 펴악되어 함수 객체가 생성되는 시점
    2) this 바인딩: 함수 호출 시점에 결정

    1. 일반 함수 호출

    --> this에 전역 객체가 바인딩된다.
    * 일반 함수에서의 this는 의미가 없다. strict mode 에서는 일반 함수에서 this 에 undefined를 바인딩된다.
    * 메서드 내에서 정의한 중첩 함수도 일반 함수로 호출되면 중첩 함수 내부의 this에는 전역 객체가 바인딩된다.
    어떠한 함수라도 일반 함수로 호출되면 this에 전역 객체가 바인딩된다!!
    * 메서드 내부의 중첩 함수나 콜백 함수의 this 바인딩을 메서드의 this 바인딩과 일치시키기 위하여 that 키워드, apply, call, bind 메서드 사용
    * 화살표 함수를 사용해서 this 바인딩을 일치시킬 수도 있다.
    --> 화살표 함수 내부의 this는 상위 스코프의 this를 가리킨다.

    2. 메서드 호출

    this는 메서드를 소유한 객체가 아닌 메서드를 호출한 객체에 바인딩

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

    --> getName 메서드는 객체에 포함된 것이 아니라 독립적으로 존재하는 별도의 객체이다.
    --> getName 메서드는 다른 객체의 메서드가 될 수도 있고 일반 변수에 할당하여 일반 함수로 호출될 수도 있다.
    --> ** 메서드 내부의 this는 프로퍼티로 메서드를 가리키고 있는 객체와는 관계가 없고, 메서드를 호출한 객체에 바인딩 (프로토타입 메서드도 마찬가지)

    // 생성자 함수
    function Person(name) {
       this.name = name;
    }
    // 프로토타입 메서드
    Person.prototype.getName = function () {
       return this.name;
    };
    // 생성자 함수로 객체 생성
    const me = new Person('Lee');
    // 1번.
    console.log(me.getName()); // this는 me에 바인딩 (me가 메서드 호출함)
    // 2번.
    Person.prototype.name = 'Kim';
    console.log(Person.prototype.getName()); // this는 Person.prototype에 바인딩 Kim

    --> this에 바인딩될 객체는 호출 시점에 결정 !!

    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(circle2.getDiameter()); // 20

    --> 생성자 함수는 객체 (인스턴스) 를 생성하지 않으면 일반 함수로 동작!!

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

    --> 이들 함수는 모든 함수가 상속받아 사용이 가능하다.
    1) apply, call 메서드는 this로 사용할 객체와 인수 리스트를 인수로 전달받아 함수를 호출

    function getThisBinding() {
       return this;
    }
    // this로 사용할 객체
    const thisArg = { a: 1 };
    console.log(getThisBinding()); // window
    // 함수 호출 + 인수로 전달한 객체를 함수의 this에 바인딩
    console.log(getThisBinding.apply(thisArg)); // { a : 1 }
    console.log(getThisBinding.call(thisArg)); // { a : 1 }

    * apply, call 메서드의 본질적인 기능은 함수를 호출하는 것

    apply vs call

    * apply 메서드는 호출할 함수의 인수를 배열로 묶어 전달
    * call 메서드는 호출할 함수의 인수를 쉼표로 구분한 리스트 형식으로 전달
    --> 함수를 호출하는 것은 동일하다

    function getThisBinding() {
       console.log(arguments);
       return this;
    }
    // this로 사용할 객체
    const thisArg = { a : 1 };
    // apply
    console.log(getThisBinding.apply(thisArg, [1,2,3]));
    // --> thisArg가 this의 역할, 1,2,3 배열로 묶어 인수 전달
    //call
    console.log(getThisBinding.call(thisArg, 1, 2, 3));
    // --> thisArg가 this의 역할, 1,2,3 쉼표 리스트로 인수 전달
    // 두 메서드 모두 함수를 호출하므로 결과값은 { a : 1 } 이다.

    2) bind 메서드는 함수를 호출하지 않고 this로 사용할 객체만 전달

    function getThisBinding() {
       return this;
    }
    // this로 사용할 객체
    const thisArg = { a : 1 };
    // bind 메서드는 함수에 this로 사용할 객체 전달, 호출은 x
    console.log(getThisBinding.bind(thisArg)); // getThisBinding
    // 명시적 호출이 필요하다.
    console.log(getThisBinding.bind(thisArg)()); // { a : 1 }

    --> 메서드의 this와 메서드 내부의 중첩 함수, 콜백 함수의 this가 불일치하는 문제 해결

    const person = {
       name: 'Lee',
       foo(callback) {
           setTimeout(callback.bind(this), 100);
       }
    };
    person.foo(function () {
       console.log(`Hi! my name is ${this.name}.`);
    }); // 일반 함수 내부의 this가 전역이 아닌 person을 가리킨다.
  • 정리

    일반 함수 호출 --> 전역 객체에 바인딩
    메서드 호출 --> 메서드를 호출한 객체에 바인딩
    생성자 함수 --> 생성자 함수가 생성할 인스턴스에 바인딩
    Function.prototype.apply/call/bind --> 메서드에 첫 번째 인수로 전달한 객체

profile
웹사이트 개발과 신발을 좋아하는 학생입니다.

0개의 댓글

Powered by GraphCDN, the GraphQL CDN