\(@^0^@)/

[BOOK] 코어 자바스크립트, this(1) 본문

BOOKS/코어 자바스크립트

[BOOK] 코어 자바스크립트, this(1)

minjuuu 2022. 3. 11. 11:08
728x90

오늘 읽은 범위 : (this) p.65 ~ p.79


< 책에서 기억하고 싶은 내용 >

다른 대부분의 객체지향 언어에서 this는 클래스로 생성한 인스턴스 객체를 의미하고,
this를 클래스에서만 사용할 수 있기 때문에 혼란의 여지가 없거나 많지 않다.
하지만, 자바스크립트(JS) 에서의 this는 어디서든 사용할 수 있다.
함수와 객체(메서드)의 구분이 느슨한 JS에서 this는 실질적으로 이 둘을 구분하는 거의 유일한 기능이다.

상황에 따라 달라지는 this

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

1️⃣ 전역 공간에서의 this

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

// 전역변수와 전역객체

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

 

  • 전역 변수를 선언하면 JS 엔진은 이를 전역 객체의 프로퍼티로 할당한다.
    그렇기 때문에, 전역 공간에서 선언한 변수 a에 1을 할당했을 때 window.a와 this.a 모두 1이 출력되는 것.
    (또한, 전역 공간에서의 this는 전역 객체를 의미하므로 두 값이 같은 값을 출력)
  • a를 직접 호출할 때도 1이 나오는 이유는 그냥 단순하게 (window.)이 생략된 것이라고 여겨도 무방.
    (변수 a에 접근하고자 하면 스코프 체인에서 a를 검색하다가 가장 마지막에 도달하는 전역 스코프의 L.E,
    즉, 전역 객체에서 해당 프로퍼티 a를 발견해서 그 값을 반환하기 때문)
  • 전역 공간에서는 var로 변수를 선언하는 대신 window의 프로퍼티에 직접 할당하더라도 결과적으로 var로 선언한 것과 대부분 똑같이 동작한다.
    그러나, '삭제' 명령에 대해서는 전역 변수 선언과 전역 객체의 프로퍼티 할당이 다르게 동작한다.
    var a = 1;
    delete a;                           // false
    console.log(a, window.a, this.a);   // 1 1 1
    
    window.c = 3;
    delete c;                           // true
    console.log(c, window.c, this.c);   // Uncaught ReferenceError: c is not defined

    처음부터 전역객체의 프로퍼티로 할당한 경우에는 삭제가 되는데, 전역 변수로 선언한 경우에는 삭제가 안됨.
    사용자가 의도치 않게 삭제하는 것을 방지하는 차원에서 마련한 방어 전략으로 해석된다.
    즉, 전역 변수를 선언하면 JS 엔진이 자동으로 configurable 속성(변경 및 삭제 가능성)을 false로 정의하기 때문이다.
    따라서, var로 선언한 전역 변수와 전역 객체의 프로퍼티는 호이스팅 여부 및 configurable 여부에서 차이를 보인다.

 

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

  • 함수 vs. 메서드
    • 어떤 함수를 실행하는 방법은 두 가지이다. (함수로서 호출하는 경우, 메서드로서 호출하는 경우)
    • 이 둘을 구분하는 유일한 차이는 독립성.
    • 함수는 그 자체로 독립적인 기능을 수행하는 반면, 메서드는 자신을 호출한 대상 객체에 관한 동작을 수행한다.
    • 어떤 함수를 객체의 프로퍼티에 할당한다고 해서 그 자체로서 무조건 메서드가 되는 것이 아니라,
      객체의 메서드로서 호출할 경우에만 메서드로 동작하고, 그렇지 않으면 함수로 동작한다.
    • '함수로서의 호출'과 '메서드로서의 호출'을 구분하는 법 : 함수 앞에 점(.)과 대괄호의 유무.
      점이 없으면 함수로서 호출, 점이 있으면 메서드로서 호출.
      대괄호가 없으면 함수로서 호출, 대괄호가 있으면 메서드로서 호출

// 함수로서 호출, 메서드로서 호출

var func = function (x) {
    console.log(this, x);
};
func(1);                    // Window { ... } 1


var obj = {
    method: func
};
obj.method(2);              // { method: f } 2
obj['method'](3);           // { method: f } 3

위의 코드를 보면, 원래의 익명 함수는 그대로인데 이를 변수에 담아 호출한 경우와 obj 객체의 프로퍼티에 할당해서 호출한 경우 this가 달라진다.

  • 메서드 내부에서의 this
    • 어떤 함수를 메서드로서 호출하는 경우 호출 주체는 바로 함수명(프로퍼티명) 앞의 객체.
      (ex. 점 표기법의 경우 마지막 점 앞에 명시된 객체가 곧 this)

 

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

  • 함수 내부에서의 this
    • 어떤 함수를 함수로서 호출할 경우에는 this가 지정되지 않는다.
      this는 지정되지 않는 경우 전역 객체를 바라보므로, 함수에서의 this는 전역 객체를 가리킨다.
  • 메서드의 내부 함수에서의 this
    • 함수로서 호출했는지 메서드로서 호출했는지만 파악하면 this의 값을 정확히 맞출 수 있다.
    • 즉, this 바인딩에 관해서는 함수를 실행하는 당시의 주변 환경(메서드 내부인지, 함수 내부인지 등)은 중요하지 않고, 오직 해당 함수를 호출하는 구문 앞에 점 또는 대괄호 표기가 있는지 없는지가 관건.
