tech.junha.kr

this 바인딩: 일반 함수와 화살표 함수의 this

대전제

  • this는 함수를 호출할 때 결정된다.
  • 함수를 함수로서 호출할 경우에는 this가 지정되지 않는다.
  • this에는 호출한 주체에 대한 정보가 담긴다.
  • 실행 컨텍스트를 활성화할 당시에 this가 지정되지 않은 경우 this는 전역 객체를 바라본다.
function func1() {
  console.log(this);
}

obj ={
	func1:func1,
}

func1();     // Window
obj.func1()  // obj
  • 메서드는 객체의 메서드로서 호출할 경우에만 메서드로 동작하고, 그렇지 않으면 함수로 동작한다.
  • this 바인딩에 관해서는 함수를 실행하는 당시의 주변 환경(메서드 내부인지, 함수 내부인지)는 중요하지 않다.
  • 오직 해당 함수를 호출하는 대상, 앞에 점 또는 대괄호 표기가 있는지 없는지가 관건이다.
obj = {
  method() {
    console.log(this);     // obj, obj.method()로써 호출됨(메소드)
    function innerMethod() {
      console.log(this);   // Window, innerMethod()로써 호출됨(함수)
    }
    innerMethod();
  },
};

obj.method();

화살표 함수 vs 일반 함수

  • 일반 함수는 객체의 메소드로 호출이 될 때 this는 객체를 바인딩한다.
  • 화살표 함수는 실행 컨텍스트 생성 시 this를 바인딩하는 과정이 제외된다.
obj1 = {
  method() {
    console.log(this);
  },
};

obj2 = {
  method: () => {
    console.log(this);  
  },
};
const func = obj1.method;

obj1.method(); // obj1
obj2.method(); // Window
func();        // Window
  • 일반 함수는 함수를 호출할 때 함수가 어떻게 호출되었는지에 따라 this에 바인딩할 객체가 동적으로 결정된다.
  • 화살표 함수는 함수 내부에 this가 아예 없어, 호출한 객체를 this에 바인딩하지 않는다.
  • 화살표 함수는 함수가 호출되는 시점에 스코프체인상 가장 가까운 this가 ****정적으로 결정된다.
obj1 = {
  method() {
  console.log(this)           // obj1
    function innerMethod() {
      console.log(this);      // winodw
    }
    innerMethod();
  },
};

obj2 = {
  method() {
  console.log(this)           // obj2
    const innerMethod = () => {
      console.log(this);      // obj2
    };
    innerMethod();
  },
};

obj3 = {
  method: () => {
  console.log(this)           // window
    const innerMethod = () => {
      console.log(this);      // window
    };
    innerMethod();
  },
};

obj1.method();
obj2.method();
obj3.method();
  • obj2.method()로써 호출했기 때문에 this는 obj2가 되었고, method함수도 일반 함수로 만들어졌기 때문에 this는 호출 대상인 obj2를 참조한다. this가 obj2를 참조하는 상황에서 innerMethod 함수를 선언하면 this는 스코프 체인상 가장 가까운 this인 obj2를 참조한다.
  • obj1.method()로써 호출했기 때문에 this는 obj1이 되었고, method 함수도 일반 함수로 만들어졌기 때문에 this는 호출 대상인 obj1를 참조한다. 여기서 innerMethod 함수는 일반 함수이고 객체의 메서드로써 호출된 것이 아니라 함수로써 호출 되었기 때문에 this는 window를 참조한다.
  • ob3.method()로써 호출했지만 method 함수는 화살표 함수이기 때문에 this를 바인딩하지 않았다. this를 바인딩하지 않은 상황에서 innerMethod 함수를 선언하면 this는 스코프 체인상 가장 가까운 this인 window를 참조한다.
  • obj3: 화살표 함수안에서 호출된 화살표 함수의 경우 그때의 실행 컨텍스트의 this는 아무것도 바라보지 않으니, window를 가리킨다.

중간정리

  1. 함수를 함수로써 호출할 경우 this가 지정되지 않는다.

  2. 지정되지 않는 this는 window를 가리킨다.

  3. But, 함수를 메서드로써 호출할 경우 this는 호출한 주체인 객체를 가리킨다.

  4. 화살표 함수는 메서드로써 호출하여도, 함수 내부에 this가 없기 때문에 호출되는 시점의 상위 스코프의 this를 가리킨다. (가장 가까운 this를 체이닝하는 것)

const person = {
  name: 'Lee',
  sayHi() { // === sayHi: function() {
    console.log(`Hi ${this.name}`);
  }
};

person.sayHi(); // Hi Lee​
const person = {
  name: 'Lee',
  sayHi: () => console.log(`Hi ${this.name}`)
};

person.sayHi(); // Hi undefined(window.name)

