티스토리 뷰

Frontend/JavaScript

[JavaScript] this?

blueprint-12 2022. 9. 29. 20:55

이전에 정리한 생활코딩의 내용을 빌려오자면 this는 이렇게 정의할 수 있다. 

this ?

  • this란 함수 내에서 함수 호출 맥락(context)를 의미한다.
  • 함수를 어떻게 호출하느냐에 따라서 this가 가리키는 대상이 달라진다는 의미이다.
  • 함수와 객체의 관계가 느슨한 자바스크립트에서 this는 이 둘을 연결시켜주는 실질적인 연결점 역할을 한다.
  • this는 상위 객체를 가르킨다. apply ,call을 활용해 제어가 가능하다. 

함수호출

  • 어떠한 객체에 소속되어 있지 않은 함수를 호출했을 때 this는 무엇을 가리키는가? -> this === window 
  • 사실상 window가 생략됐을 뿐이지(암시적으로 사용되고 있는 window) window가 해당 함수를 포함하는 전역객체인 것이다.

🙋‍♂️this는 무엇인가요?

this는 자신이 속한 객체 또는 자신이 생성할 인스턴스를 가리키는 자기 참조 변수(self-reference variable)입니다.

this를 통해 자신이 속한 객체 또는 자신이 생성할 인스턴스의 프로퍼티나 메서드를 참조할 수 있습니다.

 

🙋‍♂️this란 어떻게 동작하나요?

JavaScript에서 함수의 this 키워드는 다른 언어와 다르게 동작합니다.

또한, 엄격 모드(strict mode)와 비엄격 모드에서도 일부 차이가 있습니다.

 

대부분의 경우, this의 값은 함수를 호출한 방법에 의해 결정됩니다. 실행중에는 할당으로 설정할 수 없고 함수를 호출할 때마다 다를 수 있습니다. ES5에서는 함수를 어떻게 호출했는 지에 상관하지 않고 this 값을 설정할 수 있는 bind 메서드를 도입했습니다. 더하여 ES6(ES2015)는 스스로 this 바인딩을 제공하지 않는 화살표 함수를 추가했습니다(이는 렉시컬 컨텍스트* 안의 this 값을 유지합니다).

 

👾this를 이해하기 위해서는 실행 컨텍스트의 내용도 필요할 거 같아서 따로 빼서 정리하겠습니다. 

 

자바스크립트에서 this가 참조하는 것이 "함수가 호출되는 방식에 따라 결정되는 것"을 "this binding"이라고 합니다.

🤸‍♀️this binding 5가지

1. 기본 바인딩(Default Binding)

기본 규칙, 아래의 4가지 규칙에 해당되지 않으면 기본 규칙을 따릅니다. (this는 전역 객체로 바인딩된다. window객체)

 

단순 호출

엄격 모드 아님/ this의 값이 호출에 의해 설정 X, 기본값으로 전역객체를 참조하는 예시

function f1(){
        return this;
    }

    // 브라우저에서 전역객체 window 
    f1() === window; // true;
    // node.js에서 전역객체 global 
    f1() === global; // true;

엄격 모드 (this 값은 실행 문맥에 진입하며 설정되는 값을 유지합니다.)

function f2() {
    'use strict'; //엄격 모드 함수 단위로 적용
    return this;
  }

  f2() === undefined; // true;
  window.f2() === window //true;
 ⚠ f2를 객체의 메서드나 속성(e.g. window.f2()) 으로써가 아니라 직접 호출하면 this는 undefined여야 한다. 
그러나, 엄격 모드를 처음 지원하기 시작한 초기 브라우저에서는 구현하지 않았고, window 객체를 잘못 반환하였다. 
+ 2022 09 기준 크롬 개발자 툴에서는 undefined 가 잘 찍힌다. 

-> 엄격모드에서는 기본 바인딩 대상에서 전역객체는 제외된다. 전역 객체를 참조해야할 this가 있다면 그 값은 undefined가 됩니다.

 

2. 암시적 바인딩(Implicit Binding)

암시적 바인딩이란, 함수가 객체의 메서드로서 호출되는 상황에서 this가 바인딩되는 것을 말합니다.

const test = {
    prop: 42,
    func: function () {
      return this.prop;
    },
  };

  console.log(test.prop); // 42

이때 this는 해당 함수를 호출한 객체, 즉 컨텍스트 객체에 바인딩됩니다.

const myobj = {
    myval: 6,
    myWindow: this,
    myFunc: function () {
      console.log(this);
    },
    myArrow: () => {
      console.log(this);
    },
  };
  console.log(myobj.myFunc()); //myobj 객체
  console.log(myobj.myArrow()); //window 객체

 

