JS | 이 this가 네 this이냐, 아니면 저 this가 네 this이냐 😵‍💫

sol ahn·2021년 11월 22일
0

JavaScript

목록 보기
4/8
post-thumbnail
post-custom-banner

자바스크립트에서의 this는 마치 동요 '내 동생 곱슬머리'에서 이름은 하나인데 별명은 서너 개인 내 동생처럼 어떻게 부르는가에 따라서 this에 바인딩되는 객체가 달라진다. 가뜩이나 객체지향 언어의 this와도 헷갈리는데 자바스크립트 내에서 this가 이리저리 바뀌는 모양새를 보니 괜히 어질어질해지는 기분이 든다. 그래도 '함수 호출 방식'에 따라서 바인딩되는 this의 값을 딱 정리해두고 예제 코드들을 찬찬히 살펴보면서 이해하면 this에 대한 두려움을 조금씩 극복해나갈 수 있다!


자바스크립트에서의 this

여타 객체지향 언어와 달리 자바스크립트에서 this는 어디서든 사용 가능

함수와 객체의 구분이 명확하지 않은 자바스크립트에서 this는 실질적으로 이 둘을 구분해주는 역할을 함.

자바스크립트에서 this는 실행 컨텍스트가 생성될 때 함께 결정됨.

this

자신이 속한 객체 또는 자신이 생성할 인스턴스를 가리키는 자기 참조 변수(self-referencing variable)

this 키워드를 통해 자신이 속한 객체 또는 자신이 생성할 인스턴스의 프로퍼티 or 메서드 참조 가능

this 바인딩은 '함수 호출 방식'에 의해 동적으로 결정됨. ⇒ strict mode도 영향을 줌.

💡 this 바인딩은 this와 this가 가리킬 객체를 연결하는 것.

함수 호출 방식에 의한 this 바인딩

일반 함수 호출

일반 함수를 호출하면 함수 내부의 this에 전역 객체(global object)가 바인딩됨. ⇒ 콜백 함수가 일반 함수로 호출되는 경우에도 this에 전역 객체가 바인딩됨.

function foo() {
	console.log("foo's this: ", this); // window 출력
	function bar() {
		console.log("bar's this: ", this); // window 출력
	}
	bar();
}
foo();

strict mode가 적용된 일반 함수 내부의 this에는 undefined가 바인딩됨.

function foo() {
	'use strict';
	console.log("foo's this: ", this); // undefined 출력
	function bar() {
		console.log("bar's this: ", this); // undefined 출력
	}
	bar();
}
foo();

메서드 내에서 정의한 중첩 함수도 일반 함수로 호출되면 중첩 함수 내부의 this에는 전역 객체가 바인딩 됨.

var value = 1;

const obj = {
	value: 100,
	foo() {
		console.log("foo's this: ", this); // { value: 100, foo: f } 출력
		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 출력
		}
		bar();
	}
}

obj.foo();

💡 콜백 함수는 기본적으로 this에 전역 객체를 바인딩 되지만, 제어권을 받은 함수에서 콜백 함수에 별도로 this가 될 대상을 지정한 경우, this에 그 대상이 바인딩 됨. ex) 이벤트 리스너

setTimeout(function() { console.log(this); }, 100); // window 출력

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

document.body.innerHTML += '<button id="a">Click!</button>';
document.body.querySelector('#a').addEventListener('click', function(e) {
	console.log(this, e); // #a 엘리먼트와 클릭 이벤트 정보가 담긴 객체 출력
})

메서드 호출

메서드를 호출하면 메서드 내부의 this에 메서드를 호출한 객체가 바인딩됨. ⇒ 메서드를 소유한 객체가 아니라, 메서드를 '호출'한 객체라는 것이 포인트!

const person = {
	name: 'Ahn'
	getName(){
		return this.name;
	}
}

console.log(person.getName()); // Ahn 출력

const anotherPerson = {
	name: 'Park'
}

// getName 메서드를 anotherperson 객체의 메서드에 할당
anotherPerson.getName = person.getName;

console.log(anotherPerson.getName()); // Park 출력

const getName = person.getName;

// getName 메서드를 일반 함수로 호출
console.log(getName()); // '' 출력


프로토타입 메서드 내부에서 사용된 this도 해당 메서드를 호출한 객체에 바인딩됨.

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

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

const sol = new Person('Sol');

console.log(sol.getName()); // Sol 출력

Person.prototype.name = 'Sol Ahn';

console.log(Person.prototype.getName()); // Sol Ahn 출력

생성자 함수 호출

생성자 함수를 호출하면 생성자 함수 내부의 this에 생성자 함수가 생성할 인스턴스가 바인딩됨.

만약 new 연산자와 함께 생성자 함수를 호출하지 않으면, 생성자 함수가 아니라 '일반 함수'처럼 동작함.

function Circle(radius) {
	this.radius = radius;
	this.getDiameter = function() {
		return 2 * this.radius;
	}
}

const circle1 = new Circle(5);
const circle2 = new Circle(10);

console.log(circle1.getDiameter()); // 10 출력
console.log(circle2.getDiameter()); // 20 출력

// new 연산자와 함께 호출하지 않으면 생성자 함수가 아닌 일반 함수 호출로 동작
const circle3 = Circle(15);

console.log(circle3); // undefined 출력
console.log(radius); // 15 출력

apply / call / bind 메서드에 의한 간접 호출 ⇒ 명시적인 this 바인딩

apply / call

