본문 바로가기
언어/JavaScript

24/08/16 - JavaScript(3 - 3): this

by Jini_Lamp 2024. 8. 16.

this는 가지고 있는 메소드가 속해있는 객체를 가리키로독 만든 예약어이다. 쉽게 말해 다른 객체지향 언어에서 this는 자기 자신을 나타낸다.

 

하지만 JS에서 this는 어디에서나 사용 가능하다.

즉, this가 자기 자신을 가리키지 않을 수도 있다는 소리다.

 

JS에서 this는 실행 컨텍스트가 생성될 때 결정된다. 다시 말해 함수를 호출할 때 결정되는데, 전역 스코프에서 this는 전역 객체를 참조한다.(다른 언어에서는 클래스 내에서만 사용되며, 전역 컨텍스트에서는 사용되지 않는다.)

// 브라우저 환경에서의 this
console.log(this);
console.log(window);
console.log(this === window);	// true

// node 환경에서의 this
console.log(this);
console.log(global);
console.log(this === global);	// true

 

 

 

메서드 this와 함수 this

메서드로써 호출할 때와 함수로써 호출할 때도 다르다.

먼저 메서드와 함수의 차이점을이 있는데, 함수는 그 자체로 독립적인 기능을 수행하지만 메서드는 자신을 호출한 대상 객체에 대한 동작을 수행한다. 즉, 독립성을 기준으로 함수와 메서드가 나뉜다.

함수명();	// 함수
객체.메서드명();	// 메서드

 

따라서 함수와 메서드의 this도 다르다.

왜냐하면 함수는 그 자체로 독립적인 기능을 하기 때문에 this가 전역 객체를 가리키고, 메서드는 자신을 부른 객체가 있기 때문에 메서드의 this는 호출한 객체를 가리킨다.

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

// CASE2 : 메서드
// 호출 주체를 명시할 수 있기 때문에 this는 해당 객체(obj)를 의미
var obj = {
	method: func,
};
obj.method(2); // { method: func } 2

 

함수 내부에 this가 있고, 함수를 함수로서 호출할 때, this는 지정되지 않는다. 왜냐하면 호출 주체를 알 수 없기 때문이다. 실행 컨텍스트를 활성화할 당시 this가 지정되지 않은 경우, this는 전역 객체를 의미한다.

따라서 함수로서 독립적으로 호출할 때는, this는 항상 전역 객체를 가리킨다.

 

메서드는 기본적으로 호출 주체가 있다. 따라서 메서드 안에 있는 함수도 예외는 없다. 메서드 내부라고 해도 함수로서 호출한다면 this는 전역 객체를 의미한다.

var obj1 = {
	outer: function() {
		console.log(this); // (1)     // { outer: [Function: outer] } obj1에서 호출...
		var innerFunc = function() {
			console.log(this); // (2), (3)
		}
		innerFunc();    // Object [global]    // 함수니까 전역

		var obj2 = {
			innerMethod: innerFunc
		};
		obj2.innerMethod();   // { innerMethod: [Function: innerFunc] } obj2에서 호출...
	}
};

obj1.outer();

 

 

 

this 우회

JS에서는 this를 우회하는 방법도 있다.

우회를 하는 이유는 간단한데, 특정 상황에서 this가 개발자가 예상하는 객체를 가리키지 않기 때문이다. JS에서 this는 함수가 호출되는 방식에 따라 동적 결정되므로, 의도하지 않은 객체를 가리킬 수 있다. 따라서 이런 문제를 해결하고 this를 개발자가 원하는 객체로 고정시키기 위해 this를 우회하거나 고정하는 방법이 필요하다.

우회 방법에는 크게 변수를 활용하는 방법화살표 함수를 사용하는 방법이 있다.

 

  • 변수를 활용: 내구 스코프에 이미 존재하는 this를 별도의 변수에 할당하는 방법
var obj1 = {
	outer: function() {
		console.log(this); // (1) { outer: [Function: outer] }

		// AS-IS
		var innerFunc1 = function() {
			console.log(this); // (2) 전역객체
		}
		innerFunc1();   // Object [global] 함수로서 호출한거니까...

		// TO-BE
		var self = this;	// 스코프 내부에 있다 = obj1을 가리킨다.
		var innerFunc2 = function() {
			console.log(self); // (3) { outer: ƒ }
		};
		innerFunc2();   // { outer: [Function: outer] }
	}
};

// 메서드 호출 부분
obj1.outer();
AS - IS: 기존 것

TO - BE: 이후 것

 

 

  • 화살표 함수(= this를 바인딩하지 않는 함수): ES6에서는 함수 내부에서 this가 전역객체를 바라보는 문제 때문에 도입

일반 함수와 화살표 함수의 가장 큰 차이점은 this binding 여부이다.

 

아래 코드에서, 원래라면 innerFunc()를 함수로 호출했으므로 전역객체가 출력되어야 한다. 하지만 innerFunc()가 화살표 함수인 탓에 여전히 outer을 바라보고 있다. 왜냐하면 화살표 함수는 this binding을 생략하기 때문이다.

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

