상세 컨텐츠

본문 제목

자바스크립트: this

자바스크립트

by lazz 2021. 10. 18. 01:44

본문

반응형

자바스크립트의 this는 다른 언어의 this와 다르다고 한다. 어떻게 다를까? this에 대해 알아보자.

this

자바의 this는 객체의 인스턴스가 자기 자신을 참조하는 값이다.

public Class Person {
    String name;

    public Person(String name) {
        this.name = name;
    }

    public print() {
        System.out.println(this.name);
    }
}

Person p = new Person("James");
p.print(); // James

언뜻 보면 자바스크립트도 비슷하게 동작한다.

function Person(name) {
  this.name = name;
}

Person.prototype.print = function() {
  console.log(this.name);
}
const p = new Person('James');
p.print(); // James

하지만 사실 자바스크립트 this의 값은 함수를 호출한 방법에 의해 결정된다. 정적으로 함수가 선언될 때 this의 값이 정해지는 것이 아닌, 동적으로 호출된 방법에 따라 값이 정해진다.

위에서 호출된 방법이라는 용어를 반복해서 사용했는데, 방법이란 다음과 같다.

  • 함수 호출
  • 메소드 호출
  • 생성자 함수 호출 (new)
  • apply, call, bind 호출

함수 호출

기본적으로 this는 전역 객체에 바인딩된다. 전역 객체는 브라우저의 경우 window 객체, 서버(node)의 경우 global 객체를 의미한다.

// browser
console.log(this); // window 객체

// var 변수를 전역 스코프에서 선언하면 전역객체에 등록된다
// let, const는 해당 없음
var a = 10;
console.log(this.a); // 10

function func() {
    this.b = 20;
    console.log(this);  
}

console.log(this.b); // undefined
func(); // window
console.log(this.b); // 20

메소드 호출

객체의 메소드로 등록된 함수의 this는 해당 객체에 바인딩된다.

var obj = {
    a: 10,
    func: function() {
        console.log(this);
    }
}

console.log(obj.a); // 10
console.log(obj.func()); // obj 객체

어떤 객체에서 함수가 호출되었냐에 따라 this가 정해진다고 이해하면 쉽다.

// 전역객체에 등록됨
var a = 10;

function print() {
    console.log(this.a);
}

print(); // 10

var obj = {
    a: 20,
    foo: print,
    bar: function() {
        console.log(this.a);
    }
};

obj.foo(); // 20
obj.bar(); // 20

위 예시를 보면 전역 스코프에서 실행된 print 함수는 10을 출력하지만 obj 객체에서 같은 동일한 함수를 호출하면 20이 출력된다. 함수가 각자 다른 객체에서 호출되었기 때문이다.

apply, call, bind 호출

함수의 this 값을 전달받아 바인딩한다.

function foo(b, c) {
    this.b = b;
    this.c = c;
    console.log(this);
}

var bar = {
    a: 10
};

// bar를 this 값으로 설정해 foo 함수를 호출한다
foo.call(bar, 20, 30); // {a: 10, b: 20, c: 30}

// 인자를 배열로 받는 점만 제외하면 call과 거의 동일하다
foo.apply(bar, [20, 30]); // {a: 10, b: 20, c: 30}

// bind는 this 값을 전달받아 호출하는 새로운 함수를 반환한다
var newFoo = foo.bind(bar, 20, 30);
newFoo(); // {a: 10, b: 20, c: 30}

생성자 함수 호출(new)

new 키워드로 함수를 호출하면 생성자 함수로 동작한다.

function foo() {
    this.a = 10;
}

var bar = new foo();
console.log(bar.a); // 10

언뜻 보면 자바의 클래스와 유사하지만, 내부적으로는 자바스크립트의 프로토타입으로 클래스를 흉내 냈다고 볼 수 있다.

생성자 함수가 호출되면 4가지 과정을 거친다.

  • 객체 생성
  • this 바인딩
  • 프로퍼티 생성
  • 객체 반환

위에서 호출한 생성자 함수를 흉내 내보자.

function foo() {
    this.a = 10;
}

var bar = new foo();
console.log(bar.a); // 10


// 객체 생성
var obj = {};

// this 바인딩
// obj를 this 값으로 설정해 foo 함수를 호출한다
foo.call(obj);

// 새로 생성된 객체의 prototype을 호출 함수의 prototype으로 설정한다
Object.setPrototypeOf(obj, foo.prototype);

// 만약 생성자로 호출된 함수가 return 값이 있다면 해당 값을 return 한다
// return 값이 없다면 위에서 생성된 객체 (obj)가 반환된다
var newObj = obj;

console.log(newObj.a); // 10

이렇게 this 값이 결정되는 총 4가지 방법에 대해 알아보았다... 만 사실 하나 더 있다.

Arrow function

ES6에서 도입된 화살표 함수.
화살표 함수는 자체의 this값이 없고 함수를 둘러싼 lexical scope의 this가 사용된다.

function Person(){
  this.age = 0;

  setInterval(function func() {
    this.age++;
    console.log(this.age);
  }, 1000);
}
// func 함수가 콜백으로 실행되는 환경의 this는 Person이 아니라 window다
var p = new Person(); // undefined, Nan, Nan, Nan...

위의 경우 window.age의 값을 0으로 설정해야 제대로 동작한다.

화살표 함수를 사용해보자.

function Person(){
  this.age = 0;

  setInterval(() => {
    this.age++; // this는 Person 객체를 참조
  }, 1000);
}

var p = new Person(); // 1, 2, 3, 4...

화살표 함수의 this는 Person 객체로 정해지기 때문에 콜백으로 실행되는 환경에서도 정상적으로 작동한다.

결론

개인적으로 화살표 함수의 this는 정적으로 정해지기 때문에 this의 값이 더 직관적으로 느껴지기 때문에 선호한다. 화살표 함수를 사용할 수 없는 경우를 제외하면 화살표 함수를 사용하는 게 좋지 않을까??
(간결하고 문법이 예쁘기도 하다...)

Reference

https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/this
https://yuddomack.tistory.com/entry/자바스크립트-this의-4가지-동작-방식
https://poiemaweb.com/js-this
https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Functions/Arrow_functions

반응형

관련글 더보기

댓글 영역