Skip to content

Javascript의 this 키워드

작성일: at 오전 04:10

Table of Contents

Open Table of Contents

this 키워드

this 키워드는 현재 코드가 실행되는 Scope의 Context를 참조한다.

기본적으로 global object (브라우저에서는 window, Node에서는 global)에 binding 되며 함수의 호출 방식과 'use strict' 선언에 따라 결정된다.

만약 객체 내부에 function 키워드로 정의된 함수를 객체를 통해 호출하면 해당 함수 내부의 this는 객체를 가리킨다.

함수를 객체를 통하지 않고 단독으로 호출하면 일반적으로는 전역 객체를 가리키며, 앞서 설명한 바와 같이 'use strict'를 함수 내부나 외부에 선언한 경우에는 this가 undefined가 된다.

객체 프로토타입의 call, apply, bind 메소드로 this를 명시적으로 전달할 수 있다.

화살표 함수로 선언한 함수는 this가 정적 바인딩 된다. (자신의 this를 가지지 않는다) 이 말은 함수의 실행 시점이 아니라 함수가 정의된 위치의 상위 스코프의 this를 상속받는다는 의미이다. 또한 화살표 함수는 앞에서 설명한 bind, call, apply와 같은 함수 객체 프로토타입 함수로 명시적 바인딩이 불가능하다.

this는 항상 객체를 가리킨다. 그러나 'use strict' 키워드로 엄격 모드로 설정되면 모든 형태의 값이 될 수 있다.

함수

함수에서 this 바인딩은 호출 방법에 따라 다르게 동작한다.

const obj = {
    property_1: '1',
    some_func: function() {
        return this
    }
}

/*
    아래 함수는 다음과 같은 객체를 반환한다.
    Object { property_1: "1", some_func: some_func() }
*/
obj.some_func()

obj.some_func().some_func() // 이러한 호출도 가능하다

함수의 호출이 객체를 통해 이루어졌고 some_func 함수 내부의 this는 obj 객체가 되었다.

const obj = {
    name: 'obj'
    getThis() {
        return this
    }
}

const obj2 = {
    name: 'obj2'
}

obj2.getThis = obj.getThis()

/*
    obj2를 반환한다. this는 함수의 호출 시점에 결정된다는 것을 알 수 있다.
*/
console.log(obj2.getThis())

'use strict' 엄격 모드에서의 동작도 살펴보자

위에서 설명한 바와 같이 엄격 모드에서 this는 객체 뿐만 아니라 다른 값도 될 수 있다.

function getThisStrict() {
  "use strict"; // Enter strict mode
  return this;
}

// 엄격 모드에서는 글로벌 객체가 바인딩되지 않는다.
getThisStrict() // undefined

this가 객체가 아닌 다른 값으로 설정되는 예시는 아래와 같다.

function getThisStrict() {
  "use strict"; // Enter strict mode
  return this;
}

// Only for demonstration — you should not mutate built-in prototypes
Number.prototype.getThisStrict = getThisStrict;
console.log(typeof (1).getThisStrict()); // "number"

1은 원시 값이지만 (1)은 Number 객체로 변환된다.

Number 객체로부터 호출된 엄격 모드 함수의 this는 객체가 아닌 해당 원시 값이 되는 것을 볼 수 있다.

그리고 엄격 모드의 함수가 단독 호출되면 undefined이다.


function func() {
    'use strict'
    return this
}
console.log(func()) // undefined

함수 객체의 프로토타입 함수 bind, apply, call을 사용하면 this를 명시적으로 전달할 수 있다.

이때 위 함수를 사용해서 this를 바인딩 하는 경우에도 엄격 모드의 원칙은 유지된다.

(() => {
    'use strict'
    function a() {console.log(this.hello)}

    const obj = {hello: 'world'}

    // this를 전달하지 않았고, 엄격 모드이기 때문에 a 함수의 this는 undefined이다.
    a.call()
})()

Callback 함수

콜백 함수도 마찬가지로 호출 방식에 따라 this가 결정된다.

(() => {
const fn_with_callback_in_object = (cb) => {
    const a = {
        name: 'my name is a',
        fn: cb
    }

    a.fn()
}

const fn_with_callback = cb => cb()

fn_with_callback(function() {
    // 
    'use strict'
    console.log('전통적인 function의 this: ', this)
})

fn_with_callback(() => {
    'use strict'
    console.log('arrow function의 this: ', this)
})

// 아래 호출에서 콜백이 function 키워드로 선언한 것과, 화살표 함수로 선언한 것은 차이가 있다. 화살표 함수는 global object를 기본적으로 바인딩하지 않는다.

// function 키워드를 사용하면 this는 객체가 된다.

fn_with_callback_in_object(function() {
    console.log('전통적인 function 키워드를 사용한 콜백 함수를 객체를 통해 실행하면 this는 : ', this)
})

// 아래는 글로벌 객체가 바인딩 된다.
// arrow function은 자신만의 this를 가지지 않는다.
// 따라서 객체를 통해 메소드로서 호출되어도 해당 객체가 this에 바인딩 되지 않고, this는 글로벌 객체를 가리킨다.

fn_with_callback_in_object(() => {
    console.log('arrow function으로 정의한 콜백 함수를 객체를 통해 실행하면 this는 : ', this)
})
})()
function say() {
    // 객체가 아닌 단독 호출 시 this는 전역 객체이다.
    // user 객체를 통해 실행하면 this는 user 객체이다.
    console.log('say 함수 안에서의 this: ', this)
    let arrow = () => this.name
    console.log(arrow())
}

let user = {
    name: 'joo',
    say
}

