본문 바로가기
개발(Development)/JS(자바스크립트)

JS: DOM 이벤트 리스너로 객체의 메소드 연결 시, this가 변경되는 문제

by 카레유 2021. 6. 21.

자바스크립트에서

생성자함수나 class 등을 통해 객체(인스턴스)를 생성한 후,

HTML 엘리먼트의 이벤트 리스너로 객체(인스턴스)의 메서드를 연결하면,

객체의 메서드 내부에서 사용된 this가 객체 본인이 아닌, HTML엘리먼트를 가르키는 문제가 발생한다.


# 문제 상황

문제가 발생하는 코드는 아래와 같다.

브라우저 상에서 keydown이벤트가 발생하면, 객체의 keyList 배열에 키보드값을 push하는 코드다.

 

<생성자함수 버전>

<!DOCTYPE html>
<html lang="en">
    
<head>
</head>

<body>    
</body>

<script>
// 생성자함수 선언
function KeyboardTracker(){

    // 변수: 키보드 입력값을 담아둘 배열
    this.keyList = [];

    // 메서드: 키보드 입력값을 배열에 담는다.
    this.onKeyDown = function(event){
        // 입력된 키값 확인
        console.log(`onKeyDown: ${event.code}`);

        // this 확인
        console.log(`this: ${this}`);

        // 객체의 프로퍼티인 keyList배열에 입력된 키보드값 담아두기.
        this.keyList.push(event.code);
    }

}

// 인스턴스(객체) 생성
const keyboardTracker = new KeyboardTracker();

// document의 이벤트 리스너로 "인스턴스의 메서드" 연결
document.addEventListener('keydown', keyboardTracker.onKeyDown);


</script>
</html>

 

<클래스 버전>

(정확히는 프로토타입 메서드 부분이 다르지만, 그냥 넘어가자.)

<!DOCTYPE html>
<html lang="en">
    
<head>
</head>

<body>    
</body>

<script>
// 클래스 선언
class KeyboardTracker{
    constructor(){
        // 변수: 키보드 입력값을 담아둘 배열
        this.keyList = [];
    }

    // 메서드: 키보드 입력값을 배열에 담는다.
    onKeyDown(event){
        // 입력된 키값 확인
        console.log(`onKeyDown: ${event.code}`);

        // this 확인
        console.log(`this: ${this}`);

        // 객체의 프로퍼티인 keyList배열에 입력된 키보드값 담아두기.
        this.keyList.push(event.code);
    }

}

// 인스턴스(객체) 생성
const keyboardTracker = new KeyboardTracker();

// document의 이벤트 리스너로 "인스턴스의 메서드" 연결
document.addEventListener('keydown', keyboardTracker.onKeyDown);


</script>
</html>

 

위 코드를 브라우저에서 실행한 후, 키보드에서 엔터키 등을 누르면 콘솔에 아래와 같이 출력된다.

1. 키보드 입력값은 Enter로 잘 찍힌다.

2. this가 document로 찍힌다: keyboardTracker가 아니다.

3. this.keyList.push(event.code); 코드는 에러가 발생한다: undefined에서 push를 호출할 수 없다고 뜬다.

 

당연히 this가 keyboardTracker일 것으로 예상하고

입력된 키보드값을 keyboardTracker.keyList 배열에 추가하려고 작성한 코드이지만,

이유를 알기 함든 에러가 발생하는 것이다.

 

 

문제의 원인은 이렇다.

일반적인 함수의 경우, this는 함수를 호출하는 주체로 바인딩되는 경우가 대부분이다.

위 코드의 경우, 이벤트리스너로 연결된 함수를 호출하는 주체가 document이기 때문에 this는 document가 되는 것이다.

만약 window나 button에 연결 되었다면, this는 window나 button가 되었을 것이다.

 

따라서 위 코드에서는 this에 document가 바인딩 되며, document에는 keyList라는 배열이 없기 때문에

undefined에서 push를 호출하는 꼴이 되어 에러가 발생하는 것이다.


# 해결 방법

해결하는 방법은 다양하겠지만, bind()를 추천한다.

bind()는  함수.bind(원하는 객체)  형태로 사용하며, 함수 내부의 this를 "원하는 객체"로 바꾼 함수를 반환한다.

 

위 코드에서는 아래와 같이 사용하면 된다.

이벤트 리스너로 연결되는 함수의 this를 keyboardTracker객체로 바인딩한다.

document.addEventListener('keydown', keyboardTracker.onKeyDown.bind(keyboardTracker));

 

브라우저에서 실행후, 엔터키를 입력한 직후의 콘솔 내용은 아래와 같다.

this에 object로 뜨는데, 여기서는 keyboardTracker 인스턴스다.

 

keyboardTracker.keyList 배열 내용도 아래와 같이 정상적으로 확인 된다.

 

해결 완료!


해결이 완료된 코드 전문은 아래와 같다.

(클래스의 경우, 프로토타입 메소드로 생성되는 부분은 그냥 넘어가자)

 

<생성자함수 버전>

<!DOCTYPE html>
<html lang="en">
    
<head>
</head>

<body>    
</body>

<script>
// 생성자함수 선언
function KeyboardTracker(){

    // 변수: 키보드 입력값을 담아둘 배열
    this.keyList = [];

    // 메서드: 키보드 입력값을 배열에 담는다.
    this.onKeyDown = function(event){
        
        // 입력된 키값 확인
        console.log(`onKeyDown: ${event.code}`);

        // this 확인
        console.log(`this: ${this}`);

        // 객체의 프로퍼티인 keyList배열에 입력된 키보드값 담아두기.
        this.keyList.push(event.code);
    }

}

// 인스턴스(객체) 생성
const keyboardTracker = new KeyboardTracker();

// document의 이벤트 리스너로 "인스턴스의 메서드" 연결: bind() 사용!!!
document.addEventListener('keydown', keyboardTracker.onKeyDown.bind(keyboardTracker));


</script>
</html>

 

<클래스 버전>

<!DOCTYPE html>
<html lang="en">
    
<head>
</head>

<body>    
</body>

<script>
// 클래스 선언
class KeyboardTracker{
    constructor(){
        // 변수: 키보드 입력값을 담아둘 배열
        this.keyList = [];
    }

    // 메서드: 키보드 입력값을 배열에 담는다.
    onKeyDown(event){
        
        // 입력된 키값 확인
        console.log(`onKeyDown: ${event.code}`);

        // this 확인
        console.log(`this: ${this}`);

        // 객체의 프로퍼티인 keyList배열에 입력된 키보드값 담아두기.
        this.keyList.push(event.code);
    }

}

// 인스턴스(객체) 생성
const keyboardTracker = new KeyboardTracker();

// document의 이벤트 리스너로 "인스턴스의 메서드" 연결: bind() 사용!!!
document.addEventListener('keydown', keyboardTracker.onKeyDown.bind(keyboardTracker));


</script>
</html>

 

 

 

댓글