본문 바로가기
Dev/JavaScript

[JavaScript] 객체 응용 - this

by 5kdk 2022. 11. 13.

 들어가며

자바스크립트 객체의 this, 생성자 함수, 프로토타입을 살펴본다.

다행히도 ES6에서 자바스크립트의 Class 문법이 추가되었다. 하지만, 자바스크립트의 Class 문법은 Prototype 기반으로 구현된 자바스크립트의 문법적 편의 기능일 뿐 엔진 내부적으로는 prototype 방식으로 작동되기 때문에, Class를 활용하더라도 this, 생성자 함수, 프로토타입의 개념과 작동방식을 짚고 넘어가 보려 한다.

 

앞서, 이번 포스팅은 this 를 다룬다. 자바스크립트의 this개념과 특징을 이해하고 정리하는데에 모던 자바스크립트 deepdive의 저자 이웅모님의 poiemaweb 블로그에서 가장 많이 도움 받았다. 보다 전문적인 지식을 얻으려면 해당 블로그를 꼭 가보길 추천한다.(링크 하단 참고)

 

 

 


👀 This 란?

 대부분의 메서드는 객체에 저장된 정보에 접근할 수 있어야 제 역할을 수행한다. 객체 내부의 메서드가 객체에 저장된 정보를 사용하기 위해 사용하는 것이 'this'다. 

 

 

💡 this 예시

let user = {
  name: "john",
  age: 30,

  sayHi() {
    // 'this'는 '현재 객체'를 나타냄
    console.log(this.name);
  }

};

user.sayHi(); // john

 

그런데.. 자바스크립트는 다른 언어와 달리 함수 호출 방식에 의해  this 가리키는 곳이 동적으로 결정된다.

이 부분이 굉장히 골치 아프다. 상황별로 살펴보자.

 

 

 


전역에서의 this

최상위에서의 thisECMA 5.1에서 추가된 엄격 모드(strict mode) 여부에 관계없이 전역 객체(Global object)를 참조(이하 바인딩)한다.(Browser에서는 window, Server에서는 global)

 

 

💡 전역 문맥

// 웹 브라우저에서는 window 객체가 전역 객체
console.log(this === window); // true

a = 37;
console.log(window.a); // 37

this.b = "kim";
console.log(window.b)  // "kim"
console.log(b)         // "kim"

 

 

 


  함수 호출에서의 this

함수 내부에서 this의 값은 함수를 호출한 방법에 의해 좌우된다.

 

 

💡 전역 함수의 경우

전역에 선언된 함수의 this는 전역 객체에 바인딩된다.

단,엄격 모드에서는 this의 default 바인딩이 없기 때문에 undefined가 할당

function myFunc() {
  return this;
}
console.log(myFunc()); //Window


const myFunc2 = () => {
    return this;
}
console.log(myFunc2()); // Window


function myFunc3() {
    "use strict"; // 엄격모드
    return this;
}
console.log(myFunc3()); // undefined

 

 

💡 메서드의 내부 함수의 경우

전역 함수는 물론이고 심지어 내부 함수의 경우도 this는 외부 함수가 아닌 전역 객체에 바인딩된다.

function myFunc1() {
  console.log("myFunc1's this: ",  this);  // window
  function myFunc2() {
    console.log("myFunc2's this: ", this); // window
  }
  myFunc2();
}
myFunc1();

 

 

💡  콜백 함수의 경우

콜백 함수의 경우에도 this는 전역 객체에 바인딩된다.(???)

var value = 1;

var obj = {
  value: 100,
  myFunc1: function() {
    console.log("myFunc1's this: ",  this);  // obj
    console.log("myFunc1's this.value: ",  this.value); // 100
    function myFunc2() {
      console.log("myFunc2's this: ",  this); // window
      console.log("myFunc2's this.value: ", this.value); // 1
    }
    myFunc2();
  }
};

obj.myFunc1();

 

 


this의 참조 회피