화살표 함수는 call, apply, bind 메소드를 사용하여 this를 변경할 수 없다.

window.x = 1;
const normal = function () { return this.x; };
const arrow = () => this.x;

console.log(normal.call({ x: 10 })); // 10
console.log(arrow.call({ x: 10 }));  // 1

normal은 함수로써 호출됐지만, call함수에 의해 함수 내부의 this가 {x:10}으로 바인딩 된다.

arrow 함수내부에 this가 아예 없기 때문에 thisArg를 {x:10}을 주어도 참조하지 못한다.

(최종 보스) 메소드로써 호출되었을 때 this값이 변하는 경우

function test(){
    return {
      rs : 25,
      bar : function(){
        console.log(this.rs);
      }
    }
}

test.call({rs:100})        // {rs:25, bar:function}
test.call({rs:100}).bar(); // 25
 function test(){
    return {
      rs : 25,
      bar : () => {
        console.log(this.rs);
      }
    }
}
test.call({rs:100})        // {rs:25, bar:function}
test.call({rs:100}).bar(); // 100

위의 예시가 햇갈렸던 이유

  • test.call({rs:100}): 이 코드는 thisArg값을 {rs:100}으로 바꾸는 것이지, 리턴 값 자체를 바꾸는 것이 아니다.

  • test.call({rs:100})는 함수를 실행한 코드다. 즉, 실행 컨텍스가 생성되어 콜스택에 쌓여있다는 뜻이다.

  • 따라서 test 실행 컨텍스트의 this는 {rs:100}을 갖는다

  • 화살표 함수는 메소드로써 호출되어도, 객체를 this로 바인딩 하지 않는다. 그렇기 때문에 화살표 함수는 test의 리턴 값, {rs:25, bar:f}을 this로 보지 못하고, **bar가 실행되는 시점인,**화살표 함수의 콜스택 위에 있는 실행 컨텍스트의 this값, {rs:100}을 체이닝한다.

  • 일반 함수는 메소드로써 호출되면 this값은 호출 대상을 가리키기 때문에 this는 test의 리턴 값, {rs:25, bar:f}을 바라본다.

추가

위의 예시를 보면 자칫하면 이런 생각을 할 수 있다. "화살표 함수의 this는 call과 같은 바인딩 메서드로 값이 변할 수 있구나!" BullShit! 절대 아니다. 화살표 함수는 함수 내부에 this 바인딩 과정이 생략되어 있어, 호출 대상을 this 값으로 바인딩 하지 못한다. 따라서 상위 스코프의 this를 바라볼 뿐이라, 화살표 함수에 바인딩 메서드는 전혀 의미 없다. 단지 위의 예시는 화살표 함수가 this값을 찾으러 상위 스코프의 this를 참조하려고 할 때, 바인딩 메서드의 this를 만나 그 값을 참조했을 뿐이다.

이와 달리, 일반 함수는 함수 내부에 this 값을 바인딩하는 과정이 있고, test()의 메서드로써 호출됐기 때문에 호출대상이자, 리턴 값인 객체를 this로 바라볼 수 있게 된다.

function test(){
    return {
      rs : 25,
      bar : function(){
        console.log(this.rs);
      }
    }
}

test().bar()
 function test(){
    return {
      rs : 25,
      bar : () => {
        console.log(this.rs);
      }
    }
}
test().bar()

공부했던 자료에 토달아 보기

  1. ES5 function 안의 this는 자신을 가장 마지막으로 품고 있는 scope로 항상 변한다.

=> this값이 변하는 게 아니다. ES5 function를 호출할 때 생성되는 실행 컨텍스트의 this값을 참조하는 것이다.

  1. arrow Function은 처음 바인딩 된 스코프 안에서 가리키는 this가 절대 변하지 않는다.

=> 화살표 함수는 호출되는 시점의 실행 컨텍스트의 this를 참조한다.

  1. 화살표 함수의 this는 호출된 함수를 둘러싼 실행 컨텍스트를 가리킨다.

=> 화살표 함수는 실행 컨텍스트 생성 시 this를 바인딩하는 과정이 제외되므로, 스코프 체이닝을 통하여 상위 컨텍스트의 this를 가리킨다.

  1. 일반 함수의 this는 새롭게 생성된 실행 컨텍스트를 가리킨다.

=>일반 함수의 this는 함수로 호출되면 스코프 체이닝을 통하여 바인딩되고, 메소드로 호출되면 그 객체를 바인딩한다.

화살표 함수의 다른 특징

  • 화살표 함수는 prototype 프로퍼티를 가지고 있지 않기 때문에, 생성자 함수로 사용하기에 적합하지 않다.
  • 화살표 함수에서는 arguments 변수가 전달되지 않는다.