this

yoo chang heon·2022년 4월 25일
0

함수와 객체(메서드)의 구분이 느슨한 자바스크립트에서 this는 실질적으로 이 둘을 구분하는 거의 유일한 기능이다.

this

JS에서 this는 기본적으로 실행 컨텍스트가 생성될 때 함께 결정된다. => 함수를 호출할 때 결정된다. => 어떤 방식으로 함수를 호출하느냐에 따라 값이 달라진다.

전역 공간에서의 this

전역공간에서 this는 전역 객체를 가리킴 => 전역 컨텍스트를 생성하는 주체가 전역객체 (전역객체는 JS 런타임환경에 따라 다른 이름과 정보를 가지고 있다.)
전역변수를 선언하면 자바스크립트 엔진은 이를 전역객체의 프로퍼티로도 할당. 변수이면서 객체의프로퍼티이기도 함.
자바스크립트의 모든 변수는 실은 특정객체의 프로퍼티로서 동작 => var로 변수를 선언하더라도 실제 자바스크립트 엔진은 어떤 특정 객체의 프로퍼티로 인식함(특정객체란 실행 컨텍스트의 LexicalEnvironment)

전역변수 선언과 전역객체의 프로퍼티 할당이 거의 비슷하지만 삭제에서 차이점을 보인다.

var a=1;
a===window.a===this.a

전역객체의 프로퍼티로 할당한 경우에는 삭제가 되지만 전역변수로 선언한 경우에는 삭제가 되지 않음 => 사용자가 의도치 않게 삭제하는 것을 방지
즉 전역변수를 선언하면 자바스크립트 엔진이 이를 자동으로 전역객체의 프로퍼티로 할당하면서 추가적으로 해당 프로퍼티의 configurable 속성(변경 및 삭제가능성)을 fasle로 정의하는 것.

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

함수 vs 메서드

  • 함수로서 호출
  • 메서드로서 호출
    이 둘은 독립성 차이(함수는 그 자체로 독립적인 기능을 수행, 자신을 호출한 대상 객체에 관한 동작을 수행)
    => 상황별로 this 키워드에 다른 값을 부여함으로써 이를 구현
    .이 앞에 있는지로 둘을 구분(점이 있으면 method, 점이 없으면 함수)
    어떤 함수를 호출할때 그 함수 이름앞에 객체가 명시되어 있는 경우 메서드, 아닌경우 모두 함수로 호출

메서드 내부에서의 this

this에는 호출한 주체에 대한 정보가 담김.
어떤 함수를 메서드로써 호출하는 경우 호출 주체는 바로 함수명 앞의 객체
점 표기법의 경우 마지막 점 앞에 명시된 객체가 곧 this

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

함수 내부에서의 this

어떤 함수를 함수로서 호출할 경우에는 this가 지정되지 않음 => 함수에서의 this는 전역 객체를 가리킴

메서드 내부에서의 this

this 바인딩에 관해서는 함수를 실행하는 당시의 주변환경은 중요하지않고, 오직 해당 함수를 호출하는 구문 앞에 점 또는 대괄호 표기가 있는지 없는지가 관건인 것.

메서드 내부 함수에서의 this를 우회하는 방법

ES5) 변수를 활용
상위 스코프의 this를 저장해서 내부함수에서 활용

this를 바인딩하지 않는 함수

ES6에서 함수 내부에서 this가 전역객체를 바라보는 문제를 보완하고자, this를 바인딩하지 않는 화살표 함수를 새로 도입(실행 컨텍스트 생성할 때 this 바인딩 과정 자체가 빠지게 됨)
call, apply 등의 메서드를 활용해서 함수를 호출할 때 명시적으로 this를 지정할 수도 있음.

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

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

