tech.junha.kr

자바스크립트 this 바인딩 정리

대전제

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

obj ={
	func1:func1,
}

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

obj.method();
class Foo {
	method() {
    	console.log(this);
        [1,2].forEach(function(i) {
        	console.log(this); // forEach 내부에서 callback()으로 불리기에 "함수로써" 호출
        });
    }
}

const f = new Foo()
f.method();

// Foo, window, window

화살표 함수 vs 일반 함수

  • 화살표 함수는 호출 시 새로운 실행 컨텍스트를 만들때, this 바인딩을 하지 않는다.
  • ECMAScript 명세에 따르면, 화살표 함수는 Environment Record에서 this 값을 설정하지 않는다고 명시되어 있다.
  • 그렇기 때문에 this 값을 불러오기 위해 자연스럽게 상위 스코프로 체이닝해서 값을 찾는다.
class Foo {
	method() {
    	console.log(this);
        [1,2].forEach((i) => {
        	console.log(this); // ()=>{}에서 this값이 없으므로 스코프 체이닝해서 값을 찾음
        });
    }
}

const f = new Foo()
f.method();

// Foo, Foo, Foo
const obj1 = {
  method() {
    console.log(this);
  },
};

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

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

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

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

obj1.method();
obj2.method();
obj3.method();

중간정리

  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값이 변하는 게 아니다. function를 호출할 때 생성되는 실행 컨텍스트의 this값을 참조하는 것이다.

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

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

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

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

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

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

"use strict"

  • Without "use strict": this in a standalone function call refers to the global object. (Window, global)
  • With "use strict": this in a standalone function call is undefined.