TIL.This에 대하여

chloe·2020년 12월 31일
0

TIL

목록 보기
28/81
post-thumbnail

💜 This란?

자바스크립트에서 this는 어디서든 사용할 수 있다. 특히 자바스크립트에서는 함수와 객체(메서드)의 구분이 느슨하기에 this는 실질적으로 이 둘을 구분하는 거의 유일한 기능이다.
자바스크립트 내에서 this는 '누가 나를 불렀느냐"를 뜻한다. 즉,this는 함수를 호출하는 방벙에 의해 달라지는 것임

🙂상황에 따라 달라지는 this

js에서 this는 기본적으로 실행 컨텍스트가 생성될 때 함께 결정된다.
실행 컨텍스트는 함수를 호출할 때 생성되므로 다시 말하면 this는 함수를 호출하는 방법에 의해 결정된다. 누가 실행(호출)했냐? 이게 핵심!!누가가 this.
실행하는 동안의 할당에 의해 설정될 수 없고 함수가 호출될 때마다 다를 수 있다.
ES5는 함수의 this값이 함수가 어떻게 호출되었는지 개의치않고 설정할 수 있는 bind메서드를 소개했다.

1. 전역 공간에서의 this


전역 공간에서 this는 전역 객체를 가리킨다. 브라우저 환경에서 전역객체는 window이고 Node.js환경에서는 global이다.

-> 전역변수를 선언하면 자바스크립트 엔진은 이를 전역 객체의 프로퍼티로도 할당한다. 변수이면서 객체의 프로퍼티인 것이다.

var a =1;
console.log(a);//1
console.log(window.a);//1
console.log(this.a);//1

위 코드에서 window.a, this.a 모두 1이 출력된다. 전역 공간에서의 this는 전역객체니까 window와 this의 값이 같을 것이다. 그런데 왜 1이 출력됐을까?
=> 이유: 자바스크립트의 모든 변수는 특정 객체의 프로퍼티로서 동작하기 때문이다.

여기서 특정 객체란 실행 컨텍스트의 LexicalEnvironment이다. 실행 컨텍스트는 변수를 수집해서 L.E의 프로퍼티로 저장한다. 전역 컨텍스트의 경우 L.E는 전역 객체를 그대로 참조한다.

위 코드에서 변수 a에 접근하고자 하면 스코프 체인에서 a를 검색하다가 가장 마지막에 도달하는 전역 스코프의 L.E 즉 전역객체에서 해당 프로퍼티 a를 발견해서 그 값을 반환하기 때문에 a를 직접호출할 때도 1이 나온다.

2. 메서드로서 호출할 때 그 메서드 내부에서의 this


함수를 실행하는 방법 1) 함수로서 호출하는 경우 2) 메서드로서 호출하는 경우
그렇다면, 함수와 메서드는 어떻게 구분하는가? 독립성
👉함수는 그 자체로 독립적인 기능을 수행하고 메서드는 자신을 호출한 대상 객체에 대한 동작을 수행한다.

예제 코드를 살펴보자.

var func=function(x){
  console.log(this,x);
};
func(1); //Window{...}1 함수로서 호출

var obj={
  method:func
};
obj.method(2);//{method:f}2 메서드로서 호출

1) 1번째 줄, func라는 변수에 익명함수를 할당
2)4번째 줄,func를 호출했더니 this로 전역객체 Window가 출력된다.
3)6번째 줄,obj라는 변수에 객체를 할당하는데 그 객체의 method 프로퍼티에 앞에서 만든 func함수를 할당
4)마지막 줄에서 obj의 method를 호출했더니 이번에는 this가 obj라고 한다.

즉, 익명함수는 그대로인데 이를 변수에 담아 호출한 경우와 obj객체의 프로퍼티에 할당해서 호출한 경우에 this가 달라지는 것이다.

메서드 내부에서의 this
this에는 호출한 주체에 대한 정보가 담긴다.어떤 함수를 메서드로서 호출하는 경우 호출 주체는 바로 함수명 앞의 객체이다.

3.함수로서 호출할 때 그 함수 내부에서의 this


어떤 함수를 함수로서 호출하는 경우에는 this가 지정되지 않는다. this에는 호출한 주체에 대한 정보가 담긴다. 그런데 함수로 호출하는 것은 호출 주체를 명시하지 않고 개발자가 코드에 관여해서 실행한 것이니까 호출 주체의 정보를 알 수 없는 것이다.

메서드 내부 함수에서의 this
실행컨텍스트를 활성화할 당시 this가 지정되어있지 않으면 this는 전역객체를 바라본다. 함수에서의 this는 전역객체를 가리킨다. 이는 명백한 설계상의 오류라고 하는데..

