뮁이의 개발새발

[코어자바스크립트] Chapter 3. this 본문

Front-end/Javascript

[코어자바스크립트] Chapter 3. this

뮁뮁이 2024. 4. 10. 14:37

Chapter 3. this

this란?

  • 자기 자신이 속한 객체를 가리키는 식별자를 참조할 수 있는 키워드
  • 메서드의 주인이라고 생각하면 편함
  • JS에서는 함수와 객체의 구분이 느슨하기 때문에, 이 둘을 구분하는 거의 유일한 기능임.

상황에 따라 달라지는 this

  • this는 기본적으로 실행 컨텍스트가 생성될 때 함께 결정됨
    • 실행 컨텍스트는 함수를 호출할 때 생성됨
    • this는 함수가 호출될 때 결정됨

전역 공간에서의 this

  • 전역 객체를 가르킴
    • 브라우저 - window / Node.js - global
  • 전역변수를 선언하면 자바스크립트 엔진은 이를 전역객체의 프로퍼티로도 할당함
var a = 1;
console.log(a); // 1
console.log(window.a) // 1
console.log(this.a); // 1
  • 자바스크립트의 모든 변수는 특정 객체의 프로퍼티로서 동작함.
  • 어떤 특정 객체의 프로퍼티로 인식하게 됨
    • 전역 컨텍스트의 경우 실행 컨텍스트인 LexicalEnvironment는 전역객체를 그대로 참조함.
    • 전역변수를 선언하면 자바스크립트 엔진은 이를 전역객체의 프로퍼티로 할당한다.
  • 다만, delete 명령에서는 달라질 수 있음
    • window.a로 선언한 변수는 delete 가능 (프로퍼티로 할당)
    • var a 로 선언한 변수는 delete 불가능 (전역변수로 할당)

함수 vs 메서드

  • 독립성에서의 차이가 있음
  • 함수
    • 그 자체로 독립적인 기능을 수행
    • var func = function() {}
  • 메서드
    • 자신을 호출한 대상 객체애 관한 동작을 수행
    • var obj = { method : func }

함수로서 호출과 메서드로서의 호출을 구분하는 방법

  • 함수 앞에 점이 있으면 메서드로서의 호출
  • 점이 없으면 함수로서의 호출
  • 점 표기법 (obj.method(x)) / 대괄호 표기법(obj’method’) 모두 가능

메서드 내부에서의 this

  • 어떤 함수를 메서드로서 호출하는 경우 호출 주체는 바로 함수명(프로퍼티명) 앞의 객체가 됨.
  • 점 표기법의 경우 마지막 점 앞에 명시된 객체가 곧 this가 됨
var obj = {
  methodA: function () {console.log(this);},
  inner: {
      methodB: function () {console.log(this);}
    }
};

obj.methodA(); // == obj
obj.inner.methodB(); // == obj.inner

함수로서 호출할 때 그 함수 내부에서의 this

  • this가 지정되지 않은 경우 this는 전역 객체를 바라봄.
  • 이는 설계상의 오류
    • 호출 주체가 없을때는 자동으로 전역객체가 아닌 호출 당시 주변 환경의 this를 그대로 상속받아 사용할 수 있기를 바람 (그것에 스코프 체인과의 일관성을 지키는 것처럼 체감되기 때문)
    • 현재 컨텍스트에 바인딩된 대상이 없으면 직전 컨텍스트의 this를 바라보게끔 하면 좋겠음.

내부 함수에서 this를 우회하는 방법

  1. 변수를 활용한다.

     var self = this;
     function innerFunction() {
         console.log(self); // == innerFunction
     }
     innerFunction();
  2. 화살표 함수를 사용한다.

     function outerFunction() {
         var innerFunction = () => {
             console.log(this); // innerFunction
         }
         innerFunction();
     }
    • 화살표 함수 이용 시, 하단과 같이 사용 필요

      document.body.innerHTML += '<button id="a">클릭</button>';
      document.body.querySelector('#a').addEventListener('click', 
      function(e) {
            (()=>{
                    console.log(this);
                    } 
            )();
          console.log(this, e);
        }
      );

