본문 바로가기

💻 내 소개 안녕하세요 엄청짱 프로그래머 손다빈 입니다.
  • 나이 : 96년생
  • 특이사항 : MZ세대, INFJ, 오른손잡이, 아이폰 유저
  • 좋아하는 음식 : 햄버거피자치킨솥뚜껑삼겹살떡볶이오튀김밥
  • 취미 : 개발, Programming, 코딩, 프로그래밍, Coding

🥷기술
Unity
Godot
Cpp
Javascript
D3
Vue

🐱 우리집 고양이 소개
츄르 먹은 후 츄르 먹기 전
  • 이름 : 콜라
  • 나이 : 8살
  • 종 : Nado moreum

📱 개인 프로젝트
🏢 참여한 프로젝트
빌런즈 Life is Pair 도씨어부키우기 직장상사혼내주기 서바이벌빙고 SlitherCoin

🌱 내 잔디밭

자바스크립트는 this 를 알아야 됩니다. 본문

글 묶음/사탄도 외면한 javascript

자바스크립트는 this 를 알아야 됩니다.

초긍정 개발자 다빈맨 2019. 2. 14. 03:46

| 자바스크립트는 this 를 알아야 됩니다.



자바스크립트에서 this를 제대로 이해하지 못하면 클로저(Closure)를 활용하기 어렵고, 클로저를 활용하지 못한다면 일정 수준 이상의 코드를 작성하기 어렵게 됩니다. 물론 저는 더 좋은 코드를 주장하는 것 만큼 유치한건 없다고 생각하지만 적어도 언어에 대한 이해가 부족해서 좁혀진 방법으로 코드를 작성하는 것은 잘못되었다고 생각합니다. 앞으로 여러분들이 자바스크립트에서 this를 맞닥드리더라도 더이상 이것을 추상적으로 느끼시지 않았으면 합니다.





| 상황에 따라서 this 를 다르게 생각하기


저는 this 가 다르게 동작하는 네 가지 상황을 다음과 같이 나누고 각각에 대한 예시를 소개해드릴까 합니다.


- 메소드 안에서의 this

- 메소드가 아닌 독립적인 함수 안에서의 this

- 생성자로 인해 호출된 this

- 화살표 함수 안에서의 this





| 메소드 안에서의 this


객체가 가지고 있는 함수를 메소드(Method) 라고 부릅니다. 여기서는 메소드가 사용되는 가장 일반적인 경우인 전역 함수를 먼저 살펴봅니다.

function beaver() {
	console.log(this);
};

위와같이 전역 함수를 선언하면 그냥 함수가 허공에 만들어진다고 생각될 수 있겠지만 실제로는 전역 객체(window 혹은 global) 안에 함수가 선언되는 것이기 때문에 전역 함수도 결국에는 메소드 입니다. 이 beaver 함수를 호출해서 this가 무엇이 호출되었는지 확인해봅시다.

beaver();
Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: Window, …} //Chrome 콘솔에서 출력

전역 객체인 Window 가 출력됩니다. 이 결과로 메소드를 호출할 시 메소드 안에서 this는 함수를 소유하고 있는 객체가 된다는 것을 알 수 있습니다.

var beaver = {
	foo : function() {
		console.log(this);
    }
}
beaver.foo(); // 여기서 this 는 beaver가 된다.

위와같이 사용자가 직접 객체 안에서 메소드를 만드는 경우에는 당연히 함수를 소유하고 있는 객체인 beaver 가 this가 됩니다.



| 메소드가 아닌 독립적인 함수 안에서의 this


항상 함수가 객체 안에서만 선언되는 것은 아닙니다. 즉, 언제나 메소드로만 함수가 선언되는것이 아니라는 말입니다. 예를들어 다음과 같이 함수 안에 함수가 선언되는 경우를 많이 볼 수 있습니다.

function beaver() {
	function raccoon() {
		console.log(this);
    }
	raccoon();
}
beaver(); // Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: Window, …}

beaver 전역 함수 안에 raccon 이라는 함수를 선언해 호출합니다. 이 경우 특정 객체에 함수를 선언한 것이 아닌 독립적인 함수로 선언되고 이런식으로 객체에 속해있지 않은 함수의 경우 this 는 전역객체가 됩니다.



| 생성자로 인해 호출된 this