예제 코드를 살펴보자

var obj 1={
  outer:function(){
    console.log(this);//(1)obj 1
    var innerFunc=function(){
      console.log(this);//(2) 전역객체(window),(3)obj2.innerMethod
    }
    innerFunc(); 
 var obj2={
   innerMethod:innerFunc
 };
    obj2.innerMethod();
  }
};
obj1.outer();

1) 1번째 줄, 객체를 생성하는데 객체 내부에는 outer라는 프로퍼티가 있고 여기에 익명함수가 연결된다. 이렇게 생성된 객체는 변수obj1에 할당한다.

2) 마지막 줄, obj1.outer를 호출한다.

3) 2번째 줄, obj1.outer함수의 실행 컨텍스트가 실행되면서 호이스팅하고, 스코프 체인 정보를 수집하고, this를 바인딩한다. obj1.method니까 메서드로서 호출한 것이고 this에는 마지막 점 앞의 객체인 obj1이 바인딩된다.

4)3번째 줄, obj1 객체 정보가 출력된다.

5)4번째 줄, 호이스팅된 변수 innerFunc는 outer 스코프 내에서만 접근할 수 있는 지역변수. 이 지역변수에 익명 함수를 할당한다.

6)innerFunc를 호출한다

7)4번째 줄, innerFunc 함수의 실행 컨텍스트가 실행되면서 호이스팅, 스코프 체인 수집,this 바인딩을 수행한다. 이는 함수로서 호출한 것이니까 this가 지정되지 않았고 자동으로 스코프 체인 상의 최상위 객체인 window(전역객체)가 바인딩된다.

8)5번째 줄, window 객체 정보가 출력된다.

9) 호이스팅된 변수 obj2 역시 outer스코프 내에서만 접근할 수 있는 지역변수. 여기에는 다시 객체를 할당하는데, 그 객체에는 innerMethod라는 프로퍼티가 있고 여기에는 앞서 정의된 변수 innerFunc와 연결된 익명 함수가 연결된다.

10) obj2.innerMethod를 호출한다.

11)9번쨰 줄,obj2.innerMethod 함수의 실행 컨텍스트가 생성된다. 이는 메서드로서 호출한 것. 따라서 this에는 마지막 점 앞의 객체인 obj2가 바인딩된다.

12) 10번째 줄, obj2객체 정보가 출력된다.

7번째 줄, outer메서드 내부에 있는 함수를 함수로서 호출 반면, 12번째 줄에서는 같은 함수를 메서드로서 호출했다. 같은 함수임에도 7번째 줄에 의해 바인딩되는 this 와 12번째 줄에 의해 바인딩되는 this의 대상이 서로 달라진 것이다.
👉 이말은 즉 this 바인딩에 관해서는 함수를 실행하는 당시의 주변환경( 메서드 내부인지, 함수내부인지)는 중요하지 않고, 해당 함수를 호출하는 구문 앞에 점 또는 대괄호 표기가 있는지 없는지가 관건이다.

메서드 내부 함수에서 this를 우회하는 방법?
호출 주체가 없을 때 자동으로 전역객체를 바인딩하는 것이 아니라 주변 환경의 this를 그대로 상속받으면 더 자연스럽다. 메서드 내부 함수에서this를 우회하는 방법은 변수를 활용하는 것이다.

var obj={
  outer:function(){
    console.log(this);//1{outer:f}
    var innerFunc1=function(){
      console.log(this);//2 Window{...}
    };
    innerFunc1();
    
    var self=this;//self라는 변수에 this를 저장한 상태에서 호출한 innerFun2의 경우 self에는 객체 obj가 출력된다.
    var innerFunc2=function(){
      console.log(self);//3 {outer:f}
    };
    innerFunc2();
  }
};
obj.outer();

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

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

바인딩이란?
바인딩(Binding) 이란 프로그램의 어떤 기본 단위가 가질 수 있는 구성요소의 구체적인 값, 성격을 확정하는 것을 말한다. 다시 말해, 함수 호출과 실제 함수를 연결하는 방법이며 각종 값들이 확정되어 더이상 변경할 수 없는 구속(bind)상태가 되는 것이다.

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


함수 A의 제어권을 다른 함수 B에게 넘겨주는 경우 함수 A를 콜백 함수라고 한다.
예제를 살펴보자.

