코어 자바스크립트 - this

Hwang Tae Young·2023년 2월 24일
0

✅ this 란?

  • 대부분의 객체지향 언어에서는 class로 생성한 인스턴스 객체를 의미합니다.
  • class에서만 사용하기 때문에 혼란의 여지가 적지만, 자바스크립트에서의 this는 어디서든 사용 가능합니다.
  • 그렇기 때문에, 상황에 따라서 this가 바라보는 곳이 달라지는 이유를 파악하기 힘든 경우도 있고, 의도하지 않는 곳을 바라보는 경우도 생깁니다.

✅ 상황에 따라 달라지는 this

  • this는 기본적으로 실행 컨텍스트가 생성될 때 함께 결정됩니다. 실행 컨텍스트는 함수를 실행할때 발생함으로 즉, this는 함수를 호출할때 결정된다고 볼 수 있습니다.
  • 또한 함수를 어떻게 호출하냐에 따라 this가 바라보는 바인딩할 객체가 동적으로 결정됩니다.
  • 기본적으로 this는 전역객체(Global object)에 바인딩된다. 전역함수는 물론이고 심지어 내부함수의 경우도 this는 외부함수가 아닌 전역객체에 바인딩된다.
    • 전역객체에 바인딩이 되는 이유는, 함수로서 호출하는 것은 호출 주체(객체지향 언어에서의 객체)를 명시하지 않고 개발자가 직접 코드를 실행한 것이기 때문에 호출 주체를 알 수 없어서 전역객체에 바인딩이 되는 것이다.

1. 전역공간에서의 this

  • 기본적으로 this는 전역객체에 바인딩 된다고 했고, 아래의 예시는 브라우저와 Node.js환경일 때 전역객체와 this다.

  • 브라우저 환경

console.log(this); // { alert: f(), atob: f(), blur: f(), btoa: f(), ... }
console.log(window); // { alert: f(), atob: f(), blur: f(), btoa: f(), ... }
console.log(this === window); // true

  • Node.js 환경
console.log(this); // { process: { title: 'node', version: 'v10.13.0',... } }
console.log(global); // { process: { title: 'node', version: 'v10.13.0',... } }
console.log(this === global); // true

1-2. 전역 변수와 전역객체

var a = 1;
console.log(a); // 1
console.log(window.a); // 1
console.log(this.a); // 1
  • var 키워드로 선언한 전역 변수는 전역 객체(Global Object) window의 프로퍼티기 때문에 위와같은 결과가 나온다.
let a = 1;
console.log(a); // 1
console.log(window.a); // undefined
console.log(this.a); // undefined
  • let 혹은 const로 했을 경우는 위와같이 나온다.

2. 함수로 호출할때와 메서드로서 호출할때

  • 함수를 실행할때는, 여러가지 방법이 있는데 그중 가장 일반적인 방법 두가지는 함수로 호출하는 경우와 메서드로서 호출하는 경우이고, 이 둘을 구분하는 유일한 차이는 독립성입니다,

  • 독립성

    • 함수는 그 자체로 독립적인 기능을 수행하는 반면, 메서드자신을 호출한 대상 객체에 관한 동작을 수행합니다.

      함수는 메서드를 포함하고있는 개념이고, 독립성이라는 것을 쉽게 말하자면 함수는 직접 호출 가능하고, 메서드는 객체 안에서 호출을 하는 한단계 이상 거쳐서 호출할 수 있다고 생각하면 좋을 것 같다.

        function fn() {
          console.log("함수");
        }
    
        const obj = {
          method: function () {
            console.log("메서드");
          },
        };
    
        fn();
        obj.method();
    
  • 함수와 메서드 호출

      var func = function (x) {
        console.log(this, x);
      };
      func(1); // Window { ... } 1

      var obj = {
        method: func,
      };
      obj.method(2); // { method: f } 2
  • 함수에는 호출하는 객체가 없기 때문에 전역객체가, 메서드 호출에서는 호출 객체가 있기 때문에 그 객체 자체가 출력된다.

그렇다면 아까 내부함수에서도 this가 전역객체로 바인딩이 된다 했는데, 메서드 안의 내부함수는 어떻게 동작할까?

var obj1 = {
  outer: function() {
    console.log(this); // (1)
    var innerFunc = function() {
      console.log(this); // (2) (3)
    };
    innerFunc();

    var obj2 = {
      innerMethod: innerFunc,
    };
    obj2.innerMethod();
  },
};
obj1.outer();
  • 결과부터 말하자면, 1: obj1 , 2: 전역객체 3: obj2가 나온다. 메서드의 내부함수 역시 호출하는 객체가 명시되있지 않기 때문에 이런 결과가 나온다.
  • 그렇다면 메서드의 내부 함수에서 this를 사용해야 할때는 어떻게 하면 좋을까?
var obj = {
  outer: function() {
    console.log(this); // (1) { outer: f }
    var innerFunc1 = function() {
      console.log(this); // (2) Window { ... }
    };
    innerFunc1();

    var self = this;
    var innerFunc2 = function() {
      console.log(self); // (3) { outer: f }
    };
    innerFunc2();
  },
};
obj.outer();
  • 위와 같은 방법으로 해주면 된다. 변수명도 꼭 self로 할 필요는 없고, 구성원들이 협의된 단어를 사용하면 된다.
  • ES6이후로는 this바인딩을 하지 않는 화살표 함수가 생겨서 위와같이 할 필요 없이 아래와 같이 하면 된다.