앞서 본 내부 함수는 일반 함수, 메서드, 콜백 함수 어디에서 선언되었든 관계없이 this는 전역 객체를 바인딩한다. 더글라스 크락 포드는 “이것은 설계 단계의 결함으로 메서드가 내부 함수를 사용하여 자신의 작업을 돕게 할 수 없다는 것을 의미한다”라고 말했다. 다행히도 이 '결함'을 회피하는 방법이 있다.

 

 

💡  내부 함수 this 참조 회피 - 변수사용

변수 that에 this를 할당하여 참조하는 방법.

var value = 1;

var obj = {
  value: 100,
  foo: function() {
    var that = this;  // Workaround : this === obj

    console.log("foo's this: ",  this);  // obj
    console.log("foo's this.value: ",  this.value); // 100
    function bar() {
      console.log("bar's this: ",  this); // window
      console.log("bar's this.value: ", this.value); // 1

      console.log("bar's that: ",  that); // obj
      console.log("bar's that.value: ", that.value); // 100
    }
    bar();
  }
};

obj.foo();

 

 

 

💡  내부 함수 this 참조 회피 - 메서드 사용

변수 선언 방법 이외에도 자바스크립트는 this를 명시적으로 바인딩할 수 있는 apply, call, bind 메서드가 있다.

var value = 1;

var obj = {
  value: 100,
  foo: function() {
    console.log("foo's this: ",  this);  // obj
    console.log("foo's this.value: ",  this.value); // 100
    function bar(a, b) {
      console.log("bar's this: ",  this); // obj
      console.log("bar's this.value: ", this.value); // 100
      console.log("bar's arguments: ", arguments);
    }
    bar.apply(obj, [1, 2]);
    bar.call(obj, 1, 2);
    bar.bind(obj)(1, 2);
  }
};

obj.foo();

 

 


  메서드 호출에서의 this

함수가 객체의 프로퍼티 값이면 메서드로서 호출된다. 이때 메서드 내부의 this는 해당 메서드를 소유한 객체, 즉 해당 메서드를 호출한 객체에 바인딩된다.

 

 

💡  객체 메서드의 경우

내부 코드에서 사용된 this는 해당 메서드를 호출한 객체로 바인딩

var obj1 = {
  name: 'Lee',
  sayName: function() {
    console.log(this.name);
  }
}

var obj2 = {
  name: 'Kim'
}

obj2.sayName = obj1.sayName;

obj1.sayName(); // lee
obj2.sayName(); // kim

 

 

 

 

💡  프로토타입 객체 메서드의 경우

일반 메서드와 같이 this는 해당 메서드를 호출한 객체로 바인딩

function Person(name) {
  this.name = name;
}

Person.prototype.getName = function() {
  return this.name;
}

var me = new Person('Lee');
console.log(me.getName()); // Lee

Person.prototype.name = 'Kim';
console.log(Person.prototype.getName()); // Kim

 

 

 

 

 


DOM 이벤트 핸들러에서의 this

이벤트 헨들러에서 this는 이벤트를 일으킨 DOM 요소로 바인딩된다.

var btn = document.querySelector('#btn')
btn.addEventListener('click', function () {
  console.log(this); //#btn
});

 

 


생성자 함수에서의 this

생성자 함수가 생성하는 인스턴스로 this가 바인딩된다. new를 붙이지 않으면 this는 일반 함수처럼 작동한다.

function Person(name) {
  this.name = name;
}

var me = new Person('Lee');
console.log(me); // Person {name: "Lee"}

var you = Person('Kim');
console.log(you); // undefined

 

 

 


this 바인딩을 제공하지 않는 화살표 함수

화살표 함수는 일반 함수와는 달리 고유한 this를 가지지 않는다. 이를 Lexical this라고 한다고 한다.

화살표 함수에서 this를 참조하고자 하면, 화살표 함수가 아닌 평범한 외부 함수에서 this 값을 가져온다. 

간단히 말하면 바로 바깥 객체.

 

 

💡 화살표 함수

this.date는 바깥 객체 products의 date.

