ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [JavaScript] this
    👩🏻‍💻 정리/JavaScript 2021. 8. 26. 00:54

    목차

    1. 함수와 메소드
    2. this란?
    3. this의 동작 방식
       3.1 전역 공간에서 this가 바라보는 대상
       3.2 메소드로 호출될 때 this가 바라보는 대상 (암시적 binding)
       3.3 원하는 대상으로 this binding하기 (명시적 binding)
    4. 화살표 함수(Arrow Function)
    5. 참고 링크


    들어가기에 앞서, 다른 언어에서 this는 일반적으로 클래스(class)에서만 사용하며, class로 생성한 인스턴스 객체를 의미한다. 하지만 자바스크립트에서는 그렇지 않다. 그렇다면 자바스크립트에서의 this는 어떻게 동작할까? 

    1. 함수와 메소드

    this 공부에 앞서, 꼭 알아야 할 함수와 메소드의 차이에 대해서 짚고 넘어가 보자!

    함수와 메소드는 모두 function 키워드로 함수를 정의한 것을 의미한다. 그 중에서도 메소드는 객체의 프로퍼티로 함수가 정의되어야 한다. 여기서 중요한 건 객체가 함수를 호출해야 메소드이다. 코드를 살펴보자.

    let user = {
    	name: 'kang',
    	underTwenty: function(age) {
    		return age < 20;
    	}
    }
    
    user.underTwenty(30); // 메서드
    
    const under20 = user.underTwenty;
    under20(15); // 객체 안에 정의된 함수라도, 이것은 메서드가 아닌 함수

    즉, 메소드는 user 객체 안에 프로퍼티로 underTwenty가 함수처럼 정의되어 있어야 한다. 호출할 때 user.underTwenty()의 형식으로 호출되어야지만 메소드라고 할 수 있다. 만약 under20같이 user.underTwenty 메소드를 할당하여 under20()를 호출하게 된다면 이건 메소드가 아닌 함수이다!

    다시 말해 "객체.함수()"형식으로 호출되어야지만 메소드라고 부를 수 있다!

    2. this란?

    this란, 코드를 실행하는 현재 객체를 의미한다. 즉, 바라보고 있는 객체인데 이는 상황에 따라 대상이 달라진다. this는 실행 컨텍스트가 생성될 때 결정되는데, 실행 컨텍스트는 함수를 호출할 때 생성되므로, this는 함수를 호출할 때 결정된다.

    생성자는 전달된 매개 변수값으로 현재 객체의 속성을 설정한다. this가 없다면 속성을 지역 변수로 혼동할 수 있기 때문에 객체 속성에는 반드시 this를 붙인다. 옆에 this가 붙은 변수는 속성이고, 앞에 this가 붙은 함수는 메소드이다.

    3. this의 동작 방식

    3.1 전역 공간에서 this가 바라보는 대상

    전역 공간에서 this는 window를 의미한다! (Node.js에서는 global)

    var a = 1;
    console.log(a); // 1
    console.log(window.a); // 1
    console.log(this.a); // 1
    
    console.log(window.a === this.a); // true

    전역에서 변수를 선언하게 되면 window의 프로퍼티로 들어가게 된다.(스코프 포스팅의 전역 스코프 참조) 전역에서 this는 곧 window이기 때문에 window.a와 this.a는 같다.

    3.2 메소드로 호출될 때 this가 바라보는 대상 (암시적 binding)

    객체의 프로퍼티에 할당된 함수를 호출하면, 즉 메소드로 호출하면, this는 해당 객체를 바라본다. 좀 더 간단하게 정리하면 .(점) 바로 앞에 있는 객체가 this가 바라보는 객체이다. 코드를 살펴보자.

    var name = 'bo';
    
    var user = {
    	name: 'kang',
    	getName: function() {
    		console.log(this.name); 1️⃣ // kang
    	},
    	age: 40,
    	child: {
    		age: 10,
    		underTwenty: function() {
    			console.log(this.age); 2️⃣ // 10
    			return this.age < 20
    		}
    	}
    }
    
    user.getName();  // kang -> getName 메서드는 user 객체를 바라봄
    user.child.underTwenty();  // 10 -> underTwenty 메서드는 child 객체를 바라봄
    
    user.parentUnderTwenty = user.child.underTwenty; 3️⃣
    user.parentUnderTwenty(); // 40 -> parentUnderTwenty 메서드는 user 객체를 바라봄

    .(점) 바로 앞의 객체가 this가 바라보는 객체이기 때문에, 1️⃣ user.getName()과 2️⃣ user.child.underTwenty()는 각각 user와 child를 바라보고 있다.

    하지만, 3️⃣ user.child.underTwenty를 user.parentUnderTwenty라는 프로퍼티에 할당을 하고 user.parentUnderTwenty() 메소드를 호출하게 되면, this가 바라보게 되는 대상은 user가 된다.

    3.3 원하는 대상으로 this binding하기 (명시적 binding)

    this를 명시적으로 개발자가 정해줄 수도 있다.

    3.3.1 call

    함수를 호출할 때, 원하는 대상의 객체를 인자로 넘겨주는 메소드이다.

    var user = {
    	name: 'kang',
    	getName: function() {
    		console.log(this.name);
    	},
    	age: 40,
    	child: {
    		age: 10,
    		underTwenty: function() {
    			console.log(this.age); // 40
    			return this.age < 20
    		}
    	}
    }
    
    user.child.underTwenty.call(user); // false

    user.child.underTwenty뒤에 .call을 붙인 뒤, 인자에 원하는 객체를 넘겨주는 방식으로 사용한다. 그렇게 하면, this는 user가 된다. 단, call()을 사용하게 되면, 바로 호출된다는 점도 잊지 말자!

    3.3.2 apply

    call 메소드와 완전히 같은 기능이나, 호출할 함수에 인자를 배열로 넘기느냐 마느냐를 정할 수 있다.

    3.3.3 bind

    call과 비슷하지만, 바로 호출하는 것이 아니라 대상을 묶어놓기(binding)만 하는 메소드이다.

    class Toggle extends React.Component {
      constructor(props) {
        super(props);
        this.state = {isToggleOn: true};
    
        this.handleClick = this.handleClick.bind(this);
      }
    
      handleClick() {
        this.setState(prevState => ({
          isToggleOn: !prevState.isToggleOn
        }));
      }
    
      render() {
        return (
          <button onClick={this.handleClick}>
            {this.state.isToggleOn ? 'ON' : 'OFF'}
          </button>
        );
      }
    }

    리액트의 클래스형 컴포넌트를 쓸 때 많이 사용했던 메소드인데, 리액트 공식 문서에는 이벤트 핸들러를 사용할 때 constructor에 bind(this)를 꼭 해주라고 명시되어 있다.

    이벤트 리스너는 내부적으로 this를 이벤트가 일어난 엘리먼트로 잡아놓기 때문에 handleClick 내부의 this가 class를 가르치는 것이 아닌 엘리먼트를 가르치게 된다. 그렇기 때문에 constructor에 미리 binding 해주지 않는다면 handleClick이 발생할 때, setState함수가 존재하지 않는다는 에러가 발생하게 되는 것이다.

    하지만 우리는 엘리먼트가 아닌 컴포넌트 내부의 setState에 접근하고 싶기 때문에 constructor에 미리 binding을 해놓아야 하는 것이다. 앞서 말했듯이, 이는 this는 호출할 때 결정되는데, handleClick과 같이 이벤트 핸들러는 우리가 호출하는 것이 아니라 유저가 이벤트를 발생시키게 되면 자바스크립트 안에서 호출이 되기 때문에 제어할 수가 없는 것이다. 그렇기 때문에 이 this를 미리 constructor에 묶어놓지 않는다면 자동으로 엘리먼트로 연결되는 것이다.

    4. 화살표 함수(Arrow Function)

    위의 코드와 동일한 코드를 화살표 함수로 만들어주었다.

    class Toggle extends React.Component {
      constructor(props) {
        super(props);
        this.state = {isToggleOn: true};
      }
    
      handleClick = () => {
        this.setState(prevState => ({
          isToggleOn: !prevState.isToggleOn
        }));
      }
    
      render() {
        return (
          <button onClick={this.handleClick}>
            {this.state.isToggleOn ? 'ON' : 'OFF'}
          </button>
        );
      }
    }

    화살표 함수는 항상 this가 undefined이며 함수 내부에 this는 존재하지 않는다. 화살표 함수의 this는 호출할 때 결정되는 게 아니라 정의했을 때 바깥에 있는 스코프 체인의 객체를 가리키다 보니 원하는 대로 위의 코드에서의 this는 Toggle 컴포넌트를 가리킨다.

    그래서 화살표 함수가 등장하기 이전에는 binding을 해주는 방식으로 많이 사용하였지만, 현재는 binding을 해줄 필요 없는 화살표 함수를 많이 사용하고 있다.

    5. 참고 링크

    🔗 MDN this
    🔗 <HTML5+CSS3+JavaScript로 배우는 웹 프로그래밍 기초> 천인국 저.