var obj = {
  outer: function() {
    console.log(this); // (1) { outer: f }
    var innerFunc = () => {
      console.log(this); // (2) { outer: f }
    };
    innerFunc();
  },
};
obj.outer();

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

setTimeout(function() {
  console.log(this);
}, 300); // (1)

[1, 2, 3, 4, 5].forEach(function(x) {
  // (2)
  console.log(this, x);
});

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

  • 결과는 setTimeout와 forEach는 콜백 함수를 호출 할때 대상이 될 this를 지정하지 않습니다. 즉 객체가 명시되어있지 않기 때문에, 전역객체가 바인딩이 되는 결과가 나왔고, addEventListener은 호출되는 객체가 document.body.querySelector('#a')로 명시되어 있기 때문에, 쉽게 말하면 메서드명의 점(.) 부분이 호출하는 객체인 것이므로 button 태그가 출력된다.

4. 생성자 함수 내부에서의 this

  • 생성자 함수는 말 그대로 객체를 생성하는데 사용하는 함수입니다.
var Cat = function(name, age) {
  this.bark = '야옹';
  this.name = name;
  this.age = age;
};
var choco = new Cat('초코', 7);
var nabi = new Cat('나비', 5);
console.log(choco, nabi);

/* 결과
Cat { bark: '야옹', name: '초코', age: 7 }
Cat { bark: '야옹', name: '나비', age: 5 }
*/
  • 이렇게 new 명령어와 함수를 호출하게 되면 함수는 생성자 함수로서 호출되게 되고, 함수 자체가 호출한 객체가 되어서 this는 생성된 객체를 의미합니다.

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

  • 앞서 봤던 것들은, 암시적으로 바인딩이 동작했습니다. 명시적으로 바인딩을 하는 방법을 알아보겠습니다.
  • 명시적 바인딩 방법은 아래의 3가지 메서드를 이용한 호출이다.
    • apply, call, bind
    • 이 메서드들은 Function.prototype의 메서드로서 모든 함수가 상속받아 사용 가능합니다.
  • 이중 call, apply는 this로 사용할 객체를 전달해주고 함수를 호출해준다는 것에서 비슷한 점이 있습니다.
  • Function.prototype.call(thisArg, argsArray)
  • Function.prototype.apply(thisArg, arg1, arg2)
      const func = function (a, b, c) {
        console.log(this, a, b, c);
      };
      func.call({ this: "this" }, 1, 2, 3);
      func.apply({ this: "this" }, [1, 2, 3]);
  • bind의 경우는 함수를 호출해 주지 않기 때문에, 직접 호출해야한다.
  • Function.prototype.bind(thisArg, arg1, arg2)
      const func = function (a, b, c) {
        console.log(this, a, b, c);
      };
      func.call({ this: "this" }, 1, 2, 3); // {this: 'this'} 1 2 3
      func.apply({ this: "this" }, [1, 2, 3]); // {this: 'this'} 1 2 3
      func.bind({ this: "this" }, [1, 2, 3]); // 함수 실행이 안되어있음
      func.bind({ this: "this" }, 1, 2, 3)(); // {this: 'this'} 1 2 3

6. 바인딩의 우선순위는?

  • 결과를 먼저 알려드리자면
    1. new 생성자로 만든 함수
    2. 명시적 바인딩
    3. 암시적 바인딩
    4. 기본
      const obj = {
        name: "JavaScript",
        func: function () {
          console.log(this);
        },
      };

      obj.func(); //  {name: 'JavaScript', func: ƒ}
      obj.func.call("this"); // String {'this'}

      const ObjFunc = function (name) {
        this.name = name;
      };

      const objFunc = new ObjFunc("JavaScript");
      console.log(objFunc); // ObjFunc {name: 'JavaScript'}

      const bindObjFunc = ObjFunc.bind("typeScript");
      console.log(bindObjFunc("typeScript"));// new 로 생성하지 않으면, 아예 찾지 못하는 듯

      const ts = new bindObjFunc("JavaScript"); // ObjFunc {name: 'JavaScript'}
      console.log(ts); //ObjFunc {name: 'JavaScript'}
  • 이런 결과가 나오게 됩니다.

현재 이해한 것 까지 마무리

  • this는 기본적으로 전역객체로 바인딩이 된다.
  • this 바인딩은 함수를 호출할 때, 결정된다.
  • 함수 자체를 호출 할때는, 기본적인 방식(전역객체)으로 바인딩 된다.
  • 객체 안의 내부 함수에서도 호출 할때, 함수 자체를 호출하면 기본적인 방식(전역객체)으로 바인딩된다.
    • 그렇기에 내부 함수에서는 화살표 함수를 쓴다면 이점이 있을 수 있다.
  • 메서드를 사용할 경우, 쉽게 점(.)앞에 사용하면 그 객체를 바인딩 한다.
  • 화살표 함수는 바인딩을 하지 않는다. 그렇기 때문에 this는 상위 스코프를 가르킨다.

참고 : 코어자바스크립트
참고 : [10분 테코톡] 하리의 this youTube 영상

profile
더 나은 개발자가 되기 위해...☆

0개의 댓글