1. setTimeout(function(){ console.log(this);},300);
//setTimeout함수, 0.3초 뒤에 전역객체가 출력된다
2. [1,2,3,4,5].forEach(function(x){
  console.log(this,x);
});
//forEach메서드는 배열의 각 요소를 앞에서부터 차례로 하나씩 꺼내어 그 값을 콜백 함수의 첫 번째 인자로 삼아 함수를 실행하라는 명령이다. 전역객체와 배열의 각 요소가 총 5회 출력된다.
3. document.body.innerHTML+='<button id="a">클릭</button>';
document.body.querySelector('#a")
       .addEventListener('click',function(e){
  console.log(this,e);
});
//addEventListener는 지정한 HTML엘리먼트에 'click'이벤트가 발생할 때마다 그 이벤트 정보를 콜백 함수의 첫번째 인자로 삼아 함수를 실행하라는 것이다.

위 예제에서 setTimeout함수forEach메서드는 그 내부에서 콜백함수를 호출할 때 대상이 될 this를 지정하지 않는다. 그러므로 콜백함수 내부에서의 this는 전역객체를 참조한다.
한편,addEventListener 메서드는 콜백함수를 호출할 때 자신의 this를 상속하도록 정의돼있다. 그러므로 메서드 명의 점(.)앞부분이 곧 this가 된다.

콜백함수의 제어권을 가진 함수(메서드)가 콜백 함수에서의 this를 무엇으로 할지 결정하며 특별히 정의하지 않은 경우에는 기본적으로 함수와 마찬가지로 전역객체를 바라본다.

5.생성자 함수 내부에서의 this


생성자 함수? 어떤 공통된 성질을 지니는 객체들을 생성하는 데 사용하는 함수
객체지향 언어에서는 생성자를 클래스,클래스를 통해 만든 객체를 인스턴스라고 한다.
=>프로그래밍적으로 생성자구체적인 인스턴스를 만들기 위한 일종의 틀이다.

자바스크립트는 함수에 생성자로서의 역할을 함께 부여했다. new명령어어와 함께 함수를 호출하면 해당 함수가 생성자로서 동작한다. 그리고 어떤 함수가 생성자 함수로서 호출된 경우, 내부에서의 this는 곧 새로 만들 구체적인 instance자신이 된다.

🙂명시적으로 this를 바인딩하는 방법

1.call 메서드

function.prototype.call(thisArg[,arg1[,arg2[,...]]])
call 메서드는 메서드의 호출 주체인 함수를 즉시 실행하도록 하는 명령이다.

이때 call 메서드의 첫 번째 인자를 this로 바인딩하고, 이후 인자들을 호출할 함수의 매개변수로 한다. 함수를 그냥 실행하면 this는 전역 객체를 참조, call 메서드를 이용하면 임의의 객체를 this로 지정

var func=function(a,b,c){
  console.log(this,a,b,c);
};
func(1,2,3);//window{...}123
func.call({x:1},4,5,6);//{x:1}456

2.apply메서드

Function.prototype.apply(thisArg[,argsArray])
apply메서드는 call메서드와 기능적으로 완전히 동일하다.그러나 call 메서드는 첫 번째 인자를 제외한 나머지 모든 인자들을 호출할 함수의 매개변수로 지정하는 반면, apply메서드는 두번째 인자를 배열로 받아 그 배열의 요소들을 호출할 함수의 매개변수로 지정한다.

var func=function(a,b,c){
  console.log(this,a,b,c);
};
func.apply({x:1},[4,5,6]);//{x:1} 456
var obj={
  a:1,
  method:function(x,y){
    console.log(this.a,x,y);
  }
};
obj.method.apply({a:4},[5,6]);//456

3.call/apply메서드의 활용

🧐유사배열객체에 배열메서드를 활용

var obj={
  0:'a',
  1:'b',
  2:'c',
  length:3
};
Array.prototype.push.call(obj,'d');
//배열메서드push를 객체 obj에 적용해 프로퍼티3에 'd'를 추가했다.
console.log(obj);
//{0:'a' 1:'b',2:'c',length:4}

var arr=Array.prototype.slice.call(obj);
//slice메서드 적용해 객체를 배열로 전환
console.log(arr);//['a','b','c','d']

ES6에서는 유사배열객체 또는 순회 가능한 모든 종류의 데이터 타입을 배열로 전환하는 Array.from메서드를 새로 도입했다.

var obj={
  0:'a',
  1:'b',
  2:'c',
  length:3
};
var arr=Array.from(obj);
console.log(arr);//['a','b','c']

👉 여러인수를 묶어 하나의 배열로 전달하고 싶을 때 apply를 활용하자

var numbers=[10,20,3,16,45]
var max=Math.max.apply(null,numbers);
var min=Math.min.apply(null,numbers);
console.log(max,min)//45,3

ES6에서는 spread operator를 이용하면 apply를 적용하는 것보다 더욱 간편하게 작성할 수 있음!(아래와 같이)

const numbers=[10,20,3,16,45]
const max=Math.max(...numbers);
const min=Math.min(...numbers);
console.log(max,min);//45 3

call/apply메서드는 명시적으로 별도의 this를 바인딩하면서 함수 또는 메서드를 실행하는 좋은 방법이지만 오히려 이로인해 this를 예측하기 어렵게 만들어 코드 해석을 방해하기도 한다. 그럼에도 ES5이하의 환경에서는 마땅한 대안이 없기에 실무에서 매우 광범위하게 활용되고 있음

4. bind 메서드

Function.prototype.bind(thisArg[,arg1,arg2[,...]]])
bind메서드는 ES5에서 추가된 기능으로 call과 비슷하지만 즉시 호출하지는 않고 넘겨받은 this및 인수들을 바탕으로 새로운 함수를 반환하기만 하는 메서드