function Beaver(name) {
	this.name = name;
}
Beaver("foo");
console.log(window.name); // "foo"

위 함수에서의 this 는 전역객체가 되기 때문에 Baver 함수 호출 후 window의 name을 호출했을 때 "foo" 가 출력됨을 이제는 알고 있습니다.

그렇다면 함수를 new 연산자를 통해서 생성자로 호출하는 경우에도 this는 똑같이 전역 객체를 가리킬까요?

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

var foo = new Beaver("foo"); // 이 생성자로 만들어진 객체 this 를 반환
var bar = new Beaver("bar"); // 이 생성자로 만들어진 객체 this 를 반환

console.log(window.name); // 빈 문자열 "" 이 출력
console.log(foo.name); // "foo"
console.log(bar.name); // "bar"

예상하셨겠지만 new 로 인해서 함수를 생성자로 호출하는 경우에는 함수 안에서 this가 새롭게 만들어진 객체를 가리키게 되고 위 코드에서 window.name 을 출력해도 빈 문자열이 출력됩니다. 여기서 눈치 빠른 분들은 알아채셨을 텐데 new 연산자로 호출한 함수 생성자는 반환할 때 자기 자신(this) 를 반환합니다. 그렇기 때문에 그 값을 각각 변수에 넣어 name 프로퍼티를 출력했을 때 해당 객체의 name에 접근할 수 있죠.



| 화살표 함수 안에서의 this


ES6 가 정의되면서 새롭게 추가된 화살표 함수(Arrow Function)의 경우 기존의 함수들과 다르게 this가 적용됩니다. 맞습니다. 개빡치지만 굉장히 단순하게 동작되기 때문에 몇가지 예시를 통해 금방 이해하실 수 있습니다. 

var foo = () => {
	console.log(this);
};

var beaver = {
	foo : foo
};

foo(); // Window
beaver.foo(); //Window

원래대로 라면 메소드로 호출된 beaver.foo 함수의 결과는 함수를 소유하고있는 beaver가 this가 되어야 합니다. 하지만 화살표 함수는 함수를 호출하는 영역의 this 를 그대로 가져옵니다. 위 예제에서는 함수를 호출한 영역이 전역(즉, window) 객체이기 때문에 this 가 전역객체가 됩니다. 간단하죠?



| this 정해주기 (call, apply, bind)


언제나 this는 정해진 규칙에 의해서 작동하지만 규칙을 뒤로하고 함수를 호출할때 call 과 apply 함수를 이용해서 직접 this를 명시적으로 정해줄 수 있습니다.

function foo() {
	console.log(this.name);
}

var beaver = {
	name : "dorothy"
};

foo.call(beaver); // "dorothy"

위 예제에서 foo 함수를 호출하면 this 는 전역객체가 되어야 겠지만 call (혹은 apply) 를 이용해서 함수를 호출하게 되면 매개변수로 함수 안에서의 this를 지정할 수 있습니다. 기본적으로 call 과 apply 는 완전히 동일한 결과를 가지는 함수지만 사용하는 방법에서 차이가 있습니다.

function profile(age, weight) {
	console.log(this.name);
	console.log("age : " + age);
	console.log("weight : " + weight);
}

var beaver = {
	name : "dorothy"
};

profile.apply(beaver, [4, "2kg"]);
//profile.call(beaver, 4, "2kg");

위 예제에서 apply 와 call 을 호출한 부분을 살펴보면 apply 는 두번째 매개변수에 배열로 값을 묶어서 전달했지만 call 은 하나하나 값을 따로 전달해서 호출합니다. 즉, 함수를 호출할 때 매개변수로 전달할 값들을 배열로 묶어서 전달할지, 아니면 따로 하나하나 값을 넣어서 전달할지의 차이입니다.  

function profile(age, weight) {
	console.log(this.name);
	console.log("age : " + age);
	console.log("weight : " + weight);
}

var beaver = {
	name : "dorothy"
};

profile.bind(beaver)(4, "2kg");

마지막으로 bind 를 사용해서도 this를 변경할 수 있음을 예제로 보여줍니다. 다만, bind의 경우 커링(currying) 에 대한 이해가 있어야 동작 방식을 이해하실 수 있습니다. 커링 기법은 여기서는 다루지 않습니다.