콜백함수도 함수이기 때문에 기본적으로 this가 전역객체를 참조하지만, 제어권을 받은 함수에서 콜백 함수에 별도로 this가 될 대상을 지정한 경우는 그 대상을 참조하게 된다.
콜백함수에서의 this는 무조건 뭐라고 할 수 없고, 콜백함수의 제어권을 가지는 함수가 콜백함수에서의 this를 무엇으로 할지를 결정하며, 특별히 정의하지 않는 경우 기본적으로 함수와 마찬가지로 전역을 바라봄

생성자 함수 내부에서의 this

생성자 함수: 어떤 공통된 성질을 지니는 객체를 생성하는데 사용하는 함수
(인스턴스를 만들기 위한 틀)

JS에서는 함수에 생성자로서의 역할을 부여 => new 명령어와 함께 함수를 호출하면 해당 함수가 생성자로서 동작
어떤 함수가 생성자 함수로서 호출된 경우 내부에서의 this는 곧 새로 만들 구체적인 인스턴스 자신이 된다.

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

call 메서드

Function.prototype.call(thisArg[,arg1[,arg2[,...]]])

call 메서드는 메서드의 호출 주체인 함수를 즉시 실행하도록 만듬 => call 메서드의 첫번째 인자를 this로 바인딩하고, 이후 인자들을 호출할 함수의 매개변수로 만듬
=> 그냥 함수실행하면 this는 전역이지만 call하면 임의의 객체로 this 지정가능

apply 메서드

Function.prototype.apply(thisArg[,argsArray])

call과 기능적으로 동일하지만 call은 첫번째 인자를 제외한 나머지 모든 인자들을 호출할 함수의 매개변수로 지정하는 반면, apply 메서드는 두번째 인자를 배열로 받아 그 배열의 요소들을 호출할 하뭇의 매개변수로 지정한다

call, apply 활용

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

객체에는 배열 메서드를 직접 사용할 수 없다. 그러나 키가 0또는 양의 정수인 프로퍼티가 존재하고, length 프로퍼티의 값이 0또는 양의 정수인 객체=> 유사 배열 객체의 경우 call또는 apply를 통해 배열 메서드를 차용할 수 있음.

단 문자열의 경우 length, index 프로퍼티를 가지지만 읽기전용이기 떄문에 원본 문자열에 변경을 가하는 메서드(push,pop,shift,unshift, splice)등에는 에러를 던지며, concat처럼 대상이 반드시 배열이어 하는 경우에는 에러는 나지 않지만 제대로 된 결과를 얻을 수 없다.
(ES6에서는 유사배열객체 또는 순회 가능한 모든 종류의 데이터 타입을 배열로 전환하는 Array.from 메서드를 도입)

생성자 내부에서 다른 생성자를 호출

생성자 내부에 다른 생성자와 공통된 내용이 있을 경우 call 또는 apply를 이용해 다른 생성자를 호출하면 간단하게 반복을 줄일 수 있다.

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

bind 메서드

Function.prototype.bind(thjisArg[, arg1[, arg2[, ...]]])

bind 메서드는 ES5에서 추가된 기능
call과 비슷하지만 즉시 호출하지는 않고, 넘겨받은 this 및 인수들을 바탕으로 새로운 함수를 반환하기만 하는 메서드. 다시 새로운 함수를 호출할 때 인수를 넘기면 그 인수들은 기존 bind 메서드를 호출할 때 전달했던 인수들의 뒤에 이어서 등록
this를 미리 적용하는 것과 부분 적용함수를 구현하는 두가지 목적

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

메서드의 this를 그대로 바라보기위해 self등의 변수로 우회. => call, apply, bind 를 써서 더 깔끔하게 처리가능

화살표 함수의 예외사항

ES6에 새롭게 도입된 화살표 함수는 실행 컨텍스트 생성 시 this를 바인딩하는 과정이 제외되었습니다. 즉 이 함수내부에는 this가 아예 없으며, 접근하고자 하면 스코프체인상 가장 가까운 this에 접근=> 별도의 변수로 this를 우회하거나 별도의 call/apply/bind 필요 없음

별도의 인자로 this를 받는 경우