var func=function(a,b,c,d){
  console.log(this,a,b,c,d);
};
func(1,2,3,4);//Window{...}1234
var bindFunc1=func.bind({x:1});
//bindFunc1변수에는 func에 this를 {x:1}로 지정한 새로운 함수가 담김
//this만을 지정
bindFunc1(5,6,7,8);//{x:1}5678
var bindFunc2=func.bind({x:1},4,5);
//bind는 this 지정과 함께 부분적용함수를 구현한 것
bindFunc2(6,7);//{x:1}4567
bindFunc2(8,9);//{x:1}4589

👉bind메서드를 적용해서 새로만든 함수는 name프로퍼티에 동사 bind의 수동태인 'bound'라는 접두어가 붙는다.

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

상위 컨텍스트의 this를 내부함수나 콜백함수에 전달하기

내부함수에 this전달 _call

var obj={
  outer:function(){
    console.log(this);
    var innerFunc=function(){
      console.log(this);
    };
    innerFunc.call(this);
  }
};
obj.outer();

내부함수에 this전달 _bind

var obj={
  outer:function(){
    console.log(this);
    var innerFunc=function(){
      console.log(this);
    }.bind(this);
    innerFunc();
  }
};
obj.outer();

5.화살표 함수의 예외사항

ES6에 새롭게 도입된 화살표 함수는 실행 컨텍스트 생성시 this를 바인딩하는 과정이 제외됐다. 즉 이 함수 내부에는 this가 아예없다. 접근하고자하면 스코프 체인상 가장 가까운 this에 접근한다.

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

6.별도의 인자로 this를 받는 경우(콜백함수 내에서의 this)

콜백함수를 인자로 받는 메서드 중 일부는 추가로 this로 지정할 객체(thisArg)를 인자로 지정할 수 있는 경우가 있다. 이러한 메서드의 thisArg값을 지정하면 콜백 함수 내부에서 this값을 원하는 대로 변경할 수 있다.

이런 형태는 여러 내부요소에 대해 같은 동작을 반복 수행하는 배열 메서드에 많다.
ex)ForEach메서드 예제코드

var report={
  sum:0,
  count:0,
  add:function(){
    var args=Array.prototype.slice.call(arguments);
    //add메서드는 arguments를 배열로 변환해서 args 변수에 담고,
    args.forEach(function(entry){
      //이 배열을 순회하며 콜백함수를 실행한다
      this.sum+=entry;
      ++this.count;
    },this);
    //콜백함수 내부에서의 thissms forEach함수의 두 번째 인자로 전달해준this가 바인딩된다.
  },
  average:function(){
    return this.sum/this.count;
  }
};
report.add(60,85,95);
console.log(report.sum,report.count,report.average());//240 3 80

총정리💜
1. 명시적 this바인딩이 없는 경우

  • 전역 공간에서의 this는 전역객체를 참조한다
  • 어떤함수를 메서드로서 호출한 경우 this는 메서드 호출 주체(메서드명 앞의 객체)를 참조한다
  • 어떤 함수를 함수로서 호출한 경우 this는 전역객체를 참조한다.
  • 콜백함수내부에서의 this는 해당 콜백 함수의 제어권을 넘겨받은 함수가 정의한 바에 따르며 정의하지 않은 경우에는 전역객체를 참조한다.
  • 생성자 함수에서의 this는 생성될 인스턴스를 참조한다
    2.명시적 this 바인딩
  • call,apply메서드는 this를 명시적으로 지정하면서 함수 또는 메서드를 호출
  • bind메서드는 this 및 함수에 넘길 인수를 일부 지정해서 새로운 함수를 만든다
  • 요소를 순회하면서 콜백함수를 반복호출하는 내용의 일부 메서드는 별도의 인자로 this를 받기도 한다.

참고: 코어자바스크립트

profile
Front-end Developer 👩🏻‍💻

0개의 댓글