본문 바로가기

Javascript

this 콜백함수 & 생성자 함수_v3.9

✅ this(정의, 활용방법, 바인딩, call, apply, bind)

이번 시간에는 this에 대해 다루게 됩니다. 다른 객체지향 언어에서의 this는 곧 클래스로 생성한 인스턴스를 말합니다. 그러나 자바스크립트에서는 this가 어디에서나 사용될 수 있어요. this가 상황별로 어떻게 달라지는지, 이유는 뭔지, this를 추적하는 방법 등을 알아 볼 예정입니다.

(1) 상황에 따라 달라지는 this

잊지 않으셨겠죠? 또 리마인드!!

-실행 컨텍스트실행할 코드에 제공할 환경 정보들을 모아놓은 객체이다.
- 그 객체 안에는 3가지가 존재한다.
✓ VariableEnvironment
✓ LexicalEnvironment
✓ ThisBindings
  1. this는 실행 컨텍스트가 생성될 때 결정돼요. 이 말을 this를 bind한다(=묶는다) 라고도 하죠. 다시 말하면. **this는 함수를 호출할 때 결정된다.** 라고 할 수 있습니다.
    1. 전역 공간에서의 this
      1. 전역 공간에서 this는 전역 객체를 가리켜요.
      2. 런타임 환경에 따라 this는 window(브라우저 환경) 또는 global(node 환경ex) node.js)를 각각 가리킵니다.

런타임 환경? 여러분들이 javascript로 만들어놓은 프로그램이 구동중인 환경을 말하죠. 우리는 node 파일이름.js로 vscode 상에서 구동하고 있으니 node 환경이라고 할 수 있구요. html 파일 안에 숨겨놓아서 크롬브라우저 등에서 연다고 한다면 브라우저 환경이라고 할 수 있겠네요.

<브라우저 환경 this 확인>

console.log(this);
console.log(window);
console.log(this === window);

<node 환경 this 확인>

console.log(this);
console.log(global);
console.log(this === global);
  1. 메서드로서 호출할 때 그 메서드 내부에서의 this
    1. 함수 vs 메서드
    2. 함수와 메서드, 상당히 비슷해 보이지만 엄연한 차이가 존재합니다. 기준은 **독립성**이에요. 함수는 그 자체로 독립적인 기능을 수행해요. 
함수명();

그러나 메서드는 자신을 호출한 대상 객체에 대한 동작을 수행해요. (종속적!)

객체.메서드명();

this의 할당

// CASE1 : 함수
// 호출 주체를 명시할 수 없기 때문에 this는 전역 객체를 의미해요.
var func = function (x) {
	console.log(this, x);
};
func(1); // Window { ... } 1

// CASE2 : 메서드
// 호출 주체를 명시할 수 있기 때문에 this는 해당 객체(obj)를 의미해요.
// obj는 곧 { method: f }를 의미하죠?
var obj = {
	method: func,
};
obj.method(2); // { method: f } 2
  1. 함수로서의 호출과 메서드로서의 호출 구분 기준 : . []
  2. 아래 예시도 같아요! 점(.)으로 호출하든, 대괄호([])로 호출하든 결과는 같습니다 😉
var obj = {
	method: function (x) { console.log(this, x) }
};
obj.method(1); // { method: f } 1
obj['method'](2); // { method: f } 2
  1. 메서드 내부에서의 this
  2. 위의 내용에서 보았듯, this에는 **호출을 누가 했는지에 대한 정보**가 담겨요.
var obj = {
	methodA: function () { console.log(this) },
	inner: {
		methodB: function() { console.log(this) },
	}
};

obj.methodA();             // this === obj
obj['methodA']();          // this === obj

obj.inner.methodB();       // this === obj.inner
obj.inner['methodB']();    // this === obj.inner
obj['inner'].methodB();    // this === obj.inner
obj['inner']['methodB'](); // this === obj.inner
  1. 함수로서 호출할 때 그 함수 내부에서의 this
    1. 함수 내부에서의 this
      1. 어떤 함수를 함수로서 호출할 경우, this는 지정되지 않아요(호출 주체가 알 수 없으니까요)
      2. 실행컨텍스트를 활성화할 당시 this가 지정되지 않은 경우, this는 전역 객체를 의미하죠
      3. 따라서, 함수로서 ‘독립적으로’ 호출할 때는 **this는 항상 전역객체를 가리킨다**는 것을 주의하길 바래요 👍
    2. 메서드의 내부함수에서의 this
      1. 예외는 없습니다! 메서드의 내부라고 해도, 함수로서 호출한다면 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), (2), (3)을 예측해볼까요?