콜백 함수 호출 시 그 함수 내부에서의 this

  • 함수 A의 제어권을 다른 함수(또는 메서드) B에게 넘겨주는 경우 함수 A를 콜백함수라고 함
  • 제어권을 받은 함수에서는 콜백 함수에 별도로 this가 될 대상을 지정한 경우, 그 대상을 참조하게 됨.

생성자 함수 내부에서의 this

  • 생성자 함수 ( = 클래스 )
    • 어떤 공통된 성질을 지니는 객체들을 생성하는데 사용하는 함수
    • 구체적인 인스턴스를 만들기 위한 틀
    • new 명령어와 함께 함수를 호출하면 생성자로서 동작하게 됨.
      • 내부에서의 this는 곧 새로 만들 구체적인 인스턴스 자신이 됨.
    • ex) 인간
  • 인스턴스
    • 클래스를 통해 만든 객체
    • ex) 직립보행, 언어구사, 도구사용
// 클래스
var Cat = function (name, age){
    this.bark = '야옹';
    this.name = name;
    this.age = age;
}
// 인스턴스
var choco = new Cat('초코',7); // Cat 함수에서의 this는 choco가 됨
var nabi = new Cat('나비', 5);

명시적으로 this를 바인딩하는 방법

// apply 메서드 사용 예시
function.apply(thisArg, [argsArray])

// bind 메서드 사용 예시
function.bind(thisArg, param1, param2, ...)

// 콜백 함수 내에서의 this 지정 예시
callback.call(thisArg, arg1, arg2, ...)

call 메서드와 apply 메서드

  1. call 메서드

    1. Function.prototype.call(thisArg[,arg1[,arg2[,…]]])

    2. 메서드의 호출 주체인 함수를 즉시 실행하도록 하는 명령

    3. 첫번째 인자를 this로 바인딩하고, 이후의 인자들을 호출할 함수의 매개변수로 함.

    4. 그냥 실행하면 this는 전역객체를 참조하지만, call 메서드를 이용하면 임의의 객체를 this로 지정할 수 있음

      var func = function (a,b,c){
       console.log(this, a,b,c);
      }
      func(1,2,3);
      func.call({x:1},4,5,6); // call 메서드를 이용하면, 임의의 객체를 this로 지정할 수 있음
  2. apply 메서드

    1. Funciton.prototype.apply(thisArg[, argsArray])

    2. call 메서드와 기능적으로 완전히 동일함.

    3. 두 번째의 인자를 배열로 받아, 그 배열의 요소들을 호출할 함수의 매개변수로 지정함.

      var func = function (a,b,c){
       console.log(this, a, b, c);
      }
      func.apply({x:1},[4,5,6]);

