This란?

자바스크립트는 함수들이 호출될때, 해당 스코프의 Excution Context가 생성이 된다. 자바스크립트 런타임은 이 실행 컨텍스트를 콜 스택에 쌓고, 라인을 벗어나면서 스택에 쌓인 컨텍스트를 실행한다.
이 context 에는

  1. Scope 내 변수 및 함수
  2. arguments
  3. 호출 근원 (caller)
  4. this
    와 같은 정보들이 담겨 있으며, this는 해당 함수가 현재 어느 실행 컨텍스트에서 실행되고 있는지를 알려주는 식별자다.

스크린샷 2019-03-22 오전 2.46.04.png
(크롬 개발자도구를 통해 스텍에 쌓인 Excution Context 값, 스코프의 변수, 함수 목록 및 this를 확인 할 수 있다.)

매 함수가 실행될때 새로운 실행 컨텍스트가 생기게 되므로, this의 값은 해당 스코프의 this는 해당 함수가 실행되는 동안에 이용할 수 있다.
위에서 설명한것 처럼 this의 값은 실행 컨텍스트의 식별자 역할로, 함수 호출시점에 바인딩되게 된다.

다음으로, this가 바인딩되는 값들을 케이스 별로 확인하고자 한다.

1. Global object

Global Scope 혹은 단순한 함수안에서 this가 실행된다면, this는 global object를 바인딩한다.
Browser 환경이라면 global object는 window, Node 환경이라면 global object는 global이 된다.

let name = 'Window'
console.log(this.name); // Window
function foo(){
    console.log(this); 
}
foo(); // window
const value = 'window value';
function outer(){
    return function inner() {
        console.log(this.value);
    }
}
outer(); // 'window value'

클로저 환경에서도 역시 this는 window 객체를 바인딩하는것을 알 수 있다.

(function foo(){
    console.log(this); 
})(); // 'window'

IIFE에서도 동일하다.

함수에서 strict mode가 선언되었다면, this는 undefined가 된다.

function foo() {
    'use strict'
    console.log(this);
}
foo(); // undefined

이는 strict mode는 global 변수를 생성하는것을 막기 때문이다.
(https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Strict_mode)

2. Object의 Method

object의 메소드에서 this가 실행된다면, this는 메소드로 실행되는 부모 object를 바인딩하게 된다.

const user = {
    age: 28,
    name: 'ddinggu',
    foo() {
        console.log('this : ', this);
    }
}

user.foo(); // this: {age:28, name: 'ddinggu', foo: f}

그러나, 아래 예시를 보면 조금 헷갈릴 수 있다.

function foo() {
    'use strict';
    console.log(this.name);
}
const user = {
    age: 28,
    name: 'ddinggu',
    foo,
    foo2() {
        console.log(this);
    }
}
const test = user.foo2;
user.foo2(); // user { age:28, ... }
uesr.foo(); // 'ddinggu'
test(); // window

위에서 들었던 예시들로 생각하면
user.foo2()는 알겠는데, user.foo()의 결과값은 foo가 단순함수이므로 window object값을 받아와야 하고,
test()의 결과값은 object의 메소드로 사용된다면 위의 예시들처럼 부모 object 값을 받아와야 할 것이라고 생각 할 수 있다.

그러나, this는 어떤 컨텍스트에서 선언되는지가 아닌 어떤 컨텍스트에서 실행되는지로 판단해야 해야한다.

따라서, user.foo() 같은 경우 foo 함수가 user object의 메소드로 선언되어 실행되었기 때문에 this가 부모 object를 바인딩한다.
또한, test()는 object의 메소드가 아닌 user object의 foo2 key의 value 값인 함수 자체로 선언되어 이 함수가 실행 될때의 컨텍스트는 global이므로 window object를 반환한다.

3. Constrution mode ( new 연산자 )

new 연산자로 생성된 function 영역의 this는 새로 생성된 인스턴스를 바인딩한다.