(1) : obj1, (2) : 전역객체, (3) : obj2

맞았다면 여러분은 this에 대해 정말 많이 이해하신 거에요 👍👍

this 바인딩에 관해서는 함수를 실행하는 당시의 주변 환경(메서드 내부인지, 함수 내부인지)은 중요하지 않고,
오직 해당 함수를 호출하는 구문 앞에 점 또는 대괄호 표기가 있는지
가 관건이라는 것을 알 수 있습니다.
  1. 메서드의 내부 함수에서의 this 우회
    1. 변수를 활용하는 방법
    2. 내부 스코프에 이미 존재하는 this를 별도의 변수(ex : self)에 할당하는 방법이에요!
  2. this에 대해서 이해는 하겠지만… 사용자 입장에서. 즉, 개발자 입장에서 이게 쉽게 받아들여지시나요? 그렇지 않죠? 그렇기 때문에 우회할 수 있는 방법을 우리는 찾아볼 수 있습니다.
var obj1 = {
	outer: function() {
		console.log(this); // (1) outer

		// AS-IS : 기존 것
		var innerFunc1 = function() {
			console.log(this); // (2) 전역객체
		}
		innerFunc1();

		// TO-BE : 이후 것
		var self = this;
		var innerFunc2 = function() {
			console.log(self); // (3) outer
		};
		innerFunc2();
	}
};

// 메서드 호출 부분
obj1.outer();
  1. 화살표 함수(=this를 바인딩하지 않는 함수)
    1. ES6에서 처음 도입된 화살표 함수는, 실행 컨텍스트를 생성할 때 this 바인딩 과정 자체가 없습니다(따라서, this는 이전의 값-상위값-이 유지돼요 / ES6에서는 함수 내부에서 this가 전역객체를 바라보는 문제 때문에 화살표함수를 도입했어요!)
    2. 일반 함수와 화살표 함수의 가장 큰 차이점은 무엇인가요? 라고 물으면 여러분은 무엇이라고 답해야 할까요? this binding 여부가 가장 적절한 답입니다 😉
var obj = {
	outer: function() {
		console.log(this); // (1) obj
		var innerFunc = () => {
			console.log(this); // (2) obj
		};
		innerFunc();
	}
}

obj.outer();
  1. 콜백 함수 호출 시 그 함수 내부에서의 this

“어떠한 함수, 메서드의 인자(매개변수)로 넘겨주는 함수”<aside> ❗ 로직을 이해하는 것 보다는, this의 상태를 이해하는 것이 더 중요해요!

</aside>

이 때, 콜백함수 내부의 this는 해당 콜백함수를 넘겨받은 함수(메서드)가 정한 규칙에 따라 값이 결정된답니다. 콜백 함수도 함수기 때문에 this는 전역 객체를 참조하지만(호출 주체가 없잖아요), 콜백함수를 넘겨받은 함수에서 콜백 함수에 별도로 this를 지정한 경우는 예외적으로 그 대상을 참조하게 되어있어요. 다음 예시를 통해 구체적으로 알아봅시다.

우리는 앞선 과정에서 콜백 함수를 다음과 같이 정의한 적이 있어요.

// 별도 지정 없음 : 전역객체
setTimeout(function () { console.log(this) }, 300);

// 별도 지정 없음 : 전역객체
[1, 2, 3, 4, 5].forEach(function(x) {
	console.log(this, x);
});

// addListener 안에서의 this는 항상 호출한 주체의 element를 return하도록 설계되었음
// 따라서 this는 button을 의미함
document.body.innerHTML += '<button id="a">클릭</button>';
document.body.querySelector('#a').addEventListener('click', function(e) {
	console.log(this, e);
});
  1. setTimeout 함수, forEach 메서드는 콜백 함수를 호출할 때 대상이 될 this를 지정하지 않으므로, this는 곧 window객체
  2. addEventListner 메서드는 콜백 함수 호출 시, 자신의 this를 상속하므로, this는 addEventListner의 앞부분(button 태그)

생성자 함수 내부에서의 this

  1. 생성자 : 구체적인 인스턴스(어려우면 객체로 이해!)를 만들기 위한 일종의 틀
  2. 공통 속성들이 이미 준비돼 있어요.
  3. **객체를 생성하는 방법**에서 이미 언급한 적이 있었죠. 거기서 this를 본 적이 있었어요!
var Cat = function (name, age) {
	this.bark = '야옹';
	this.name = name;
	this.age = age;
};

var choco = new Cat('초코', 7); //this : choco
var nabi = new Cat('나비', 5);  //this : nabi