function 키워드는 해당 객체를 바인딩하여 this값을 변경하는 것이고, () => {}는 상위 객체의 this를 그대로 이어받아 사용하는 것입니다.  (5번째에 화살표 함수에 대한 자세한 내용이 있습니다.)

 

⚠ 주의) 암시적 바인딩을 사용할 때 문제 상황은 해당 메소드(함수)를 매개변수(콜백)으로 넘겨서 실행할 때생깁니다.

setTimeout(foo.bar, 1); // foo.bar은 foo객체의 bar메소드 레퍼런스를 콜백으로 넘긴 것

객체에 정의되어 있는 함수의 레퍼런스를 매개변수로 전달하는 상황에서는 undefined가 뜹니다.

setTimeout 내부에서 호출되는 콜백은 foo 객체의 콘텍스트에서 실행되는 것이 아니기 때문에, this는 기본 바인딩이 적용돼어 전역 객체에 바인딩됩니다.

 

3. 명시적 바인딩(Explicit Binding)

자바스크립트의 모든 function은 call(), apply(), bind() 라는 프로토타입 메소드를 가지고 있습니다.

이 메서드 중 하나를 호출함으로써 this 바인딩을 코드에서 명시하는 것을 명시적 바인딩이라고 합니다.

이때, this는 내가 명시한 객체가 됩니다. -> 메소드의 첫번째 매개변수로 넘겨주는 객체로 바인딩

 

세 메소드에 대한 자세한 설명은 하단에 있습니다. 

4. new 바인딩

자바스크립트의 "new 키워드"는 함수를 호출할 때 앞에 new 키워드를 사용하는 것으로 객체를 초기화할 때 사용합니다. 이때 사용되는 함수를 "생성자 함수" 라고 합니다. (코드 컨벤션: 생성자 함수는 대문자로 시작해야 합니다.)

 

new 키워드를 통해 객체를 생성하면, this가 해당 객체에 바인딩되어 해당 값을 읽게됩니다. ES6에 추가된 class를 사용하면 동일하게 작동합니다.

//얘는 생성자 함수 
function Binding() {
    this.a = 20;
  }

  const bind = new Binding();
  console.log(bind.a);
  
//ES6 class (생성자 함수를 만들려고 하면 IDE에서 class로 변환하라고 안내가 뜨네요.)
class Test {
    constructor(name) {
      this.name = name;
    }
    sayHi() {
      return this.name;
    }
  }

  const test1 = new Test('이름');
  test1.name; //이름

그리고 생성자 함수에서는 this키워드를 해당 생성자를 이용해 생성할 객체에 대한 참조로 사용합니다. 

 

5. 화살표 함수(ES6)

화살표 함수는 this를 바인딩할 때, 앞선 바인딩 규칙들이 아니라 this에 어휘적 스코프(lexical scope)가 적용됩니다. 

즉, 화살표 함수를 정의하는 시점의 컨텍스트 객체가 this에 바인딩된다는 소리입니다. 

const foo = {
  a: 20,
  bar: function () {
    setTimeout(() => {
      console.log(this.a);
    }, 1);
  }
}

foo.bar(); // 20

 

setTimeout의 콜백함수의 this는 foo 객체를 가리키고 있기 때문에, 콜백이 실행될 때 this는 foo를 가리키게 됩니다. 

 

🕵️‍♀️이 속성을 언제 활용 하면 좋을까?

  • 화살표 함수로 선언시에 렉시컬 스코프를 통해 바인딩된 this는 apply,call,bind등의 함수나 new 함수로 오버라이드할 수 없습니다. 그렇기 때문에 주로 콜백 함수로 사용할 때 유용합니다. 

 

+

렉시컬 스코프(Lexical Scope)

스코프? 변수에 접근할 수 있는 범위를 나타냅니다. -> 함수가 선언되면 무조건 스코프가 생성된다.
컨텍스트? this 키워드 값이 무엇인지를 나타내는 용어 -> 함수가 속해있는 객체가 무엇인지 의미한다.

다른 말로, 정적 스코프(static scope)라고 부릅니다. 

함수를 어디서 호출하는 지가 아니라 어디에 선언하였는 지에 따라 결정되는 것을 말합니다.

렉시컬 스코프는 함수를 처음 선언하는 순간, 함수 내부의 변수는 자기 스코프로부터 가장 가까운 곳(상위 범위에서)에 있는 변수를 계속 참조하게 됩니다.

var x = 1; //global

  function first() {
    var x = 21;
    second();
  }
  function second() {
    console.log(x);
  }

  first(); // 1
  second(); //1

first는 second 함수를 호출하고 있는데 세컨드함수는 자기와 가장 가까운 변수를 상위 범위에서 찾기 때문에 전역변수인 x 를 변수값으로 사용하게 됩니다. 