apply와 call 메서드는 this로 사용할 객체인수 리스트를 전달받아 함수를 호출함.

apply 메서드는 호출할 함수의 인수를 배열로 전달하고, call 메서드는 호출할 함수의 인수를 쉼표로 구분한 리스트로 전달함.

function getThisBinding() {
	console.log(arguments);
	return this;
}

const thisArg = { a: 1 };

// apply 메서드 호출
console.log(getThisBinding.apply(thisArg, [1, 2, 3]));
// Arguments(3) [ 1, 2, 3, callee: f Symbol(Symbol.iterator): f]
// { a: 1 }

// call 메서드 호출
console.log(getThisBinding.call(thisArg, 1, 2, 3));
// Arguments(3) [ 1, 2, 3, callee: f Symbol(Symbol.iterator): f]
// { a: 1 }

대표적으로 유사 배열 객체에 배열 메서드를 사용할 때 apply나 call 메서드를 호출함.

function convertArgsToArr() {
	console.log(arguments);

	// call 메서드를 호출하여 유사 배열 객체인 arguments를 slice 배열 메서드에 바인딩 
	const arr = Array.prototype.slice.call(arguments);
	console.log(arr);

	return arr; 
}

convertArgsToArr(1, 2, 3); // [ 1, 2, 3 ] 출력

bind

bind 메서드는 함수를 호출하지 않고 this로 사용할 객체만 전달함.

function getThisBinding() {
	return this;
}

const thisArg = { a: 1 };

console.log(getThisBinding.bind(thisArg)); // getThisBinding 출력
// bind 메서드는 함수를 호출하지 않기 때문에 명시적 호출 필요
console.log(getThisBinding.bind(thisArg)()); // { a:1 } 출력

대표적으로 메서드의 this와 메서드 내부의 중첩 함수나 콜백 함수의 this를 일치시킬 때 사용됨.

const person1 = {
	name: 'Ahn',
	foo(callback) {
		setTimeout(callback, 100);
	}
}

// 일반 함수로 호출된 콜백 함수 내부의 this는 전역 객체를 가리킴.
// window.name은 브라우저 창의 이름을 나타내는 빌트인 프로퍼티로 ''이 기본값
person1.foo(function() {
	console.log(`Hi~ my name is ${this.name}!`); // Hi~ my name is ! 출력
})

const person2 = {
	name: 'Sol',
	foo(callback) {
		// bind 메서드로 callback 함수 내부의 this에 person2 객체의 this를 바인딩
		setTimeout(callback.bind(this), 100);
	}
}

person2.foo(function() {
	console.log(`Hi~ my name is ${this.name}!`) // Hi~ my name is Sol! 출력
})

this를 바인딩하지 않는 함수 : 화살표 함수

함수 내부에서 this가 전역 객체에 바인딩되는 문제를 보완하기 위해 ES6에 화살표 함수(arrow function)가 도입됨.

화살표 함수는 실행 컨텍스트를 실행할 때, this 바인딩 자체가 빠지기 때문에 상위 스코프의 this를 그대로 활용할 수 있음!

const obj = {
	outer: function() {
		console.log(this); // { outer: f } 출력
		// 화살표 함수의 this는 상위 스코프의 this를 가리킴.
		const inner = () => {
			console.log(this); // { outer: f } 출력 
		}
		inner();
	}
}

obj.outer();

별도의 인자로 this를 받는 경우 : 콜백 함수 내의 this

콜백 함수를 인자로 받는 메서드 중 일부는 추가로 this로 지정할 객체(thisArg)를 인자로 받아 콜백 함수 내부의 this값을 원하는 대로 바꿀 수 있음.

const report = {
	sum: 0,
	count: 0,
	add: function() {
		const args = Array.prototype.slice.call(arguments);
		// 콜백함수 내부의 this에 forEach 메서드의 두 번째 인자로 전달한 this 바인딩
		args.forEach(function(entry) {
			this.sum += entry;
			++this.count;
		}, this);
	}
	average: function() {
		return this.sum / this.count;
	}
}

report.add(60, 85, 95);
console.log(report.sum, report.count, report.average()); // 240 3 80

  • 콜백 함수와 thisArg를 인자로 받는 메서드

Array.prototype.forEach(callback[, thisArg])
Array.prototype.map(callback[, thisArg])
Array.prototype.filter(callback[, thisArg])
Array.prototype.map(callback[, thisArg])
Array.prototype.filter(callback[, thisArg])
Array.prototype.some(callback[, thisArg])
Array.prototype.every(callback[, thisArg])
Array.prototype.find(callback[, thisArg])
Array.prototype.findIndex(callback[, thisArg])
Array.prototype.flatMap(callback[, thisArg])
Array.prototype.from(callback[, thisArg])
Set.prototype.forEach(callback[, thisArg])
Map.prototype.forEach(callback[, thisArg])


Ref

[유튜브] 코드종 자바스크립트 this
[도서] 송형주/고현준, ⌜인사이드 자바스크립트⌟, 한빛미디어, 2014
[도서] 정재남, ⌜코어 자바스크립트⌟, 위키북스, 2019
[도서] 이웅모, ⌜모던 자바스크립트 Deep Dive⌟, 위키북스, 2020
[MDN] this - JavaScript | MDN
[사이트] 화살표 함수 다시 살펴보기
[블로그] (JavaScript) 자바스크립트의 this는 무엇인가?

profile
아는 만큼 재밌는 개발자 🤓
post-custom-banner

0개의 댓글