obj.outer();

 

 

 

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

먼저 알아야 할 것이 있다. 콜백 함수도 함수다. 콜백 함수는 어떠한 함수를 호출했을 때 매개변수로 들어가는 함수이다.(https://dkskfktldi.tistory.com/entry/240814-JavaScript2-2-%EC%9D%BC%EA%B8%89-%EA%B0%9D%EC%B2%B4%EC%99%80-%ED%95%A8%EC%88%98)

// 별도 지정 없음 : 전역객체
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);	// 여기는 전역이 아니라 addEventListener를 호출한 주체를 바라본다.
});

 

콜백 함수도 함수기 때문에, 기본적으로 전역객체를 바라본다.

하지만 물론, 예외도 있다. 콜백 함수에 별도로 this를 지정한 경우는 예외적으로 그 대상을 참조하게 되어있다.

 

 

 

생성자 함수 내부에서의 this

생성자는 구체적인 인스턴스(객체)를 만들기 위한 일종의 틀로, 이 안에서의 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

 

 

 

명시적 this 바인딩

명시적 this 바인딩은 자동으로 부여되는 상황별 this 규칙을 깨고, this에 별도의 값을 저장하는 방법이다.

종류는 크게 call, apply, bind가 있다.

 

  • call: 첫 번째 매개변수에 this로 binding 할 객체를 넣어주면 명시적으로 binding 할 수 있다.
var func = function (a, b, c) {
	console.log(this, a, b, c);
};

// no binding
func(1, 2, 3); // Window{ ... } 1 2 3

// 명시적 binding
// func 안에 this에는 {x: 1}이 binding돼요
// call 명령어를 통해 첫번째 매개변수에 this로 바인딩할 객체를 넣어주면 명시적으로 바인딩할 수 있다.
func.call({ x: 1 }, 4, 5, 6}; // { x: 1 } 4 5 6
// 예상되는 this가 있음에도 바꿀 수 있다.
var obj = {
	a: 1,
	method: function (x, y) {
		console.log(this.a, x, y);
	}
};

obj.method(2, 3); // 1 2 3
obj.method.call({ a: 4 }, 5, 6); // 4 5 6

 

  • apply: call 과 완전히 동일하다. 하지만 this에 binding 할 객체는 똑같이 넣어주고 나머지 부분만 배열 형태로 넘겨준다.
var func = function (a, b, c) {
	console.log(this, a, b, c);
};
func.apply({ x: 1 }, [4, 5, 6]); // { x: 1 } 4 5 6

var obj = {
	a: 1,
	method: function (x, y) {
		console.log(this.a, x, y);
	}
};

obj.method.apply({ a: 4 }, [5, 6]); // 4 5 6

 

여기서 call 과 apply를 활용하면 좀 더 다양한 것들을 시도할 수 있다.

// 공통된 내용은 Person으로 묶고, 개별적인 부분은 따로 하기

function Person(name, gender) {
	this.name = name;
	this.gender = gender;
}
function Student(name, gender, school) {
	Person.call(this, name, gender); // 여기서 this는 student 인스턴스!
	this.school = school;
}
function Employee(name, gender, company) {
	Person.apply(this, [name, gender]); // 여기서 this는 employee 인스턴스!
	this.company = company;
}
var kd = new Student('길동', 'male', '서울대');
var ks = new Employee('길순', 'female', '삼성');

 

  • bind: call과 비슷해 보이나 call과 다르게 즉시 호출하지는 않고 넘겨 받은 this 및 인수들을 바탕으로 새로운 함수를 반환하는 메서드이다.
    • 함수에 this를 미리 적용
    • 부분 적용 함수 구현할 때 용이
var func = function (a, b, c, d) {
	console.log(this, a, b, c, d);
};
func(1, 2, 3, 4); // window객체

// 함수에 this 미리 적용
var bindFunc1 = func.bind({ x: 1 }); // 바로 호출되지는 않아요! 그 외에는 같아요.
bindFunc1(5, 6, 7, 8); // { x: 1 } 5 6 7 8

// 부분 적용 함수 구현
var bindFunc2 = func.bind({ x: 1 }, 4, 5); // 4와 5를 미리 적용
bindFunc2(6, 7); // { x: 1 } 4 5 6 7
bindFunc2(8, 9); // { x: 1 } 4 5 8 9

 

bind 메서드를 적용해서 새로 만든 함수는 name 프로퍼티에 'bound' 라는 접두어가 붙는다.(= 추적하기 쉽다.)

var func = function (a, b, c, d) {
	console.log(this, a, b, c, d);
};
var bindFunc = func.bind({ x:1 }, 4, 5);

// func와 bindFunc의 name 프로퍼티의 차이를 살펴보세요!
console.log(func.name); // func
console.log(bindFunc.name); // bound func