const products = {
  date: new Date('2022-07-16T00:00:00'),
  names: ["가지", "오이", "파프리카", "당근"],
  buyAll : function () {
  // this -> products
    const buy = (name) => {
      // this x
      console.log(`${this.date.toLocaleString()}에 ${name}을 구매합니다.`);
    }
    this.names.forEach((name) => {
      buy(name); // no Error -> this가 원래 없음 -> 상위 this인 products 할당
    })
  }
};

products.buyAll();

// 2022. 7. 16. 오전 12:00:00에 가지을 구매합니다.
// 2022. 7. 16. 오전 12:00:00에 오이을 구매합니다.
// 2022. 7. 16. 오전 12:00:00에 파프리카을 구매합니다.
// 2022. 7. 16. 오전 12:00:00에 당근을 구매합니다.

 

 

💡 일반 함수

일반 함수는 내부에 this {}를 가지고 있어, buy2 함수에 this가 정의되지 않았기 때문에 error를 만든다.

const products = {
  date: new Date('2022-07-16T00:00:00'),
  names: ["가지", "오이", "파프리카", "당근"],
  buyAll : function () {
    const buy2 = function (name) {
      // this = {}   this 정의 되지 않음
      console.log(`${this.date.toLocaleString()}에 ${name}을 구매합니다.`);
    }
    this.names.forEach((name) => {
      buy2(name); // Error -> 호출한 객체가 없음 -> this에 전역 객체 할당
    })
  }
};

products.buyAll();

 

 

 


📖 마치며

this의 개념과 어떻게 바인딩되고 어떤 객체를 참조하는지 살펴봤다. 파이썬의 self 같은 참조 변수와 다르기에 조금 까다롭게 느껴졌다. 생성자 함수, 프로토타입에서도 this를 다룰 거기에  this의 유의해야 할 부분들은 후에 함께 정리해보도록 하겠다.

 

요약정리

  • 메서드는 this로 객체를 참조한다.
  • 자바스크립트는 함수 선언이 아닌 함수 호출에 따라 참조 변수가 달라진다. (동적)
  • 전역 함수는 전역 객체를 참조한다.
  • 내부 함수는 일반 함수, 메서드, 콜백 함수 어디에서 선언되었든 관계없이 this는 전역 객체를 참조한다.
    이는 설계단계의 결함이며, 참조를 회피할 수 있는 메서드들이 존재한다.(apply, call, bind)
  • 함수가 객체의 프로퍼티 값이면 메서드로서 호출된다. 이때 메서드 내부의 this는 해당 메서드를 소유한 객체, 즉 해당 메서드를 호출한 객체에 바인딩된다.
  • DOM 이벤트 헨들러에서 this는 이벤트를 일으킨 DOM 요소로 바인딩된다.
  • 화살표 함수는 자신만의 this를 가지지 않고, 외부 객체에 바인딩된다.

 

💬

다른 언어를 사용하다 자바스크립트로 넘어온 개발자는 this를 혼동하기 쉽습니다. this는 항상 메서드가 정의된 객체를 참조할 것이라고 착각하죠. 이런 개념을 'bound this'라고 합니다.

자바스크립트에서 this는 런타임에 결정됩니다. 메서드가 어디서 정의되었는지에 상관없이 this는 ‘점 앞의’ 객체가 무엇인가에 따라 ‘자유롭게’ 결정됩니다.

이렇게 this가 런타임에 결정되면 좋은 점도 있고 나쁜 점도 있습니다. 함수(메서드)를 하나만 만들어 여러 객체에서 재사용할 수 있다는 것은 장점이지만, 이런 유연함이 실수로 이어질 수 있다는 것은 단점입니다.

자바스크립트가 this를 다루는 방식이 좋은지, 나쁜지는 우리가 판단할 문제가 아닙니다. 개발자는 this의 동작 방식을 충분히 이해하고 장점을 취하면서 실수를 피하는 데만 집중하면 됩니다.

- 코어 자바스크립트

 

 

참고자료

https://poiemaweb.com/js-this

https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/this

https://ko.javascript.info/object-methods

 

 

댓글