1. this가 뭐고 왜 알아야 할까?
2. 언제 바인딩이 될까?
3. 코드를 분석해 보자!
4. 느낀점
이 글은 제가 이해한 바탕으로 작성한 글이여서 틀린 내용이 있을수 있습니다.그런 부분은 피드백을 주세요!
This랑 친해져야 했던 계기
function RockBand(members) { this.members = members; this.perform = function () { console.log(this); setTimeout(function () { console.log(this); this.members.forEach(function (member) { member.perform(); }); }, 1000); }; } let me = new RockBand([ { name: "kim", perform: function () { console.log("이건 출력되야함"); }, }, ]); me.perform();
이 코드를 보고 this가 무엇을 가리키고 있는지 예상하지 못하고 알고 싶지도 않았지만 js를 좀 더 익숙하게 다루기 위해서는 알아야 된다고 생각이 들었다.이 글은 이 코드를 하나하나 분석해 보면서 결국 this가 무엇을 가리키고 있는지를 통해서 this에 대한 궁금증과 두려움을 해소 할수 있는 글이라고 생각하고 진행해보겠다!
this가 뭐고 왜 알아야 할까
this라는게 무엇일까? 생각을 하면 이걸 통해서 무엇을 할 수 있는지 알수가 있다.this는 호출한 놈을 가리키는 것이다!특히 객체를 가리킨다.즉 어디서,어떻게 호출되어있는지에 따라서 가리키는 객체가 달라진다는걸 알수가 있다.
function foo(){ console.log(this) // window } foo(); const obj={ foo(){console.log(this)} // obj } obj.foo();
이 코드를 보면 함수가 어떻게 호출 되었는지 한눈에 파악 할 수 있다. foo함수가 무엇에 의해 호출되었는지 알 수 있는가? 알수 있으면 js고수이다.저 코드만 보고 foo함수는 무엇에 의해 호출 되었는지 알 수 가 없다.그래서 없다고 생각해서 this를 찍어보면 window 객체가 나온다.그렇다 앞에 window 객체가 숨어 있는거였다.궁금하면
console.log(window.foo)
라고 찍어보면 알 수 가 있다.저 다음에 있는 foo메소드는 무엇에 의해 호출 되었는지 한눈에 파악이 가능하다.바로 obj이라는 객체로 통해서 호출이 되어서 obj에 담겨져 있는 객체 정보가 나오는걸 볼수 있다.그렇다면 이걸 통해서 뭘 할 수 있을까? 바로 객체 안에 가지고 오고 싶은 속성들이나 메소드들을 자유자재로 불러올 수 있다는 점을 알 수 가 있었다.전역 객체(node,브라우저 환경)
브라우저 :
window
, globalThis: window
node.js : {} - 모듈에 의해서! REPL에서는 :
global
, globalThis : global
this 바인딩
지금까지 this가 뭔지,this를 통해서 무엇을 할 수 있는지 알아보았다.이제 this가 디테일하게 언제 바인딩 되는지 살펴보고 자바스크립트의 독특한 동작 방식인 동적 바인딩과 정적 바인딩에 대해서 살펴보면서 this가 무엇을 가리키는 지 예상 할 수 있는 능력을 갖춰보자.
함수 안에서 this
- 느슨한 모드
function a(){ console.log(this) // window }
- 엄격 모드
'use strict' function a(){ console.log(this) // undefined }
- 이걸 통해서 객체에 속하지 않는 this는 의미가 없다 라는 걸 알 수 있었다.
객체 안에서 this
- 객체 리터널 - 해당 객체를 가리킴
const obj={ getName:function () { console.log(this); } } obj.getName() // obj const obj={ getCheck:function(){ console.log(this); // obj }, name:{ getName:function(){ console.log(this); // name } } } obj.getCheck() // obj에서 호출! obj.name.getName() // 여기에서는 name에서 호출하고 있기 때문!
- 생성자 함수 - 생성될 인스턴스를 가리킴
function Person(name){ this.name=name; this.getName=function(){ console.log(this); // Person console.log(this.name); // Person.name } } const person1=new Person("kim"); person1.getName(); // 여기서 생성된 인스턴스를 가리키는걸 알수 있다. const person2=new Person("real kim"); person2.getName();// 여기서 생성된 인스턴스를 가리키는걸 알수 있다.
- 클래스 - 생성될 인스턴스를 가리킴
class Person{ constructor(name){ this.name=name; } getName(){ console.log(this); console.log(this.name); } } const person1=new Person("real kim"); person1.getName();// 여기서 생성된 인스턴스를 가리키는걸 알수 있다.
⭐ 동적 바인딩
- this가 가리키는 대상이 함수의 호출 주체 또는 그 방식에 따라 달라짐
- 자바스크립트의 독특한 동작방식
const korean={ name:'', getAge:function(isAge){ return `${isAge ? `${isAge}세` : ""} ${this.name}`; } } korean.getAge(); const korean1={ name:'jung' } korean1.getAge=korean.getAge; korean1.getAge(); //여기서 가리키는 this는 해당 객체인 korean1으로 동적으로 바인딩 되었다.
- ⭐ 함수가 누가,어떻게 호출되었는가에 따라 this가 가리키는 대상이 달라진다.
동적 바인딩 해결 방법들
- call(this의 대상,인자(들)→ ,로 나열해서 구분)
korean.getAge.call(korean1)
→ korean.getAge는 korean1를 가리킨다!korean.getAge.call({name:’ho’})
→ 이런식으로도 활용 가능!
- apply(this의 대상,인자(들) → 배열로 표시)
korean.getAge.apply(korean1)
→ korean.getAge는 korean1를 가리킨다!- bind(this의 대상,인자(들)→ ,로 나열해서 구분) - ⭐ 함수를 반환 ⇒ 이걸 활용해서 다양한걸 할수 있을 것 같다!
- 활용 1
const korean={ name:'kim', age:25, getAge:function(){ return `${this.age} ${this.name}`; }, thisChangeAge:function(){ return this.getAge.bind(this) } } const korean1={ name:'jung' } korean1.getAge=korean.thisChangeAge(); // 함수를 반환하므로 바로 실행! korean1.getAge(); // 25세 kim
function Person(name){ this.name=name; this.getAge=function(isAge){ return `${isAge ? `${isAge}세` : ""} ${this.name}`; }; this.getAge=this.getAge.bind(this) } const korean1=new Person('kim'); const korean2=new Person('jung'); korean2.getAge=korean1.getAge; korean2.getAge(23) // 23세 kim
정적 바인딩
- ⭐ 정적 바인딩은 함수가 어디서 선언되었는지에 따라 다름 ⇒ 가장 근접한 상위 스코프에 고정
const obj = { x: 1, y: 2, func1: function () { console.log('1.', this); }, func2 () { console.log('2.', this); }, func3: () => { console.log('3.', this); } } // this가 해당 객체를 가리킴 obj.func1(); obj.func2(); // 💡 this가 상위 스코프를 가리킴 obj.func3();
- 생성자 함수와 클래스에서는 해당 인스턴스를 가르킴
function Korean () { this.favorite = '김치'; this.makeStew = function (isHot) { return `${isHot ? '매운' : '순한'} ${this.favorite}찌개`; }; this.fryRice = (isHot) => { return `${isHot ? '매운' : '순한'} ${this.favorite}볶음밥`; }; } function Italian () { this.favorite = '피자'; } const korean = new Korean(); const italian = new Italian(); console.log(korean.makeStew(true)); // 매운 김치찌개 console.log(korean.fryRice(true)); // 매운 김치찌개 italian.makeStew = korean.makeStew; italian.fryRice = korean.fryRice; console.log(italian.makeStew(false)); // 순환 피자찌개 console.log(italian.fryRice(false)); // 순환 김치볶음밥 -> 정적으로 바인딩 되는걸 알수 있다.
- call,bind,apply의 this 인자가 무시된다!
배열의 고차함수 메소드의 thisArg
배열의 고차함수 메소드 안에 여러 인자가 존재하지만 thisArg라는 매개변수가 거의 다 들어가 있다. 이게 무엇인지 궁금했고 조사해보았다.
- 콜백으로 주어진 함수 내에서
this
가 가리킬 대상 (사실상 별개 아니였다…)
const me={name:'kim',age:25}; array.forEach(function(){console.log(this.name,this.age)},me); // function선언에 주목!
이제 본격적으로 코드 분석해보자!(this 추적)
function RockBand(members) { this.members = members; this.perform = function () { console.log(this); // me를 가리킨다.(생성자 함수로 인스턴스를 생성했기때문에 기본적으로 인스턴스를 가리킨다) setTimeout(function () { console.log(this); // window를 가리킨다.(콜백함수는 누군가에 의해서 호출이 정의되어있지 않아서 this는 window가 가리킨다) this.members.forEach(function (member) { member.perform(); }); // window.members는 존재하지 않아서 오류가 나온다! }, 1000); }; } let me = new RockBand([ { name: "kim", perform: function () { console.log("이건 출력되야함"); }, }, ]); me.perform();
- 해결하는 방법!
//1.화살표 함수 function RockBand(members) { this.members = members; this.perform = function () { setTimeout(() =>{ console.log(this); // 상위 스코프를 가리키기 떄문에 me 인스턴스를 가리킨다 this.members.forEach(function (member) { member.perform(); }); }, 1000); }; } //2.call 활용 function RockBand(members) { this.members = members; this.perform = function () { setTimeout(function (){ console.log(this); // me를 가리킨다(call은 동적 바인딩을 내가 원하는 this로 바꿀수 있어서) this.members.forEach(function (member) { member.perform(); }); }.call(this), 1000); }; } //3.따로 정의해서 사용 function RockBand(members) { let that=this; this.members = members; this.perform = function () { setTimeout(function (){ console.log(that); // me를 가리킨다(this는 따로 정의 되어 있어서) that.members.forEach(function (member) { member.perform(); }); }, 1000); }; }
느낀점
비동기 콜백함수안에 this를 추적하는게 감이 안잡혔는데 결국 어디서 호출하는지에 따라서 결정되는걸로 생각하면 쉽게 this를 추적할 수 있었다.다양한 메소드를 활용해서 내가 자유자재로 다루고 할 수 있을정도로 연습을 해야할것 같고 이런 과정을 통해서 완전한 친한 친구가 될것 같다.