call/apply 메서드의 활용

  • 유사배열객체에 배열 메서드를 적용

    • 키가 0 또는 양의 정수인 프로퍼티가 존재하고, length 프로퍼티의 값이 0 또는 양의 정수인 객체, 즉 배열의 구조와 유사한 객체의 경우(유사배열객체) call 또는 apply 메서드를 이용해 배열 메서드를 차용할 수 있음

      var obj = {
        0: 'a',
        1: 'b',
        2: 'c',
        length: 3
      }
      Array.prototype.push.call(obj,'d);
      onsole.log(obj);
    • 단, 문자열의 경우 length 프로퍼티가 읽기 전용이기 때문에, 문자열에 변경을 가하는 메서드는 에러를 던짐.

      • ES6에서는 유사배열객체의 데이터타입을 배열로 전환하는 Array.from 메서드를 도입함
  • 생성자 내부에서 다른 생성자를 호출

    • 생성자 내부에 다른 생성자와 공통된 내용이 있을 경우 call 또는 apply를 이용해 다른 생성자를 호출하면 반복을 줄일 수 있음.

      function Person(name, gender){
        this.name = name;
        this.gender = gender;
      }
      function Student(name,gender,school){
        Person.call(this,name,gender);
        this.school = school;
      }
      
      var by = new Student('민정', 'female','서울대');
  • 여러 인수를 묶어 하나의 배열로 전달하고 싶을 때 - apply 활용

    • 여러 개의 인수를 받는 메서드에게 하나의 배열로 인수들을 전달하고 싶을때는 apply 메서드를 사용

      var numbers = [10,20,3,16,45];
      var max = Math.max.apply(null, numbers);
      var min = Math.min.apply(null, numbers);
      
      // ES6에서 도입된 펼치기 연산자 활용 시
      const max2 = Math.max(...numbers);
      const min2 = Math.min(...numbers);
  1. bind 메서드

    1. call과 비슷하지만, 즉시 호출하지는 않고 넘겨받은 this 및 인수들을 바탕으로 새로운 함수를 반환하기만 하는 메서드

    2. 함수에 this를 미리 적용하는 것과 부분 적용 함수를 구현하는 두가지 목적을 모두 지님

      var func = function(a, b, c, d) {
      console.log(this, a, b, c, d);
      };
      func(1, 2, 3, 4); // Window{ ... } 1 2 3 4
      
      var bindFunc1 = func.bind({ x: 1 }); // {x:1}을 func의 this로 지정?
      bindFunc1(5, 6, 7, 8); // { x: 1 } 5 6 7 8
      
      var bindFunc2 = func.bind({ x: 1 }, 4, 5);
      bindFunc2(6, 7); // { x: 1 } 4 5 6 7
      bindFunc2(8, 9); // { x: 1 } 4 5 8 9

      c. name 프로퍼티

    • name 프로퍼티에 bound라는 접두어가 붙음

    • 기존의 call이나 apply보다 코드를 추적하기에 더 수월함

      상위 컨텍스트의 this를 내부함수나 콜백함수에 전달하기

    • call, apply, bind를 활용하면 편리

      // call
      var obj = {
      outer: function() {
        console.log(this);
        var innerFunc = function() {
          console.log(this);
        };
        innerFunc.call(this);
      },
      };
      obj.outer();
      // bind
      var obj = {
      outer: function() {
        console.log(this);
        var innerFunc = function() {
          console.log(this);
        }.bind(this);
        innerFunc();
      },
      };
      obj.outer();
    • 콜백함수를 인자로 받는 함수나 메서드 중에서 콜백함수 내에서의 this에 관여하는 함수/메서드에 대해서도 bind를 이용하면 this의 값을 지정할 수 있음

      var obj = {
      logThis: function() {
        console.log(this);
      },
      logThisLater1: function() {
        setTimeout(this.logThis, 500);
      },
      logThisLater2: function() {
        setTimeout(this.logThis.bind(this), 1000);
      },
      };
      obj.logThisLater1(); // Window { ... }
      obj.logThisLater2(); // obj { logThis: f, ... }
  • 화살표 함수의 예외사항
    • ES6에서의 화살표 함수는, this를 바인딩하는 과정이 제외되었음
    • 접근하고자 하면 스코프체인상 가장 가까운 this에 접근하게 됨.
  1. 별도의 인자로 this를 받는 경우(콜백 함수 내에서의 this)

    1. 콜백함수를 인자로 받는 메서드 중 일부는 추가로 this로 지정할 객체(thisArg)를 인자로 지정할 수 있는 경우가 있음

    2. 메서드의 thisArg값을 지정하면 콜백 함수 내부에서 this값을 원하는대로 변경할 수 있음

    3. 배열 메서드에서 많이 포진되어 있음.

      var report = {
      sum: 0,
      count: 0,
      add: function() {
       var args = Array.prototype.slice.call(arguments);
       args.forEach(function(entry) {
         this.sum += entry;
         ++this.count;
       }, this); // 여기서 this를 report로 바인딩
      },
      average: function() {
       return this.sum / this.count;
      },
      };
      report.add(60, 85, 95);
      console.log(report.sum, report.count, report.average()); // 240 3 80

      정리

    • 묵시적 this 바인딩
      • 전역공간: 전역객체
      • 함수를 메서드로 호출: 호출 주체
      • 함수를 함수로 호출: 전역 객체 (메서드 내부 함수도 똑같다)
      • 함수를 생성자로 호출: 생성될 인스턴스
      • 콜백 내부 함수: 제어권을 넘겨받은 함수가 정의한 바에 따름
    • 명시적 this 바인딩
      • call, apply 메서드: this/인자를 지정하면서 호출
      • bind: this/인자를 지정하면서 새로운 함수 생성
      • 순회 메서드: 별도 인자로 this를 받음
        • forEach, map, filter 등등
Comments