정리

  • 전역공간에서의 this는 전역객체 참조

  • 어떤 함수를 메서드로서 호출한 경우 this는 메서드 호출 주체를 참조

  • 어떤 함수를 함수로서 호출한 경우 this는 전역객체(메서드 내부 함수에서도 동일)

  • 콜백 함수 내부에서의 this는 해당 콜백 함수의 제어권을 넘겨받은 함수가 정의한 바에 따르며, 정의안하면 전역객체

  • 생성자 함수에서의 this는 생성될 인스턴스를 참조

    명시적일 때

  • call, apply 메서드는 this를 명시적으로 지정하면서 함수 또는 메서드를 호출합니다.

  • bind 메서드는 this 및 함수에 넘길 인수를 일부 지정해서 새로운 함수를 만듬

  • 요소를 순회하면서 콜백함수를 반복 호출하는 내용의 일부 메서드는 별도의 인자로 this를 받기도 함


Arrow function (2-depth)

전통적인 함수표현식의 간편한 대안이다. 하지만 몇가지 제한점이 있고 모든 상황에 사용할 수는 없다.

1. this 나 super에 대한 바인딩이 없고, methods로 사용될 수 없다.
2. new.target 키워드가 없다.
3. 일반적으로 스코프를 지정할 때 사용하는 call,aply,bind 메서드를 이용할 수 없다.
4. 생성자로 사용할 수 없다.
5. yield를 화살표 함수 내부에서 사용할 수 없다.

화살표함수가 나오기 전까지는 모든 새로운 함수는어떻게 그 함수가 호출되는지에 따라 자신의 this 값을 정의했다. 하지만 이는 객체 지향 스타일로 프로그래밍할 때 별로 좋지 않았다.

예시)

문제코드

function Person() {
 // Person() 생성자는 `this`를 자신의 인스턴스로 정의.
 this.age = 0;

 setInterval(function growUp() {
   // 비엄격 모드에서, growUp() 함수는 `this`를
   // 전역 객체로 정의하고, 이는 Person() 생성자에
   // 정의된 `this`와 다름.
   this.age++;
 }, 1000);
}

var p = new Person();

ECMAScript 3/5 에서는, 이 문제를 this 값을 폐쇄될 수 있는 (비전역) 변수에 할당하여 해결

function Person() {
 var that = this;
 that.age = 0;

 setInterval(function growUp() {
   // 콜백은  `that` 변수를 참조하고 이것은 값이 기대한 객체이다.
   that.age++;
 }, 1000);
}

화살표 함수는 자신의 this가 없다. 대신 화살표 함수를 둘러싸는 렉시컬 범위(lexical scope)의 this가 사용된다.

function Person(){
 this.age = 0;

 setInterval(() => {
   this.age++; // |this|는 Person 객체를 참조
 }, 1000);
}

var p = new Person();

setInterval에 전달 된 함수 내부의 this는 setInterval을 포함한 function의 this와 동일한 값

call 또는 apply를 통한 피호출

화살표함수에서는 this가 바인딩되지 않기 때문에 call() 또는 apply() 메서드는 인자만 전달하고, this는 무시됨.

var adder = {
 base : 1,

 add : function(a) {
   var f = v => v + this.base;
   return f(a);
 },

 addThruCall: function(a) {
   var f = v => v + this.base;
   var b = {
     base : 2
   };

   return f.call(b, a);
 }
};

console.log(adder.add(1));         // 뭘까요?
console.log(adder.addThruCall(1)); // 뭘까요?

바인딩 되지 않는 arguments

화살표함수는 arguments 객체를 바인드 하지 않는다. arguments는 그저 둘러싸는 범위 내 이름에 대한 참조일 뿐이다.

new 연산자 사용

화살표 함수는 생성자로서 사용될 수 없으며 new와 함께 사용하면 오류가 발생한다.

prototype 속성 사용

화살표 함수는 prototype 속성이 없다.


참고자료

0개의 댓글