즉, 함수를 어디서 선언하였는 지에 따라 상위 스코프를 결정한다는 뜻이며, 함수 호출이 아니라 함수 선언에 따라 결정되는 것입니다. 

 


예외적인 경우

// Jquery
$('div').on('click', function() {
  console.log(this); // <div>
  function normalFunc() {
    console.log(this); // Window 객체
  }
  const arrowFunc = () => {
    console.log(this); // <div>
  }
});

jquery, React 등 일부 라이브러리에서 엘리먼트에 이벤트를 추가할 때, 콜백함수에 this를 사용하면 값이 바뀌는 경우가 있습니다. -> 라이브러리 상에서 this를 바인딩해주는 경우가 있으니 해당 라이브러리의 document를 찾아보는 것이 중요합니다. 

 

정리하자면, this는 기본적으로 window이지만, 객체 메서드, bind call apply, new일 때 this가 바뀝니다.

function의 this는 항상 window라는 것 (strict 모드에서는 undefined )


🙋‍♂️apply(), call(), bind()

⭐자바스크립트에서 this를 바꿀 수 있도록 해주는 메소드를 제공하는데 바로 apply, call, bind입니다.

 

this의 값을 한 문맥에서 다른 문맥으로 넘기려면 call()이나 apply() 메소드를 사용하면 됩니다.

call 또는 apply의 첫 번째 인자로 객체가 전달될 수 있으며 this가 그 객체에 묶이게 됩니다.

 const obj = { a: "obj's a" };
  var a = 'global';
  function whatsThis() {
    return this.a;
  }

  //변수 a가 let이나 const일 경우, undefined
  whatsThis(); // global
  whatsThis.call(obj); //"obj's a"
  whatsThis.apply(obj); //"obj's a"
function add(c, d) {
    return this.a + this.b + c + d;
  }
  var o = { a: 1, b: 3 };
  add.call(o, 5, 7); // 16
  add.apply(o, [10, 20]); //34

비엄격 모드에서 this로 전달된 값이 객체가 아닌 경우, call과 apply는 이를 객체로 변환하기 위한 시도를 합니다. 

null과 undefined 값은 전역 객체가 됩니다. 7이나 "안녕" 같은 원시값은 관련된 생성자를 사용해 객체로 변환됩니다. 

 

bind 메서드

ECMAScript5(ES6) 는 Function.proptotype.bind를 도입했습니다. f.bind(obj)를 호출하면 f와 같은 본문(코드)과 범위를 가졌지만 this는 원본 함수를 가진 새로운 함수를 생성합니다. 새 함수의 this는 호출 방식과 상관없이 영구적으로 bind의 첫번째 매개변수로 고정됩니다.

🙋‍♂️bind가 call, apply와 차이가 있다면?

거의 동일하게 활용하지만 함수를 실행하지 않고 함수를 생성한 후 반환하는 점에서 call(), apply()와의 차이가 있습니다. 즉, 함수를 한 번 더 호출해야 하지요.

 

세 가지 메서드 비교 정리

const cong = {
    age: 27,
    gender: 'female',
  };

  function printProfile(name) {
    console.log(name, this.age, this.gender);
  }

  printProfile.apply(cong, ['alexa']); //alexa 27 female
  printProfile.call(cong, '콜입니다.'); //콜입니다. 27 female
  printProfile.bind(cong, 'cong').call(); //cong 27 female
  • apply(): call 함수와 유사하지만,  두번째 매개변수로 배열을 받습니다. 
  • call(): 객체를 바인딩함과 동시에 호출합니다. 두번째 매개변수로 변수의 목록을 받습니다. 
  • bind(): 바인딩된 함수를 반환하며, 한번 더 호출 시 함수를 실행합니다. 

 

 

 

 

참고자료

 

this - JavaScript | MDN

JavaScript에서 함수의 this 키워드는 다른 언어와 조금 다르게 동작합니다. 또한 엄격 모드와 비엄격 모드에서도 일부 차이가 있습니다.

developer.mozilla.org

 

https://www.zerocho.com/category/JavaScript/post/5740531574288ebc5f2ba97e

 

www.zerocho.com

 

어중간히 알면 안되는 JS 개념 #1 this

무심코 지나가게 될 수도 있는 "this"의 개념을 확실히 짚고 지나가 봅시다.

velog.io

 

[JS] 알쏭달쏭 자바스크립트 this 바인딩

Java, C# 같은 객체지향 프로그래밍 언어들은 this라는 키워드를 사용한다(Python 에는 self라는 키워드가 있다). 이때 this가 의미하는 것은 해당 코드를 실행하는 클래스의 인스턴스를 나타낸다. 자바

seungtaek-overflow.tistory.com

댓글