function User(age, name) {
    this.age = age;
    this.name = name;
    this.userInfo = function() {
        console.log(`Name: ${this.name}, Age: ${this.age}`);
    }
}
const writer = new User(100, 'ddinggu');
writer.userInfo(); // 'Name: ddinggu, Age: 100'
const reader = new User(20, 'John');
reader.userInfo(); // 'Name: John, Age: 20'

4. call & apply 호출

call, apply는 Function 프로토타입에 존재하여 모든 함수의 메소드로 사용 가능하다.
이 메소드들은 this 값과 인자값을 사용자가 직접 지정할 수 있다는 공통점을 갖고 있고, 인자값의 type이 다른 차이점이 있는데, 이는 예시를 통해 보도록 하자.

function User(age, name) {
    this.age = age;
    this.name = name;

    this.userInfo = function() {
        console.log(`Name: ${this.name}, Age: ${this.age}`);
    }
}

const writer = new User(100, 'ddinggu');
writer.userInfo(); // 'Name: ddinggu, Age: 100'
const reader = new User(20, 'John');
writer.userInfo.call(reader); // 'Name: John, Age: 20'
const add = function (x, y) {
    this.sum = x + y;
};
const target = {
    sum: 0
};
add.apply(target, [1, 2]);
console.log(target.sum); // 3
add.call(target, 4, 5);
console.log(target.sum); // 9

apply, call 메소드는 첫번째 argument로 this를 바인딩할 대상이다. 나머지 argument들은 실행할 함수의 인자값으로 넘겨주는데, array로 넘겨줄지 목록으로 넘겨줄지의 차이를 갖고 있다.

5. arrow function

ES6에서 새로 나온 함수 선언 표현방법으로, 위에서 정리한 this 바인딩 규칙을 무시하여 바인딩을 갖지 않는다.

const foo = {
    bar: 123,
    callBar: () => console.log(`bar: ${this.bar}`)
};
foo.callBar(); // undefined

위 예시는 this가 부모 object를 바인딩 하지 않고 window object를 바인딩하여 undefined가 나오게 된다.

arrow function은 this를 바인딩하지 않아야하는 경우에 유용하게 쓰일 수 있다.

const jane = {
    name: "Jane",
    logHello: function (friends) {
        const that = this;
        friends.forEach(function (friend) {
            console.log(that.name + " says hello to " + friend);
        });
    }
};

// http://webframeworks.kr/tutorials/translate/arrow-function 예시 참고

위 코드는 forEach의 콜백 함수에서 this가 window object를 바인딩하여 최상위 object의 name가져오지 못하는 문제 때문에, 상위 스코프에서 that을 선언해서 부모 object의 name을 가져오고 있다.

위와 같은 경우에 arrow function을 사용하면 that과 같은 변수를 선언할 필요 없이 편리하게 name value값을 가져올 수 있다.

const jane = {
   name: "Jane",
   logHello: function (friends) {
     friends.forEach(friend => {
       console.log(this.name + " says hello to " + friend);
     }); 
   }
};

정리

this는 해당 함수가 현재 어느 실행 컨텍스트에서 실행되고 있는지를 알려주는 식별자로, 특정한 조건에 따라 다른 값으로 바인딩이 된다.

총 5가지 경우를 살펴봤는데, 정리해서 한번 더 살펴보면

  1. 단순한 함수안에서 사용하는 경우와 Global Scope에서 사용되는 경우에는 window object(Node 환경에선 global)로 바인딩 된다.

  2. Method로 사용된다면 부모 object로 바인딩 된다.

  3. new 연산자를 이용하여 새로운 인스턴스를 생성할 때는 인스턴스 그 자체로 바인딩이 된다.

  4. apply, call 메소드로 실행이 되면 해당 메소드의 첫번째 인자값으로 바인딩 된다.

  5. ES6의 arrow function은 위의 바인딩 규칙을 따르지 않는다.