// 내부함수에서의 this

var obj1 = {
            outer: function () {
                console.log(this);                       // (1)
                    var innerFunc = function () {
                            console.log(this);           // (2) (3)
                        }
                    innerFunc();

                    var obj2 = {
                            innerMethod: innerFunc
                    };
                    obj2.innerMethod();
                }
            };
obj1.outer();

위의 코드에서 (1)은 outer function을 호출, (2)는 innerFunc를 호출, (3)은 obj2.innerMethod를 호출한 결과를 의미.
각 console.log 위치에서 this가 가리키는 건
(1) : {outer: ƒ}               // obj1
(2) : Window { ... }         // 전역 객체(Window)
(3) : {innerMethod: ƒ}    // obj2이다.

  • 메서드의 내부 함수에서의 this를 우회하는 방법
    • ES5 까지는 자체적으로 내부함수에 this를 상속할 방법이 없지만, 변수를 활용하면 우회할 수 있음.
// 내부함수에서의 this를 우회하는 방법

var obj = {
            outer: function () {
                console.log(this);
                    var innerFunc1 = function () {
                            console.log(this);
                        }
                    innerFunc1();
                var self = this;
                var innerFunc2 = function () {
                    console.log(self);
                };
                innerFunc2();
            }
};
obj.outer();

self라는 변수에 this를 저장한 상태에서 innerFunc2를 호출할 경우 self에는 객체 obj가 출력된다.
(상위 스코프의 this를 저장해서 내부함수에서 활용하려는 수단으로, self 이외에 다른 변수명을 사용해도 되지만 일반적으로 self를 많이 사용함)

  • this를 바인딩하지 않는 함수
    • ES6에서는 this를 바인딩하지 않는 화살표 함수를 새로 도입했음.
      화살표 함수는 실행 컨텍스트를 생성할 때 this 바인딩 과정 자체가 빠지게 되어, 상위 스코프의 this를 그대로 활용할 수 있다.
// this를 바인딩하지 않는 함수(화살표 함수)

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

 

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

  • 함수 A의 제어권을 다른 함수(또는 메서드) B에게 넘겨주는 경우 함수 A를 콜백 함수라고 한다.
  • 함수 A는 함수 B의 내부 로직에 따라 실행되며, this 역시 함수 B 내부 로직에서 정한 규칙에 따라 값이 결정된다.

(1) : setTimeout 함수는 0.3초 뒤 전역 객체가 출력된다.
(2) : forEach 메서드는 전역 객체와 배열의 각 요소가 총 5회 출력된다.
(3) : addEventListener는 버튼을 클릭하면 앞서 지정한 엘리먼트와 클릭 이벤트에 관한 정보가 담긴 객체가 출력된다.

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

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

  • 생성자 함수는 어떤 공통된 성질을 지니는 객체들을 생성하는 데 사용하는 함수.
  • 객체지향 언어에서는 생성자를 클래스, 클래스를 통해 만든 객체를 인스턴스라 한다.
  • 생성자는 구체적인 인스턴스를 만들기 위한 일종의 틀.
  • new 명령어와 함께 함수를 호출하면 해당 함수가 생성자로서 동작하게 된다.
    그리고, 어떤 함수가 생성자 함수로서 호출된 경우 내부에서의 this는 곧 새로 만들 구체적인 인스턴스 자신이 된다.
var Cat = function (name, age) {
	this.bark = '야옹';
	this.name = name;
    this.age = age;
};

var choco = new Cat('초코', 7); // 해당 생성자 함수 내부에서의 this는 choco 인스턴스를 가리킴
var nabi = new Cat('나비', 5);  // 해당 생성자 함수 내부에서의 this는 nabi 인스턴스를 가리킴

console.log(choco, nabi);

/* 결과
Cat {bark: '야옹', name: '초코', age: 7}
Cat {bark: '야옹', name: '나비', age: 5}
*/

 


< 요약 정리 >

  • 전역공간에서의 this는 전역객체(브라우저에서는 window, Node.js에서는 global)를 참조한다.
  • 어떤 함수를 메서드로서 호출한 경우 this는 메서드 호출 주체(메서드명 앞의 객체)를 참조한다.
  • 어떤 함수를 함수로서 호출한 경우 this는 전역객체를 참조한다. (메서드의 내부함수에서도 같음)
  • 콜백 함수 내부에서 this는 해당 콜백 함수의 제어권을 넘겨받은 함수가 정의한 바에 따르며, 정의하지 않은 경우에는 전역객체를 참조한다.
  • 생성자 함수에서의 this는 생성될 인스턴스를 참조한다.

< 소감 4줄 요약 >

  • this는 함수를 호출할 때 결정되며, 함수를 어떠한 방식으로 호출하느냐에 따라 결괏값이 달라진다.
  • 전역 변수를 선언하면 JS 엔진은 이를 전역 객체의 프로퍼티로 할당한다.
  • '함수로서의 호출'과 '메서드로서의 호출'을 구분하는 법 : 함수 앞에 점(.)과 대괄호의 유무.
  • 콜백 함수의 this는 답이 정해져 있지 않다.

 


[ 출처 : 코어 자바스크립트 ]

728x90