say() // 엄격 모드가 아니므로 전역 객체를 바인딩한다.
user.say() // 화살표 함수는 상위 컨텍스트의 this를 가져온다는 것을 알 수 있다.

forEach나 map 처럼 Array 객체의 반복 함수의 콜백에서 this가 어떻게 나타나는지도 살펴보자

let group = {
  title: "1모둠",
  students: ["보라", "호진", "지민"],

  showList() {
    this.students.forEach(function(student) {
      alert(this.title + ': ' + student)
    });
  }
};

group.showList();

this가 group 객체를 가리키지 않고, 전역 객체인 window를 가리킨다. 전통적 function 선언 함수는 호출 대상의 객체를 바인딩하는데, forEach 함수 내부에서 특정 객체를 통해서 실행하지 않았다면 전역 객체를 가리킨다. (엄격 모드에서는 undefined)

이벤트 리스너 콜백

이벤트 리스너의 콜백이 화살표 함수라면 thisWindow 객체를, function 키워드를 사용한 함수라면 Event 객체의 currentTarget 속성을 가리킨다.

li.addEventListener('click', e=> {
    // 화살표 함수는 렉시컬 스코프에 따라 this가 결정된다.
    // 따라서 this는 window 객체가 바인딩된다.
    // 단, addEventListener 메소드가 함수 또는 클래스 내부에 작성된다면 해당 클래스 또는 함수의 컨텍스트에 바인딩된다.
    const currentTarget = e.currentTarget;

    // false!
    console.log(this === currentTarget)

    // true!
    console.log(this === window)
})
li.addEventListener('click', function(e){
    // 콜백 함수를 function 키워드로 선언하면 이벤트가 발생할 때 해당 함수에 this를 currentTarget 속성으로 바인딩한다.
    // 일반 함수의 동적 바인딩때문에 이러한 동작이 가능한 것이다.
    const currentTarget = e.currentTarget;

    // true!
    console.log(this === currentTarget)
})
const bookStore = {
    _this: this, // Window!
    title: '파이썬',
    buy: function() {
        return this
    },
    buy_with_arrow_fn: () => {
        return this // undefined
    },
    fninfn: function() {
        console.log('fninfn에서 this: ', this)
        return this.buy_with_arrow_fn() // 
    }
} 

console.log('객체의 this: ', bookStore._this) // window 객체 반환

// function 키워드를 사용한 함수와 화살표 함수에서 this 차이

// 화살표 함수는 this를 동적으로 바인딩하지 않는다. 선언될때의 렉시컬 스코프의 상위 스코프의 this 값, 즉 선언 당시의 this 값을 끝까지 가져간다.

console.log('1: ', bookStore.buy()) // function 키워드로 선언한 함수를 bookStore를 통해 호출하면 this는 bookStore.
console.log('2: ', bookStore.buy_with_arrow_fn()) // 항상 Window.
console.log('3: ', bookStore.fninfn()) // buy_with_arrow_fn 함수의 호출 환경이 바뀌어도 this는 항상 window로 고정.

생성자와 클래스 메소드

생성자 함수에서의 this는 해당 클래스의 인스턴트를 가리킨다.

function Person(name) {
  // this는 새로 생성된 객체 인스턴스를 참조
  this.name = name;
  this.sayHello = function() {
    console.log(`Hello, I'm ${this.name}`);
  };
  this.arrowFn = () => {
    console.log(`Hello, I'm ${this.name}`);
  }
}

const person1 = new Person('Kim');
person1.sayHello(); // "Hello, I'm Kim"

// 만약 아래와 같이 인스턴스 생성 없이 메소드를 호출하면 this가 window 또는 undefined가 된다.

const method = Person.sayHello;
method(); // name 변수가 전역에 선언되어 있지 않으면 undefined.

// 단, 화살표 함수로 선언한 메소드는 인스턴스를 통해서만 접근할 수 있다.
const arrowMethod = Person.arrowFn;
arrowMethod(); // arrowMethod is not a function

간단하게 설명하면, 일반 메소드는 클래스 객체의 프로토타입에 정의되며 모든 인스턴스가 하나의 함수 참조를 공유한다. 그러나 화살표 함수는 인스턴스마다 별도의 함수 복사본이 생성된다. 그래서 인스턴스 없이 접근할 수 없다.

class Test {
  regular() { return this; }
  arrow = () => this;
}

const t1 = new Test();
const t2 = new Test();

// 프로토타입 메소드는 공유됨
console.log(t1.regular === t2.regular); // true

// 화살표 함수는 각 인스턴스마다 별도 생성
console.log(t1.arrow === t2.arrow); // false

클래스 생성자에서 이벤트 바인딩 시 주의해야 하는 점은 아래와 같다.

class Button {
  constructor(text) {
    this.text = text;
    this.element = document.createElement('button');
    
    // 클래스 메소드는 this를 동적으로 바인딩 할 수 있기 때문에 이벤트 호출 시 currentTarget으로 바인딩된다. 따라서 handleClick에 접근할 수 없다.
    this.element.addEventListener('click', this.handleClick);
    
    // 화살표 함수는 this를 동적 바인딩하지 않기 때문에 인스턴스를 가리킬 수 있다.
    this.element.addEventListener('click', () => this.handleClick());
    
    // 아래와 같이 bind 메소드를 사용하면 새로운 handleClick 메소드가 생성 및 할당된다.
    // 이렇게 생성된 메소드는 이벤트의 바인딩을 무시하고 최초의 바인딩 값을 사용한다.
    this.handleClick = this.handleClick.bind(this);
    this.element.addEventListener('click', this.handleClick);
  }
  
  handleClick() {
    console.log(`Clicked on: ${this.text}`);
  }
}